[fusion-commits] r2371 - in trunk: lib lib/Proj4js lib/jxLib lib/jxLib/themes lib/jxLib/themes/crispin lib/jxLib/themes/crispin/images lib/jxLib/themes/delicious lib/jxLib/themes/delicious/images templates/mapguide/standard templates/mapserver/standard

svn_fusion at osgeo.org svn_fusion at osgeo.org
Fri Apr 15 14:46:32 EDT 2011


Author: madair
Date: 2011-04-15 11:46:31 -0700 (Fri, 15 Apr 2011)
New Revision: 2371

Added:
   trunk/lib/Proj4js/
   trunk/lib/Proj4js/proj4js-combined.js
   trunk/lib/Proj4js/proj4js-compressed.js
   trunk/lib/jxLib/
   trunk/lib/jxLib/jxlib.js
   trunk/lib/jxLib/jxlib.uncompressed.09Feb.js
   trunk/lib/jxLib/jxlib.uncompressed.24Jan.js
   trunk/lib/jxLib/jxlib.uncompressed.js
   trunk/lib/jxLib/themes/
   trunk/lib/jxLib/themes/crispin/
   trunk/lib/jxLib/themes/crispin/ie6.css
   trunk/lib/jxLib/themes/crispin/ie7.css
   trunk/lib/jxLib/themes/crispin/images/
   trunk/lib/jxLib/themes/crispin/images/a_pixel.png
   trunk/lib/jxLib/themes/crispin/images/button.png
   trunk/lib/jxLib/themes/crispin/images/button_combo.png
   trunk/lib/jxLib/themes/crispin/images/button_multi.png
   trunk/lib/jxLib/themes/crispin/images/button_multi_disclose.png
   trunk/lib/jxLib/themes/crispin/images/dialog_chrome.png
   trunk/lib/jxLib/themes/crispin/images/dialog_resize.png
   trunk/lib/jxLib/themes/crispin/images/emblems.png
   trunk/lib/jxLib/themes/crispin/images/flyout_chrome.png
   trunk/lib/jxLib/themes/crispin/images/grid.png
   trunk/lib/jxLib/themes/crispin/images/icons.png
   trunk/lib/jxLib/themes/crispin/images/listitem.png
   trunk/lib/jxLib/themes/crispin/images/loading.gif
   trunk/lib/jxLib/themes/crispin/images/menuitem.png
   trunk/lib/jxLib/themes/crispin/images/notice.png
   trunk/lib/jxLib/themes/crispin/images/notice_error.png
   trunk/lib/jxLib/themes/crispin/images/notice_success.png
   trunk/lib/jxLib/themes/crispin/images/notice_warning.png
   trunk/lib/jxLib/themes/crispin/images/panel_controls.png
   trunk/lib/jxLib/themes/crispin/images/panelbar.png
   trunk/lib/jxLib/themes/crispin/images/progressbar.png
   trunk/lib/jxLib/themes/crispin/images/spinner_16.gif
   trunk/lib/jxLib/themes/crispin/images/spinner_24.gif
   trunk/lib/jxLib/themes/crispin/images/tab_bottom.png
   trunk/lib/jxLib/themes/crispin/images/tab_close.png
   trunk/lib/jxLib/themes/crispin/images/tab_left.png
   trunk/lib/jxLib/themes/crispin/images/tab_right.png
   trunk/lib/jxLib/themes/crispin/images/tab_top.png
   trunk/lib/jxLib/themes/crispin/images/tabbar.png
   trunk/lib/jxLib/themes/crispin/images/tabbar_bottom.png
   trunk/lib/jxLib/themes/crispin/images/tabbar_left.png
   trunk/lib/jxLib/themes/crispin/images/tabbar_right.png
   trunk/lib/jxLib/themes/crispin/images/table_col.png
   trunk/lib/jxLib/themes/crispin/images/table_row.png
   trunk/lib/jxLib/themes/crispin/images/toolbar.png
   trunk/lib/jxLib/themes/crispin/images/toolbar_separator_h.png
   trunk/lib/jxLib/themes/crispin/images/toolbar_separator_v.png
   trunk/lib/jxLib/themes/crispin/images/tree.png
   trunk/lib/jxLib/themes/crispin/images/tree_hover.png
   trunk/lib/jxLib/themes/crispin/images/tree_vert_line.png
   trunk/lib/jxLib/themes/crispin/jxtheme.css
   trunk/lib/jxLib/themes/crispin/jxtheme.uncompressed.css
   trunk/lib/jxLib/themes/delicious/
   trunk/lib/jxLib/themes/delicious/ie6.css
   trunk/lib/jxLib/themes/delicious/ie7.css
   trunk/lib/jxLib/themes/delicious/images/
   trunk/lib/jxLib/themes/delicious/images/a_pixel.png
   trunk/lib/jxLib/themes/delicious/images/button.png
   trunk/lib/jxLib/themes/delicious/images/button_combo.png
   trunk/lib/jxLib/themes/delicious/images/button_multi.png
   trunk/lib/jxLib/themes/delicious/images/button_multi_disclose.png
   trunk/lib/jxLib/themes/delicious/images/dialog_chrome.png
   trunk/lib/jxLib/themes/delicious/images/dialog_resize.png
   trunk/lib/jxLib/themes/delicious/images/emblems.png
   trunk/lib/jxLib/themes/delicious/images/flyout_chrome.png
   trunk/lib/jxLib/themes/delicious/images/grid.png
   trunk/lib/jxLib/themes/delicious/images/icons.png
   trunk/lib/jxLib/themes/delicious/images/listitem.png
   trunk/lib/jxLib/themes/delicious/images/loading.gif
   trunk/lib/jxLib/themes/delicious/images/menuitem.png
   trunk/lib/jxLib/themes/delicious/images/notice.png
   trunk/lib/jxLib/themes/delicious/images/notice_error.png
   trunk/lib/jxLib/themes/delicious/images/notice_success.png
   trunk/lib/jxLib/themes/delicious/images/notice_warning.png
   trunk/lib/jxLib/themes/delicious/images/panel_controls.png
   trunk/lib/jxLib/themes/delicious/images/panelbar.png
   trunk/lib/jxLib/themes/delicious/images/progressbar.png
   trunk/lib/jxLib/themes/delicious/images/spinner_16.gif
   trunk/lib/jxLib/themes/delicious/images/spinner_24.gif
   trunk/lib/jxLib/themes/delicious/images/tab_bottom.png
   trunk/lib/jxLib/themes/delicious/images/tab_close.png
   trunk/lib/jxLib/themes/delicious/images/tab_left.png
   trunk/lib/jxLib/themes/delicious/images/tab_right.png
   trunk/lib/jxLib/themes/delicious/images/tab_top.png
   trunk/lib/jxLib/themes/delicious/images/tabbar.png
   trunk/lib/jxLib/themes/delicious/images/tabbar_bottom.png
   trunk/lib/jxLib/themes/delicious/images/tabbar_left.png
   trunk/lib/jxLib/themes/delicious/images/tabbar_right.png
   trunk/lib/jxLib/themes/delicious/images/table_col.png
   trunk/lib/jxLib/themes/delicious/images/table_row.png
   trunk/lib/jxLib/themes/delicious/images/toolbar.png
   trunk/lib/jxLib/themes/delicious/images/toolbar_separator_h.png
   trunk/lib/jxLib/themes/delicious/images/toolbar_separator_v.png
   trunk/lib/jxLib/themes/delicious/images/tree.png
   trunk/lib/jxLib/themes/delicious/images/tree_hover.png
   trunk/lib/jxLib/themes/delicious/images/tree_vert_line.png
   trunk/lib/jxLib/themes/delicious/jxtheme.css
   trunk/lib/jxLib/themes/delicious/jxtheme.uncompressed.css
Removed:
   trunk/lib/proj4js-combined.js
   trunk/lib/proj4js-compressed.js
   trunk/templates/mapguide/standard/themes/
   trunk/templates/mapserver/standard/themes/
Modified:
   trunk/lib/fusion.js
   trunk/templates/mapguide/standard/index.html
   trunk/templates/mapserver/standard/index.html
Log:
re #441: put libraries into their own directories under lib; clients point to a common version of Jx theme files

Copied: trunk/lib/Proj4js/proj4js-combined.js (from rev 2363, trunk/lib/proj4js-combined.js)
===================================================================
--- trunk/lib/Proj4js/proj4js-combined.js	                        (rev 0)
+++ trunk/lib/Proj4js/proj4js-combined.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,5142 @@
+/*
+  proj4js.js -- Javascript reprojection library. 
+  
+  Authors:      Mike Adair madairATdmsolutions.ca
+                Richard Greenwood richATgreenwoodmap.com
+                Didier Richard didier.richardATign.fr
+                Stephen Irons
+  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html 
+                Note: This program is an almost direct port of the C library
+                Proj4.
+*/
+/* ======================================================================
+    proj4js.js
+   ====================================================================== */
+
+/*
+Author:       Mike Adair madairATdmsolutions.ca
+              Richard Greenwood rich at greenwoodmap.com
+License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html
+
+$Id: Proj.js 2956 2007-07-09 12:17:52Z steven $
+*/
+
+/**
+ * Namespace: Proj4js
+ *
+ * Proj4js is a JavaScript library to transform point coordinates from one 
+ * coordinate system to another, including datum transformations.
+ *
+ * This library is a port of both the Proj.4 and GCTCP C libraries to JavaScript. 
+ * Enabling these transformations in the browser allows geographic data stored 
+ * in different projections to be combined in browser-based web mapping 
+ * applications.
+ * 
+ * Proj4js must have access to coordinate system initialization strings (which
+ * are the same as for PROJ.4 command line).  Thes can be included in your 
+ * application using a <script> tag or Proj4js can load CS initialization 
+ * strings from a local directory or a web service such as spatialreference.org.
+ *
+ * Similarly, Proj4js must have access to projection transform code.  These can
+ * be included individually using a <script> tag in your page, built into a 
+ * custom build of Proj4js or loaded dynamically at run-time.  Using the
+ * -combined and -compressed versions of Proj4js includes all projection class
+ * code by default.
+ *
+ * Note that dynamic loading of defs and code happens ascynchrously, check the
+ * Proj.readyToUse flag before using the Proj object.  If the defs and code
+ * required by your application are loaded through script tags, dynamic loading
+ * is not required and the Proj object will be readyToUse on return from the 
+ * constructor.
+ * 
+ * All coordinates are handled as points which have a .x and a .y property
+ * which will be modified in place.
+ *
+ * Override Proj4js.reportError for output of alerts and warnings.
+ *
+ * See http://trac.osgeo.org/proj4js/wiki/UserGuide for full details.
+*/
+
+/**
+ * Global namespace object for Proj4js library
+ */
+Proj4js = {
+
+    /**
+     * Property: defaultDatum
+     * The datum to use when no others a specified
+     */
+    defaultDatum: 'WGS84',                  //default datum
+
+    /** 
+    * Method: transform(source, dest, point)
+    * Transform a point coordinate from one map projection to another.  This is
+    * really the only public method you should need to use.
+    *
+    * Parameters:
+    * source - {Proj4js.Proj} source map projection for the transformation
+    * dest - {Proj4js.Proj} destination map projection for the transformation
+    * point - {Object} point to transform, may be geodetic (long, lat) or
+    *     projected Cartesian (x,y), but should always have x,y properties.
+    */
+    transform: function(source, dest, point) {
+        if (!source.readyToUse) {
+            this.reportError("Proj4js initialization for:"+source.srsCode+" not yet complete");
+            return point;
+        }
+        if (!dest.readyToUse) {
+            this.reportError("Proj4js initialization for:"+dest.srsCode+" not yet complete");
+            return point;
+        }
+        
+        // Workaround for Spherical Mercator
+        if ((source.srsProjNumber =="900913" && dest.datumCode != "WGS84") ||
+            (dest.srsProjNumber == "900913" && source.datumCode != "WGS84")) {
+            var wgs84 = Proj4js.WGS84;
+            this.transform(source, wgs84, point);
+            source = wgs84;
+        }
+
+        // Transform source points to long/lat, if they aren't already.
+        if ( source.projName=="longlat") {
+            point.x *= Proj4js.common.D2R;  // convert degrees to radians
+            point.y *= Proj4js.common.D2R;
+        } else {
+            if (source.to_meter) {
+                point.x *= source.to_meter;
+                point.y *= source.to_meter;
+            }
+            source.inverse(point); // Convert Cartesian to longlat
+        }
+
+        // Adjust for the prime meridian if necessary
+        if (source.from_greenwich) { 
+            point.x += source.from_greenwich; 
+        }
+
+        // Convert datums if needed, and if possible.
+        point = this.datum_transform( source.datum, dest.datum, point );
+
+        // Adjust for the prime meridian if necessary
+        if (dest.from_greenwich) {
+            point.x -= dest.from_greenwich;
+        }
+
+        if( dest.projName=="longlat" ) {             
+            // convert radians to decimal degrees
+            point.x *= Proj4js.common.R2D;
+            point.y *= Proj4js.common.R2D;
+        } else  {               // else project
+            dest.forward(point);
+            if (dest.to_meter) {
+                point.x /= dest.to_meter;
+                point.y /= dest.to_meter;
+            }
+        }
+        return point;
+    }, // transform()
+
+    /** datum_transform()
+      source coordinate system definition,
+      destination coordinate system definition,
+      point to transform in geodetic coordinates (long, lat, height)
+    */
+    datum_transform : function( source, dest, point ) {
+
+      // Short cut if the datums are identical.
+      if( source.compare_datums( dest ) ) {
+          return point; // in this case, zero is sucess,
+                    // whereas cs_compare_datums returns 1 to indicate TRUE
+                    // confusing, should fix this
+      }
+
+      // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest
+      if( source.datum_type == Proj4js.common.PJD_NODATUM
+          || dest.datum_type == Proj4js.common.PJD_NODATUM) {
+          return point;
+      }
+
+      // If this datum requires grid shifts, then apply it to geodetic coordinates.
+      if( source.datum_type == Proj4js.common.PJD_GRIDSHIFT )
+      {
+        alert("ERROR: Grid shift transformations are not implemented yet.");
+        /*
+          pj_apply_gridshift( pj_param(source.params,"snadgrids").s, 0,
+                              point_count, point_offset, x, y, z );
+          CHECK_RETURN;
+
+          src_a = SRS_WGS84_SEMIMAJOR;
+          src_es = 0.006694379990;
+        */
+      }
+
+      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
+      {
+        alert("ERROR: Grid shift transformations are not implemented yet.");
+        /*
+          dst_a = ;
+          dst_es = 0.006694379990;
+        */
+      }
+
+      // Do we need to go through geocentric coordinates?
+      if( source.es != dest.es || source.a != dest.a
+          || source.datum_type == Proj4js.common.PJD_3PARAM
+          || source.datum_type == Proj4js.common.PJD_7PARAM
+          || dest.datum_type == Proj4js.common.PJD_3PARAM
+          || dest.datum_type == Proj4js.common.PJD_7PARAM)
+      {
+
+        // Convert to geocentric coordinates.
+        source.geodetic_to_geocentric( point );
+        // CHECK_RETURN;
+
+        // Convert between datums
+        if( source.datum_type == Proj4js.common.PJD_3PARAM || source.datum_type == Proj4js.common.PJD_7PARAM ) {
+          source.geocentric_to_wgs84(point);
+          // CHECK_RETURN;
+        }
+
+        if( dest.datum_type == Proj4js.common.PJD_3PARAM || dest.datum_type == Proj4js.common.PJD_7PARAM ) {
+          dest.geocentric_from_wgs84(point);
+          // CHECK_RETURN;
+        }
+
+        // Convert back to geodetic coordinates
+        dest.geocentric_to_geodetic( point );
+          // CHECK_RETURN;
+      }
+
+      // Apply grid shift to destination if required
+      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
+      {
+        alert("ERROR: Grid shift transformations are not implemented yet.");
+        // pj_apply_gridshift( pj_param(dest.params,"snadgrids").s, 1, point);
+        // CHECK_RETURN;
+      }
+      return point;
+    }, // cs_datum_transform
+
+    /**
+     * Function: reportError
+     * An internal method to report errors back to user. 
+     * Override this in applications to report error messages or throw exceptions.
+     */
+    reportError: function(msg) {
+      //console.log(msg);
+    },
+
+/**
+ *
+ * Title: Private Methods
+ * The following properties and methods are intended for internal use only.
+ *
+ * This is a minimal implementation of JavaScript inheritance methods so that 
+ * Proj4js can be used as a stand-alone library.
+ * These are copies of the equivalent OpenLayers methods at v2.7
+ */
+ 
+/**
+ * Function: extend
+ * Copy all properties of a source object to a destination object.  Modifies
+ *     the passed in destination object.  Any properties on the source object
+ *     that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+    extend: function(destination, source) {
+      destination = destination || {};
+      if(source) {
+          for(var property in source) {
+              var value = source[property];
+              if(value !== undefined) {
+                  destination[property] = value;
+              }
+          }
+      }
+      return destination;
+    },
+
+/**
+ * Constructor: Class
+ * Base class used to construct all other classes. Includes support for 
+ *     multiple inheritance. 
+ *  
+ */
+    Class: function() {
+      var Class = function() {
+          this.initialize.apply(this, arguments);
+      };
+  
+      var extended = {};
+      var parent;
+      for(var i=0; i<arguments.length; ++i) {
+          if(typeof arguments[i] == "function") {
+              // get the prototype of the superclass
+              parent = arguments[i].prototype;
+          } else {
+              // in this case we're extending with the prototype
+              parent = arguments[i];
+          }
+          Proj4js.extend(extended, parent);
+      }
+      Class.prototype = extended;
+      
+      return Class;
+    },
+
+    /**
+     * Function: bind
+     * Bind a function to an object.  Method to easily create closures with
+     *     'this' altered.
+     * 
+     * Parameters:
+     * func - {Function} Input function.
+     * object - {Object} The object to bind to the input function (as this).
+     * 
+     * Returns:
+     * {Function} A closure with 'this' set to the passed in object.
+     */
+    bind: function(func, object) {
+        // create a reference to all arguments past the second one
+        var args = Array.prototype.slice.apply(arguments, [2]);
+        return function() {
+            // Push on any additional arguments from the actual function call.
+            // These will come after those sent to the bind call.
+            var newArgs = args.concat(
+                Array.prototype.slice.apply(arguments, [0])
+            );
+            return func.apply(object, newArgs);
+        };
+    },
+    
+/**
+ * The following properties and methods handle dynamic loading of JSON objects.
+ *
+    /**
+     * Property: scriptName
+     * {String} The filename of this script without any path.
+     */
+    scriptName: "proj4js-combined.js",
+
+    /**
+     * Property: defsLookupService
+     * AJAX service to retreive projection definition parameters from
+     */
+    defsLookupService: 'http://spatialreference.org/ref',
+
+    /**
+     * Property: libPath
+     * internal: http server path to library code.
+     */
+    libPath: null,
+
+    /**
+     * Function: getScriptLocation
+     * Return the path to this script.
+     *
+     * Returns:
+     * Path to this script
+     */
+    getScriptLocation: function () {
+        if (this.libPath) return this.libPath;
+        var scriptName = this.scriptName;
+        var scriptNameLen = scriptName.length;
+
+        var scripts = document.getElementsByTagName('script');
+        for (var i = 0; i < scripts.length; i++) {
+            var src = scripts[i].getAttribute('src');
+            if (src) {
+                var index = src.lastIndexOf(scriptName);
+                // is it found, at the end of the URL?
+                if ((index > -1) && (index + scriptNameLen == src.length)) {
+                    this.libPath = src.slice(0, -scriptNameLen);
+                    break;
+                }
+            }
+        }
+        return this.libPath||"";
+    },
+
+    /**
+     * Function: loadScript
+     * Load a JS file from a URL into a <script> tag in the page.
+     * 
+     * Parameters:
+     * url - {String} The URL containing the script to load
+     * onload - {Function} A method to be executed when the script loads successfully
+     * onfail - {Function} A method to be executed when there is an error loading the script
+     * loadCheck - {Function} A boolean method that checks to see if the script 
+     *            has loaded.  Typically this just checks for the existance of
+     *            an object in the file just loaded.
+     */
+    loadScript: function(url, onload, onfail, loadCheck) {
+      var script = document.createElement('script');
+      script.defer = false;
+      script.type = "text/javascript";
+      script.id = url;
+      script.src = url;
+      script.onload = onload;
+      script.onerror = onfail;
+      script.loadCheck = loadCheck;
+      if (/MSIE/.test(navigator.userAgent)) {
+        script.onreadystatechange = this.checkReadyState;
+      }
+      document.getElementsByTagName('head')[0].appendChild(script);
+    },
+    
+    /**
+     * Function: checkReadyState
+     * IE workaround since there is no onerror handler.  Calls the user defined 
+     * loadCheck method to determine if the script is loaded.
+     * 
+     */
+    checkReadyState: function() {
+      if (this.readyState == 'loaded') {
+        if (!this.loadCheck()) {
+          this.onerror();
+        } else {
+          this.onload();
+        }
+      }
+    }
+};
+
+/**
+ * Class: Proj4js.Proj
+ *
+ * Proj objects provide transformation methods for point coordinates
+ * between geodetic latitude/longitude and a projected coordinate system. 
+ * once they have been initialized with a projection code.
+ *
+ * Initialization of Proj objects is with a projection code, usually EPSG codes,
+ * which is the key that will be used with the Proj4js.defs array.
+ * 
+ * The code passed in will be stripped of colons and converted to uppercase
+ * to locate projection definition files.
+ *
+ * A projection object has properties for units and title strings.
+ */
+Proj4js.Proj = Proj4js.Class({
+
+  /**
+   * Property: readyToUse
+   * Flag to indicate if initialization is complete for this Proj object
+   */
+  readyToUse: false,   
+  
+  /**
+   * Property: title
+   * The title to describe the projection
+   */
+  title: null,  
+  
+  /**
+   * Property: projName
+   * The projection class for this projection, e.g. lcc (lambert conformal conic,
+   * or merc for mercator).  These are exactly equivalent to their Proj4 
+   * counterparts.
+   */
+  projName: null,
+  /**
+   * Property: units
+   * The units of the projection.  Values include 'm' and 'degrees'
+   */
+  units: null,
+  /**
+   * Property: datum
+   * The datum specified for the projection
+   */
+  datum: null,
+  /**
+   * Property: x0
+   * The x coordinate origin
+   */
+  x0: 0,
+  /**
+   * Property: y0
+   * The y coordinate origin
+   */
+  y0: 0,
+  /**
+   * Property: localCS
+   * Flag to indicate if the projection is a local one in which no transforms
+   * are required.
+   */
+  localCS: false,
+
+  /**
+   * Constructor: initialize
+   * Constructor for Proj4js.Proj objects
+  *
+  * Parameters:
+  * srsCode - a code for map projection definition parameters.  These are usually
+  * (but not always) EPSG codes.
+  */
+  initialize: function(srsCode) {
+      this.srsCodeInput = srsCode;
+      
+      //check to see if this is a WKT string
+      if ((srsCode.indexOf('GEOGCS') >= 0) ||
+          (srsCode.indexOf('GEOCCS') >= 0) ||
+          (srsCode.indexOf('PROJCS') >= 0) ||
+          (srsCode.indexOf('LOCAL_CS') >= 0)) {
+            this.parseWKT(srsCode);
+            this.datum = new Proj4js.datum(this);
+            this.loadProjCode(this.projName);
+            return;
+      }
+      
+      // DGR 2008-08-03 : support urn and url
+      if (srsCode.indexOf('urn:') == 0) {
+          //urn:ORIGINATOR:def:crs:CODESPACE:VERSION:ID
+          var urn = srsCode.split(':');
+          if ((urn[1] == 'ogc' || urn[1] =='x-ogc') &&
+              (urn[2] =='def') &&
+              (urn[3] =='crs')) {
+              srsCode = urn[4]+':'+urn[urn.length-1];
+          }
+      } else if (srsCode.indexOf('http://') == 0) {
+          //url#ID
+          var url = srsCode.split('#');
+          if (url[0].match(/epsg.org/)) {
+            // http://www.epsg.org/#
+            srsCode = 'EPSG:'+url[1];
+          } else if (url[0].match(/RIG.xml/)) {
+            //http://librairies.ign.fr/geoportail/resources/RIG.xml#
+            //http://interop.ign.fr/registers/ign/RIG.xml#
+            srsCode = 'IGNF:'+url[1];
+          }
+      }
+      this.srsCode = srsCode.toUpperCase();
+      if (this.srsCode.indexOf("EPSG") == 0) {
+          this.srsCode = this.srsCode;
+          this.srsAuth = 'epsg';
+          this.srsProjNumber = this.srsCode.substring(5);
+      // DGR 2007-11-20 : authority IGNF
+      } else if (this.srsCode.indexOf("IGNF") == 0) {
+          this.srsCode = this.srsCode;
+          this.srsAuth = 'IGNF';
+          this.srsProjNumber = this.srsCode.substring(5);
+      // DGR 2008-06-19 : pseudo-authority CRS for WMS
+      } else if (this.srsCode.indexOf("CRS") == 0) {
+          this.srsCode = this.srsCode;
+          this.srsAuth = 'CRS';
+          this.srsProjNumber = this.srsCode.substring(4);
+      } else {
+          this.srsAuth = '';
+          this.srsProjNumber = this.srsCode;
+      }
+      this.loadProjDefinition();
+  },
+  
+/**
+ * Function: loadProjDefinition
+ *    Loads the coordinate system initialization string if required.
+ *    Note that dynamic loading happens asynchronously so an application must 
+ *    wait for the readyToUse property is set to true.
+ *    To prevent dynamic loading, include the defs through a script tag in
+ *    your application.
+ *
+ */
+    loadProjDefinition: function() {
+      //check in memory
+      if (Proj4js.defs[this.srsCode]) {
+        this.defsLoaded();
+        return;
+      }
+
+      //else check for def on the server
+      var url = Proj4js.getScriptLocation() + 'defs/' + this.srsAuth.toUpperCase() + this.srsProjNumber + '.js';
+      Proj4js.loadScript(url, 
+                Proj4js.bind(this.defsLoaded, this),
+                Proj4js.bind(this.loadFromService, this),
+                Proj4js.bind(this.checkDefsLoaded, this) );
+    },
+
+/**
+ * Function: loadFromService
+ *    Creates the REST URL for loading the definition from a web service and 
+ *    loads it.
+ *
+ */
+    loadFromService: function() {
+      //else load from web service
+      var url = Proj4js.defsLookupService +'/' + this.srsAuth +'/'+ this.srsProjNumber + '/proj4js/';
+      Proj4js.loadScript(url, 
+            Proj4js.bind(this.defsLoaded, this),
+            Proj4js.bind(this.defsFailed, this),
+            Proj4js.bind(this.checkDefsLoaded, this) );
+    },
+
+/**
+ * Function: defsLoaded
+ * Continues the Proj object initilization once the def file is loaded
+ *
+ */
+    defsLoaded: function() {
+      this.parseDefs();
+      this.loadProjCode(this.projName);
+    },
+    
+/**
+ * Function: checkDefsLoaded
+ *    This is the loadCheck method to see if the def object exists
+ *
+ */
+    checkDefsLoaded: function() {
+      if (Proj4js.defs[this.srsCode]) {
+        return true;
+      } else {
+        return false;
+      }
+    },
+
+ /**
+ * Function: defsFailed
+ *    Report an error in loading the defs file, but continue on using WGS84
+ *
+ */
+   defsFailed: function() {
+      Proj4js.reportError('failed to load projection definition for: '+this.srsCode);
+      Proj4js.defs[this.srsCode] = Proj4js.defs['WGS84'];  //set it to something so it can at least continue
+      this.defsLoaded();
+    },
+
+/**
+ * Function: loadProjCode
+ *    Loads projection class code dynamically if required.
+ *     Projection code may be included either through a script tag or in
+ *     a built version of proj4js
+ *
+ */
+    loadProjCode: function(projName) {
+      if (Proj4js.Proj[projName]) {
+        this.initTransforms();
+        return;
+      }
+
+      //the URL for the projection code
+      var url = Proj4js.getScriptLocation() + 'projCode/' + projName + '.js';
+      Proj4js.loadScript(url, 
+              Proj4js.bind(this.loadProjCodeSuccess, this, projName),
+              Proj4js.bind(this.loadProjCodeFailure, this, projName), 
+              Proj4js.bind(this.checkCodeLoaded, this, projName) );
+    },
+
+ /**
+ * Function: loadProjCodeSuccess
+ *    Loads any proj dependencies or continue on to final initialization.
+ *
+ */
+    loadProjCodeSuccess: function(projName) {
+      if (Proj4js.Proj[projName].dependsOn){
+        this.loadProjCode(Proj4js.Proj[projName].dependsOn);
+      } else {
+        this.initTransforms();
+      }
+    },
+
+ /**
+ * Function: defsFailed
+ *    Report an error in loading the proj file.  Initialization of the Proj
+ *    object has failed and the readyToUse flag will never be set.
+ *
+ */
+    loadProjCodeFailure: function(projName) {
+      Proj4js.reportError("failed to find projection file for: " + projName);
+      //TBD initialize with identity transforms so proj will still work?
+    },
+    
+/**
+ * Function: checkCodeLoaded
+ *    This is the loadCheck method to see if the projection code is loaded
+ *
+ */
+    checkCodeLoaded: function(projName) {
+      if (Proj4js.Proj[projName]) {
+        return true;
+      } else {
+        return false;
+      }
+    },
+
+/**
+ * Function: initTransforms
+ *    Finalize the initialization of the Proj object
+ *
+ */
+    initTransforms: function() {
+      Proj4js.extend(this, Proj4js.Proj[this.projName]);
+      this.init();
+      this.readyToUse = true;
+  },
+
+/**
+ * Function: parseWKT
+ * Parses a WKT string to get initialization parameters
+ *
+ */
+ wktRE: /^(\w+)\[(.*)\]$/,
+ parseWKT: function(wkt) {
+    var wktMatch = wkt.match(this.wktRE);
+    if (!wktMatch) return;
+    var wktObject = wktMatch[1];
+    var wktContent = wktMatch[2];
+    var wktTemp = wktContent.split(",");
+    var wktName = wktTemp.shift();
+    wktName = wktName.replace(/^\"/,"");
+    wktName = wktName.replace(/\"$/,"");
+    
+    /*
+    wktContent = wktTemp.join(",");
+    var wktArray = wktContent.split("],");
+    for (var i=0; i<wktArray.length-1; ++i) {
+      wktArray[i] += "]";
+    }
+    */
+    
+    var wktArray = new Array();
+    var bkCount = 0;
+    var obj = "";
+    for (var i=0; i<wktTemp.length; ++i) {
+      var token = wktTemp[i];
+      for (var j=0; j<token.length; ++j) {
+        if (token.charAt(j) == "[") ++bkCount;
+        if (token.charAt(j) == "]") --bkCount;
+      }
+      obj += token;
+      if (bkCount === 0) {
+        wktArray.push(obj);
+        obj = "";
+      } else {
+        obj += ",";
+      }
+    }
+    
+    //do something based on the type of the wktObject being parsed
+    //add in variations in the spelling as required
+    switch (wktObject) {
+      case 'LOCAL_CS':
+        this.projName = 'identity'
+        this.localCS = true;
+        this.srsCode = wktName;
+        break;
+      case 'GEOGCS':
+        this.projName = 'longlat'
+        this.geocsCode = wktName;
+        if (!this.srsCode) this.srsCode = wktName;
+        break;
+      case 'PROJCS':
+        this.srsCode = wktName;
+        break;
+      case 'GEOCCS':
+        break;
+      case 'PROJECTION':
+        this.projName = Proj4js.wktProjections[wktName]
+        break;
+      case 'DATUM':
+        this.datumName = wktName;
+        break;
+      case 'LOCAL_DATUM':
+        this.datumCode = 'none';
+        break;
+      case 'SPHEROID':
+        this.ellps = wktName;
+        this.a = parseFloat(wktArray.shift());
+        this.rf = parseFloat(wktArray.shift());
+        break;
+      case 'PRIMEM':
+        this.from_greenwich = parseFloat(wktArray.shift()); //to radians?
+        break;
+      case 'UNIT':
+        this.units = wktName;
+        this.unitsPerMeter = parseFloat(wktArray.shift());
+        break;
+      case 'PARAMETER':
+        var name = wktName;
+        var value = parseFloat(parseFloat(wktArray.shift()));
+        //there amy be many variations on the wktName values, add in case
+        //statements as required
+        switch (name) {
+          case 'false_easting':
+            this.x0 = value;
+            break;
+          case 'false_northing':
+            this.y0 = value;
+            break;
+          case 'scale_factor':
+            this.k0 = value;
+            break;
+          case 'central_meridian':
+            this.long0 = value;
+            break;
+          case 'latitude_of_origin':
+            this.lat0 = value;
+            break;
+          case 'more_here':
+            break;
+          default:
+            break;
+        }
+        break;
+      case 'TOWGS84':
+        this.datum_params = wktArray;
+        break;
+      case 'MORE_HERE':
+        break;
+      default:
+        break;
+    }
+    for (var i=0; i<wktArray.length; ++i) {
+      this.parseWKT(wktArray[i]);
+    }
+ },
+
+/**
+ * Function: parseDefs
+ * Parses the PROJ.4 initialization string and sets the associated properties.
+ *
+ */
+  parseDefs: function() {
+      this.defData = Proj4js.defs[this.srsCode];
+      var paramName, paramVal;
+      if (!this.defData) {
+        return;
+      }
+      var paramArray=this.defData.split("+");
+
+      for (var prop=0; prop<paramArray.length; prop++) {
+          var property = paramArray[prop].split("=");
+          paramName = property[0].toLowerCase();
+          paramVal = property[1];
+
+          switch (paramName.replace(/\s/gi,"")) {  // trim out spaces
+              case "": break;   // throw away nameless parameter
+              case "title":  this.title = paramVal; break;
+              case "proj":   this.projName =  paramVal.replace(/\s/gi,""); break;
+              case "units":  this.units = paramVal.replace(/\s/gi,""); break;
+              case "datum":  this.datumCode = paramVal.replace(/\s/gi,""); break;
+              case "nadgrids": this.nagrids = paramVal.replace(/\s/gi,""); break;
+              case "ellps":  this.ellps = paramVal.replace(/\s/gi,""); break;
+              case "a":      this.a =  parseFloat(paramVal); break;  // semi-major radius
+              case "b":      this.b =  parseFloat(paramVal); break;  // semi-minor radius
+              // DGR 2007-11-20
+              case "rf":     this.rf = parseFloat(paramVal); break; // inverse flattening rf= a/(a-b)
+              case "lat_0":  this.lat0 = paramVal*Proj4js.common.D2R; break;        // phi0, central latitude
+              case "lat_1":  this.lat1 = paramVal*Proj4js.common.D2R; break;        //standard parallel 1
+              case "lat_2":  this.lat2 = paramVal*Proj4js.common.D2R; break;        //standard parallel 2
+              case "lat_ts": this.lat_ts = paramVal*Proj4js.common.D2R; break;      // used in merc and eqc
+              case "lon_0":  this.long0 = paramVal*Proj4js.common.D2R; break;       // lam0, central longitude
+              case "alpha":  this.alpha =  parseFloat(paramVal)*Proj4js.common.D2R; break;  //for somerc projection
+              case "lonc":   this.longc = paramVal*Proj4js.common.D2R; break;       //for somerc projection
+              case "x_0":    this.x0 = parseFloat(paramVal); break;  // false easting
+              case "y_0":    this.y0 = parseFloat(paramVal); break;  // false northing
+              case "k_0":    this.k0 = parseFloat(paramVal); break;  // projection scale factor
+              case "k":      this.k0 = parseFloat(paramVal); break;  // both forms returned
+              case "r_a":    this.R_A = true; break;                 // sphere--area of ellipsoid
+              case "zone":   this.zone = parseInt(paramVal); break;  // UTM Zone
+              case "south":   this.utmSouth = true; break;  // UTM north/south
+              case "towgs84":this.datum_params = paramVal.split(","); break;
+              case "to_meter": this.to_meter = parseFloat(paramVal); break; // cartesian scaling
+              case "from_greenwich": this.from_greenwich = paramVal*Proj4js.common.D2R; break;
+              // DGR 2008-07-09 : if pm is not a well-known prime meridian take
+              // the value instead of 0.0, then convert to radians
+              case "pm":     paramVal = paramVal.replace(/\s/gi,"");
+                             this.from_greenwich = Proj4js.PrimeMeridian[paramVal] ?
+                                Proj4js.PrimeMeridian[paramVal] : parseFloat(paramVal);
+                             this.from_greenwich *= Proj4js.common.D2R; 
+                             break;
+              case "no_defs": break; 
+              default: //alert("Unrecognized parameter: " + paramName);
+          } // switch()
+      } // for paramArray
+      this.deriveConstants();
+  },
+
+/**
+ * Function: deriveConstants
+ * Sets several derived constant values and initialization of datum and ellipse
+ *     parameters.
+ *
+ */
+  deriveConstants: function() {
+      if (this.nagrids == '@null') this.datumCode = 'none';
+      if (this.datumCode && this.datumCode != 'none') {
+        var datumDef = Proj4js.Datum[this.datumCode];
+        if (datumDef) {
+          this.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
+          this.ellps = datumDef.ellipse;
+          this.datumName = datumDef.datumName ? datumDef.datumName : this.datumCode;
+        }
+      }
+      if (!this.a) {    // do we have an ellipsoid?
+          var ellipse = Proj4js.Ellipsoid[this.ellps] ? Proj4js.Ellipsoid[this.ellps] : Proj4js.Ellipsoid['WGS84'];
+          Proj4js.extend(this, ellipse);
+      }
+      if (this.rf && !this.b) this.b = (1.0 - 1.0/this.rf) * this.a;
+      if (Math.abs(this.a - this.b)<Proj4js.common.EPSLN) {
+        this.sphere = true;
+        this.b= this.a;
+      }
+      this.a2 = this.a * this.a;          // used in geocentric
+      this.b2 = this.b * this.b;          // used in geocentric
+      this.es = (this.a2-this.b2)/this.a2;  // e ^ 2
+      this.e = Math.sqrt(this.es);        // eccentricity
+      if (this.R_A) {
+        this.a *= 1. - this.es * (Proj4js.common.SIXTH + this.es * (Proj4js.common.RA4 + this.es * Proj4js.common.RA6));
+        this.a2 = this.a * this.a;
+        this.b2 = this.b * this.b;
+        this.es = 0.;
+      }
+      this.ep2=(this.a2-this.b2)/this.b2; // used in geocentric
+      if (!this.k0) this.k0 = 1.0;    //default value
+
+      this.datum = new Proj4js.datum(this);
+  }
+});
+
+Proj4js.Proj.longlat = {
+  init: function() {
+    //no-op for longlat
+  },
+  forward: function(pt) {
+    //identity transform
+    return pt;
+  },
+  inverse: function(pt) {
+    //identity transform
+    return pt;
+  }
+};
+Proj4js.Proj.identity = Proj4js.Proj.longlat;
+
+/**
+  Proj4js.defs is a collection of coordinate system definition objects in the 
+  PROJ.4 command line format.
+  Generally a def is added by means of a separate .js file for example:
+
+    <SCRIPT type="text/javascript" src="defs/EPSG26912.js"></SCRIPT>
+
+  def is a CS definition in PROJ.4 WKT format, for example:
+    +proj="tmerc"   //longlat, etc.
+    +a=majorRadius
+    +b=minorRadius
+    +lat0=somenumber
+    +long=somenumber
+*/
+Proj4js.defs = {
+  // These are so widely used, we'll go ahead and throw them in
+  // without requiring a separate .js file
+  'WGS84': "+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",
+  'EPSG:4326': "+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",
+  'EPSG:4269': "+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",
+  'EPSG:3785': "+title= Google Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"
+};
+Proj4js.defs['GOOGLE'] = Proj4js.defs['EPSG:3785'];
+Proj4js.defs['EPSG:900913'] = Proj4js.defs['EPSG:3785'];
+Proj4js.defs['EPSG:102113'] = Proj4js.defs['EPSG:3785'];
+
+Proj4js.common = {
+  PI : 3.141592653589793238, //Math.PI,
+  HALF_PI : 1.570796326794896619, //Math.PI*0.5,
+  TWO_PI : 6.283185307179586477, //Math.PI*2,
+  FORTPI : 0.78539816339744833,
+  R2D : 57.29577951308232088,
+  D2R : 0.01745329251994329577,
+  SEC_TO_RAD : 4.84813681109535993589914102357e-6, /* SEC_TO_RAD = Pi/180/3600 */
+  EPSLN : 1.0e-10,
+  MAX_ITER : 20,
+  // following constants from geocent.c
+  COS_67P5 : 0.38268343236508977,  /* cosine of 67.5 degrees */
+  AD_C : 1.0026000,                /* Toms region 1 constant */
+
+  /* datum_type values */
+  PJD_UNKNOWN  : 0,
+  PJD_3PARAM   : 1,
+  PJD_7PARAM   : 2,
+  PJD_GRIDSHIFT: 3,
+  PJD_WGS84    : 4,   // WGS84 or equivalent
+  PJD_NODATUM  : 5,   // WGS84 or equivalent
+  SRS_WGS84_SEMIMAJOR : 6378137.0,  // only used in grid shift transforms
+
+  // ellipoid pj_set_ell.c
+  SIXTH : .1666666666666666667, /* 1/6 */
+  RA4   : .04722222222222222222, /* 17/360 */
+  RA6   : .02215608465608465608, /* 67/3024 */
+  RV4   : .06944444444444444444, /* 5/72 */
+  RV6   : .04243827160493827160, /* 55/1296 */
+
+// Function to compute the constant small m which is the radius of
+//   a parallel of latitude, phi, divided by the semimajor axis.
+// -----------------------------------------------------------------
+  msfnz : function(eccent, sinphi, cosphi) {
+      var con = eccent * sinphi;
+      return cosphi/(Math.sqrt(1.0 - con * con));
+  },
+
+// Function to compute the constant small t for use in the forward
+//   computations in the Lambert Conformal Conic and the Polar
+//   Stereographic projections.
+// -----------------------------------------------------------------
+  tsfnz : function(eccent, phi, sinphi) {
+    var con = eccent * sinphi;
+    var com = .5 * eccent;
+    con = Math.pow(((1.0 - con) / (1.0 + con)), com);
+    return (Math.tan(.5 * (this.HALF_PI - phi))/con);
+  },
+
+// Function to compute the latitude angle, phi2, for the inverse of the
+//   Lambert Conformal Conic and Polar Stereographic projections.
+// ----------------------------------------------------------------
+  phi2z : function(eccent, ts) {
+    var eccnth = .5 * eccent;
+    var con, dphi;
+    var phi = this.HALF_PI - 2 * Math.atan(ts);
+    for (var i = 0; i <= 15; i++) {
+      con = eccent * Math.sin(phi);
+      dphi = this.HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) - phi;
+      phi += dphi;
+      if (Math.abs(dphi) <= .0000000001) return phi;
+    }
+    alert("phi2z has NoConvergence");
+    return (-9999);
+  },
+
+/* Function to compute constant small q which is the radius of a 
+   parallel of latitude, phi, divided by the semimajor axis. 
+------------------------------------------------------------*/
+  qsfnz : function(eccent,sinphi) {
+    var con;
+    if (eccent > 1.0e-7) {
+      con = eccent * sinphi;
+      return (( 1.0- eccent * eccent) * (sinphi /(1.0 - con * con) - (.5/eccent)*Math.log((1.0 - con)/(1.0 + con))));
+    } else {
+      return(2.0 * sinphi);
+    }
+  },
+
+/* Function to eliminate roundoff errors in asin
+----------------------------------------------*/
+  asinz : function(x) {
+    if (Math.abs(x)>1.0) {
+      x=(x>1.0)?1.0:-1.0;
+    }
+    return Math.asin(x);
+  },
+
+// following functions from gctpc cproj.c for transverse mercator projections
+  e0fn : function(x) {return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},
+  e1fn : function(x) {return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},
+  e2fn : function(x) {return(0.05859375*x*x*(1.0+0.75*x));},
+  e3fn : function(x) {return(x*x*x*(35.0/3072.0));},
+  mlfn : function(e0,e1,e2,e3,phi) {return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},
+
+  srat : function(esinp, exp) {
+    return(Math.pow((1.0-esinp)/(1.0+esinp), exp));
+  },
+
+// Function to return the sign of an argument
+  sign : function(x) { if (x < 0.0) return(-1); else return(1);},
+
+// Function to adjust longitude to -180 to 180; input in radians
+  adjust_lon : function(x) {
+    x = (Math.abs(x) < this.PI) ? x: (x - (this.sign(x)*this.TWO_PI) );
+    return x;
+  },
+
+// IGNF - DGR : algorithms used by IGN France
+
+// Function to adjust latitude to -90 to 90; input in radians
+  adjust_lat : function(x) {
+    x= (Math.abs(x) < this.HALF_PI) ? x: (x - (this.sign(x)*this.PI) );
+    return x;
+  },
+
+// Latitude Isometrique - close to tsfnz ...
+  latiso : function(eccent, phi, sinphi) {
+    if (Math.abs(phi) > this.HALF_PI) return +Number.NaN;
+    if (phi==this.HALF_PI) return Number.POSITIVE_INFINITY;
+    if (phi==-1.0*this.HALF_PI) return -1.0*Number.POSITIVE_INFINITY;
+
+    var con= eccent*sinphi;
+    return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;
+  },
+
+  fL : function(x,L) {
+    return 2.0*Math.atan(x*Math.exp(L)) - this.HALF_PI;
+  },
+
+// Inverse Latitude Isometrique - close to ph2z
+  invlatiso : function(eccent, ts) {
+    var phi= this.fL(1.0,ts);
+    var Iphi= 0.0;
+    var con= 0.0;
+    do {
+      Iphi= phi;
+      con= eccent*Math.sin(Iphi);
+      phi= this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)
+    } while (Math.abs(phi-Iphi)>1.0e-12);
+    return phi;
+  },
+
+// Needed for Gauss Schreiber
+// Original:  Denis Makarov (info at binarythings.com)
+// Web Site:  http://www.binarythings.com
+  sinh : function(x)
+  {
+    var r= Math.exp(x);
+    r= (r-1.0/r)/2.0;
+    return r;
+  },
+
+  cosh : function(x)
+  {
+    var r= Math.exp(x);
+    r= (r+1.0/r)/2.0;
+    return r;
+  },
+
+  tanh : function(x)
+  {
+    var r= Math.exp(x);
+    r= (r-1.0/r)/(r+1.0/r);
+    return r;
+  },
+
+  asinh : function(x)
+  {
+    var s= (x>= 0? 1.0:-1.0);
+    return s*(Math.log( Math.abs(x) + Math.sqrt(x*x+1.0) ));
+  },
+
+  acosh : function(x)
+  {
+    return 2.0*Math.log(Math.sqrt((x+1.0)/2.0) + Math.sqrt((x-1.0)/2.0));
+  },
+
+  atanh : function(x)
+  {
+    return Math.log((x-1.0)/(x+1.0))/2.0;
+  },
+
+// Grande Normale
+  gN : function(a,e,sinphi)
+  {
+    var temp= e*sinphi;
+    return a/Math.sqrt(1.0 - temp*temp);
+  }
+
+};
+
+/** datum object
+*/
+Proj4js.datum = Proj4js.Class({
+
+  initialize : function(proj) {
+    this.datum_type = Proj4js.common.PJD_WGS84;   //default setting
+    if (proj.datumCode && proj.datumCode == 'none') {
+      this.datum_type = Proj4js.common.PJD_NODATUM;
+    }
+    if (proj && proj.datum_params) {
+      for (var i=0; i<proj.datum_params.length; i++) {
+        proj.datum_params[i]=parseFloat(proj.datum_params[i]);
+      }
+      if (proj.datum_params[0] != 0 || proj.datum_params[1] != 0 || proj.datum_params[2] != 0 ) {
+        this.datum_type = Proj4js.common.PJD_3PARAM;
+      }
+      if (proj.datum_params.length > 3) {
+        if (proj.datum_params[3] != 0 || proj.datum_params[4] != 0 ||
+            proj.datum_params[5] != 0 || proj.datum_params[6] != 0 ) {
+          this.datum_type = Proj4js.common.PJD_7PARAM;
+          proj.datum_params[3] *= Proj4js.common.SEC_TO_RAD;
+          proj.datum_params[4] *= Proj4js.common.SEC_TO_RAD;
+          proj.datum_params[5] *= Proj4js.common.SEC_TO_RAD;
+          proj.datum_params[6] = (proj.datum_params[6]/1000000.0) + 1.0;
+        }
+      }
+    }
+    if (proj) {
+      this.a = proj.a;    //datum object also uses these values
+      this.b = proj.b;
+      this.es = proj.es;
+      this.ep2 = proj.ep2;
+      this.datum_params = proj.datum_params;
+    }
+  },
+
+  /****************************************************************/
+  // cs_compare_datums()
+  //   Returns 1 (TRUE) if the two datums match, otherwise 0 (FALSE).
+  compare_datums : function( dest ) {
+    if( this.datum_type != dest.datum_type ) {
+      return false; // false, datums are not equal
+    } else if( this.a != dest.a || Math.abs(this.es-dest.es) > 0.000000000050 ) {
+      // the tolerence for es is to ensure that GRS80 and WGS84
+      // are considered identical
+      return false;
+    } else if( this.datum_type == Proj4js.common.PJD_3PARAM ) {
+      return (this.datum_params[0] == dest.datum_params[0]
+              && this.datum_params[1] == dest.datum_params[1]
+              && this.datum_params[2] == dest.datum_params[2]);
+    } else if( this.datum_type == Proj4js.common.PJD_7PARAM ) {
+      return (this.datum_params[0] == dest.datum_params[0]
+              && this.datum_params[1] == dest.datum_params[1]
+              && this.datum_params[2] == dest.datum_params[2]
+              && this.datum_params[3] == dest.datum_params[3]
+              && this.datum_params[4] == dest.datum_params[4]
+              && this.datum_params[5] == dest.datum_params[5]
+              && this.datum_params[6] == dest.datum_params[6]);
+    } else if( this.datum_type == Proj4js.common.PJD_GRIDSHIFT ) {
+      return strcmp( pj_param(this.params,"snadgrids").s,
+                     pj_param(dest.params,"snadgrids").s ) == 0;
+    } else {
+      return true; // datums are equal
+    }
+  }, // cs_compare_datums()
+
+  /*
+   * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
+   * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
+   * according to the current ellipsoid parameters.
+   *
+   *    Latitude  : Geodetic latitude in radians                     (input)
+   *    Longitude : Geodetic longitude in radians                    (input)
+   *    Height    : Geodetic height, in meters                       (input)
+   *    X         : Calculated Geocentric X coordinate, in meters    (output)
+   *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
+   *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
+   *
+   */
+  geodetic_to_geocentric : function(p) {
+    var Longitude = p.x;
+    var Latitude = p.y;
+    var Height = p.z ? p.z : 0;   //Z value not always supplied
+    var X;  // output
+    var Y;
+    var Z;
+
+    var Error_Code=0;  //  GEOCENT_NO_ERROR;
+    var Rn;            /*  Earth radius at location  */
+    var Sin_Lat;       /*  Math.sin(Latitude)  */
+    var Sin2_Lat;      /*  Square of Math.sin(Latitude)  */
+    var Cos_Lat;       /*  Math.cos(Latitude)  */
+
+    /*
+    ** Don't blow up if Latitude is just a little out of the value
+    ** range as it may just be a rounding issue.  Also removed longitude
+    ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
+    */
+    if( Latitude < -Proj4js.common.HALF_PI && Latitude > -1.001 * Proj4js.common.HALF_PI ) {
+        Latitude = -Proj4js.common.HALF_PI;
+    } else if( Latitude > Proj4js.common.HALF_PI && Latitude < 1.001 * Proj4js.common.HALF_PI ) {
+        Latitude = Proj4js.common.HALF_PI;
+    } else if ((Latitude < -Proj4js.common.HALF_PI) || (Latitude > Proj4js.common.HALF_PI)) {
+      /* Latitude out of range */
+      Proj4js.reportError('geocent:lat out of range:'+Latitude);
+      return null;
+    }
+
+    if (Longitude > Proj4js.common.PI) Longitude -= (2*Proj4js.common.PI);
+    Sin_Lat = Math.sin(Latitude);
+    Cos_Lat = Math.cos(Latitude);
+    Sin2_Lat = Sin_Lat * Sin_Lat;
+    Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
+    X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
+    Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
+    Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
+
+    p.x = X;
+    p.y = Y;
+    p.z = Z;
+    return Error_Code;
+  }, // cs_geodetic_to_geocentric()
+
+
+  geocentric_to_geodetic : function (p) {
+/* local defintions and variables */
+/* end-criterium of loop, accuracy of sin(Latitude) */
+var genau = 1.E-12;
+var genau2 = (genau*genau);
+var maxiter = 30;
+
+    var P;        /* distance between semi-minor axis and location */
+    var RR;       /* distance between center and location */
+    var CT;       /* sin of geocentric latitude */
+    var ST;       /* cos of geocentric latitude */
+    var RX;
+    var RK;
+    var RN;       /* Earth radius at location */
+    var CPHI0;    /* cos of start or old geodetic latitude in iterations */
+    var SPHI0;    /* sin of start or old geodetic latitude in iterations */
+    var CPHI;     /* cos of searched geodetic latitude */
+    var SPHI;     /* sin of searched geodetic latitude */
+    var SDPHI;    /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
+    var At_Pole;     /* indicates location is in polar region */
+    var iter;        /* # of continous iteration, max. 30 is always enough (s.a.) */
+
+    var X = p.x;
+    var Y = p.y;
+    var Z = p.z ? p.z : 0.0;   //Z value not always supplied
+    var Longitude;
+    var Latitude;
+    var Height;
+
+    At_Pole = false;
+    P = Math.sqrt(X*X+Y*Y);
+    RR = Math.sqrt(X*X+Y*Y+Z*Z);
+
+/*      special cases for latitude and longitude */
+    if (P/this.a < genau) {
+
+/*  special case, if P=0. (X=0., Y=0.) */
+        At_Pole = true;
+        Longitude = 0.0;
+
+/*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
+ *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
+        if (RR/this.a < genau) {
+            Latitude = Proj4js.common.HALF_PI;
+            Height   = -this.b;
+            return;
+        }
+    } else {
+/*  ellipsoidal (geodetic) longitude
+ *  interval: -PI < Longitude <= +PI */
+        Longitude=Math.atan2(Y,X);
+    }
+
+/* --------------------------------------------------------------
+ * Following iterative algorithm was developped by
+ * "Institut für Erdmessung", University of Hannover, July 1988.
+ * Internet: www.ife.uni-hannover.de
+ * Iterative computation of CPHI,SPHI and Height.
+ * Iteration of CPHI and SPHI to 10**-12 radian resp.
+ * 2*10**-7 arcsec.
+ * --------------------------------------------------------------
+ */
+    CT = Z/RR;
+    ST = P/RR;
+    RX = 1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);
+    CPHI0 = ST*(1.0-this.es)*RX;
+    SPHI0 = CT*RX;
+    iter = 0;
+
+/* loop to find sin(Latitude) resp. Latitude
+ * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
+    do
+    {
+        iter++;
+        RN = this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);
+
+/*  ellipsoidal (geodetic) height */
+        Height = P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);
+
+        RK = this.es*RN/(RN+Height);
+        RX = 1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);
+        CPHI = ST*(1.0-RK)*RX;
+        SPHI = CT*RX;
+        SDPHI = SPHI*CPHI0-CPHI*SPHI0;
+        CPHI0 = CPHI;
+        SPHI0 = SPHI;
+    }
+    while (SDPHI*SDPHI > genau2 && iter < maxiter);
+
+/*      ellipsoidal (geodetic) latitude */
+    Latitude=Math.atan(SPHI/Math.abs(CPHI));
+
+    p.x = Longitude;
+    p.y = Latitude;
+    p.z = Height;
+    return p;
+  }, // cs_geocentric_to_geodetic()
+
+  /** Convert_Geocentric_To_Geodetic
+   * The method used here is derived from 'An Improved Algorithm for
+   * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
+   */
+  geocentric_to_geodetic_noniter : function (p) {
+    var X = p.x;
+    var Y = p.y;
+    var Z = p.z ? p.z : 0;   //Z value not always supplied
+    var Longitude;
+    var Latitude;
+    var Height;
+
+    var W;        /* distance from Z axis */
+    var W2;       /* square of distance from Z axis */
+    var T0;       /* initial estimate of vertical component */
+    var T1;       /* corrected estimate of vertical component */
+    var S0;       /* initial estimate of horizontal component */
+    var S1;       /* corrected estimate of horizontal component */
+    var Sin_B0;   /* Math.sin(B0), B0 is estimate of Bowring aux variable */
+    var Sin3_B0;  /* cube of Math.sin(B0) */
+    var Cos_B0;   /* Math.cos(B0) */
+    var Sin_p1;   /* Math.sin(phi1), phi1 is estimated latitude */
+    var Cos_p1;   /* Math.cos(phi1) */
+    var Rn;       /* Earth radius at location */
+    var Sum;      /* numerator of Math.cos(phi1) */
+    var At_Pole;  /* indicates location is in polar region */
+
+    X = parseFloat(X);  // cast from string to float
+    Y = parseFloat(Y);
+    Z = parseFloat(Z);
+
+    At_Pole = false;
+    if (X != 0.0)
+    {
+        Longitude = Math.atan2(Y,X);
+    }
+    else
+    {
+        if (Y > 0)
+        {
+            Longitude = Proj4js.common.HALF_PI;
+        }
+        else if (Y < 0)
+        {
+            Longitude = -Proj4js.common.HALF_PI;
+        }
+        else
+        {
+            At_Pole = true;
+            Longitude = 0.0;
+            if (Z > 0.0)
+            {  /* north pole */
+                Latitude = Proj4js.common.HALF_PI;
+            }
+            else if (Z < 0.0)
+            {  /* south pole */
+                Latitude = -Proj4js.common.HALF_PI;
+            }
+            else
+            {  /* center of earth */
+                Latitude = Proj4js.common.HALF_PI;
+                Height = -this.b;
+                return;
+            }
+        }
+    }
+    W2 = X*X + Y*Y;
+    W = Math.sqrt(W2);
+    T0 = Z * Proj4js.common.AD_C;
+    S0 = Math.sqrt(T0 * T0 + W2);
+    Sin_B0 = T0 / S0;
+    Cos_B0 = W / S0;
+    Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
+    T1 = Z + this.b * this.ep2 * Sin3_B0;
+    Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
+    S1 = Math.sqrt(T1*T1 + Sum * Sum);
+    Sin_p1 = T1 / S1;
+    Cos_p1 = Sum / S1;
+    Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
+    if (Cos_p1 >= Proj4js.common.COS_67P5)
+    {
+        Height = W / Cos_p1 - Rn;
+    }
+    else if (Cos_p1 <= -Proj4js.common.COS_67P5)
+    {
+        Height = W / -Cos_p1 - Rn;
+    }
+    else
+    {
+        Height = Z / Sin_p1 + Rn * (this.es - 1.0);
+    }
+    if (At_Pole == false)
+    {
+        Latitude = Math.atan(Sin_p1 / Cos_p1);
+    }
+
+    p.x = Longitude;
+    p.y = Latitude;
+    p.z = Height;
+    return p;
+  }, // geocentric_to_geodetic_noniter()
+
+  /****************************************************************/
+  // pj_geocentic_to_wgs84( p )
+  //  p = point to transform in geocentric coordinates (x,y,z)
+  geocentric_to_wgs84 : function ( p ) {
+
+    if( this.datum_type == Proj4js.common.PJD_3PARAM )
+    {
+      // if( x[io] == HUGE_VAL )
+      //    continue;
+      p.x += this.datum_params[0];
+      p.y += this.datum_params[1];
+      p.z += this.datum_params[2];
+
+    }
+    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
+    {
+      var Dx_BF =this.datum_params[0];
+      var Dy_BF =this.datum_params[1];
+      var Dz_BF =this.datum_params[2];
+      var Rx_BF =this.datum_params[3];
+      var Ry_BF =this.datum_params[4];
+      var Rz_BF =this.datum_params[5];
+      var M_BF  =this.datum_params[6];
+      // if( x[io] == HUGE_VAL )
+      //    continue;
+      var x_out = M_BF*(       p.x - Rz_BF*p.y + Ry_BF*p.z) + Dx_BF;
+      var y_out = M_BF*( Rz_BF*p.x +       p.y - Rx_BF*p.z) + Dy_BF;
+      var z_out = M_BF*(-Ry_BF*p.x + Rx_BF*p.y +       p.z) + Dz_BF;
+      p.x = x_out;
+      p.y = y_out;
+      p.z = z_out;
+    }
+  }, // cs_geocentric_to_wgs84
+
+  /****************************************************************/
+  // pj_geocentic_from_wgs84()
+  //  coordinate system definition,
+  //  point to transform in geocentric coordinates (x,y,z)
+  geocentric_from_wgs84 : function( p ) {
+
+    if( this.datum_type == Proj4js.common.PJD_3PARAM )
+    {
+      //if( x[io] == HUGE_VAL )
+      //    continue;
+      p.x -= this.datum_params[0];
+      p.y -= this.datum_params[1];
+      p.z -= this.datum_params[2];
+
+    }
+    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
+    {
+      var Dx_BF =this.datum_params[0];
+      var Dy_BF =this.datum_params[1];
+      var Dz_BF =this.datum_params[2];
+      var Rx_BF =this.datum_params[3];
+      var Ry_BF =this.datum_params[4];
+      var Rz_BF =this.datum_params[5];
+      var M_BF  =this.datum_params[6];
+      var x_tmp = (p.x - Dx_BF) / M_BF;
+      var y_tmp = (p.y - Dy_BF) / M_BF;
+      var z_tmp = (p.z - Dz_BF) / M_BF;
+      //if( x[io] == HUGE_VAL )
+      //    continue;
+
+      p.x =        x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp;
+      p.y = -Rz_BF*x_tmp +       y_tmp + Rx_BF*z_tmp;
+      p.z =  Ry_BF*x_tmp - Rx_BF*y_tmp +       z_tmp;
+    } //cs_geocentric_from_wgs84()
+  }
+});
+
+/** point object, nothing fancy, just allows values to be
+    passed back and forth by reference rather than by value.
+    Other point classes may be used as long as they have
+    x and y properties, which will get modified in the transform method.
+*/
+Proj4js.Point = Proj4js.Class({
+
+    /**
+     * Constructor: Proj4js.Point
+     *
+     * Parameters:
+     * - x {float} or {Array} either the first coordinates component or
+     *     the full coordinates
+     * - y {float} the second component
+     * - z {float} the third component, optional.
+     */
+    initialize : function(x,y,z) {
+      if (typeof x == 'object') {
+        this.x = x[0];
+        this.y = x[1];
+        this.z = x[2] || 0.0;
+      } else if (typeof x == 'string') {
+        var coords = x.split(',');
+        this.x = parseFloat(coords[0]);
+        this.y = parseFloat(coords[1]);
+        this.z = parseFloat(coords[2]) || 0.0;
+      } else {
+        this.x = x;
+        this.y = y;
+        this.z = z || 0.0;
+      }
+    },
+
+    /**
+     * APIMethod: clone
+     * Build a copy of a Proj4js.Point object.
+     *
+     * Return:
+     * {Proj4js}.Point the cloned point.
+     */
+    clone : function() {
+      return new Proj4js.Point(this.x, this.y, this.z);
+    },
+
+    /**
+     * APIMethod: toString
+     * Return a readable string version of the point
+     *
+     * Return:
+     * {String} String representation of Proj4js.Point object. 
+     *           (ex. <i>"x=5,y=42"</i>)
+     */
+    toString : function() {
+        return ("x=" + this.x + ",y=" + this.y);
+    },
+
+    /** 
+     * APIMethod: toShortString
+     * Return a short string version of the point.
+     *
+     * Return:
+     * {String} Shortened String representation of Proj4js.Point object. 
+     *         (ex. <i>"5, 42"</i>)
+     */
+    toShortString : function() {
+        return (this.x + ", " + this.y);
+    }
+});
+
+Proj4js.PrimeMeridian = {
+    "greenwich": 0.0,               //"0dE",
+    "lisbon":     -9.131906111111,   //"9d07'54.862\"W",
+    "paris":       2.337229166667,   //"2d20'14.025\"E",
+    "bogota":    -74.080916666667,  //"74d04'51.3\"W",
+    "madrid":     -3.687938888889,  //"3d41'16.58\"W",
+    "rome":       12.452333333333,  //"12d27'8.4\"E",
+    "bern":        7.439583333333,  //"7d26'22.5\"E",
+    "jakarta":   106.807719444444,  //"106d48'27.79\"E",
+    "ferro":     -17.666666666667,  //"17d40'W",
+    "brussels":    4.367975,        //"4d22'4.71\"E",
+    "stockholm":  18.058277777778,  //"18d3'29.8\"E",
+    "athens":     23.7163375,       //"23d42'58.815\"E",
+    "oslo":       10.722916666667   //"10d43'22.5\"E"
+};
+
+Proj4js.Ellipsoid = {
+  "MERIT": {a:6378137.0, rf:298.257, ellipseName:"MERIT 1983"},
+  "SGS85": {a:6378136.0, rf:298.257, ellipseName:"Soviet Geodetic System 85"},
+  "GRS80": {a:6378137.0, rf:298.257222101, ellipseName:"GRS 1980(IUGG, 1980)"},
+  "IAU76": {a:6378140.0, rf:298.257, ellipseName:"IAU 1976"},
+  "airy": {a:6377563.396, b:6356256.910, ellipseName:"Airy 1830"},
+  "APL4.": {a:6378137, rf:298.25, ellipseName:"Appl. Physics. 1965"},
+  "NWL9D": {a:6378145.0, rf:298.25, ellipseName:"Naval Weapons Lab., 1965"},
+  "mod_airy": {a:6377340.189, b:6356034.446, ellipseName:"Modified Airy"},
+  "andrae": {a:6377104.43, rf:300.0, ellipseName:"Andrae 1876 (Den., Iclnd.)"},
+  "aust_SA": {a:6378160.0, rf:298.25, ellipseName:"Australian Natl & S. Amer. 1969"},
+  "GRS67": {a:6378160.0, rf:298.2471674270, ellipseName:"GRS 67(IUGG 1967)"},
+  "bessel": {a:6377397.155, rf:299.1528128, ellipseName:"Bessel 1841"},
+  "bess_nam": {a:6377483.865, rf:299.1528128, ellipseName:"Bessel 1841 (Namibia)"},
+  "clrk66": {a:6378206.4, b:6356583.8, ellipseName:"Clarke 1866"},
+  "clrk80": {a:6378249.145, rf:293.4663, ellipseName:"Clarke 1880 mod."},
+  "CPM": {a:6375738.7, rf:334.29, ellipseName:"Comm. des Poids et Mesures 1799"},
+  "delmbr": {a:6376428.0, rf:311.5, ellipseName:"Delambre 1810 (Belgium)"},
+  "engelis": {a:6378136.05, rf:298.2566, ellipseName:"Engelis 1985"},
+  "evrst30": {a:6377276.345, rf:300.8017, ellipseName:"Everest 1830"},
+  "evrst48": {a:6377304.063, rf:300.8017, ellipseName:"Everest 1948"},
+  "evrst56": {a:6377301.243, rf:300.8017, ellipseName:"Everest 1956"},
+  "evrst69": {a:6377295.664, rf:300.8017, ellipseName:"Everest 1969"},
+  "evrstSS": {a:6377298.556, rf:300.8017, ellipseName:"Everest (Sabah & Sarawak)"},
+  "fschr60": {a:6378166.0, rf:298.3, ellipseName:"Fischer (Mercury Datum) 1960"},
+  "fschr60m": {a:6378155.0, rf:298.3, ellipseName:"Fischer 1960"},
+  "fschr68": {a:6378150.0, rf:298.3, ellipseName:"Fischer 1968"},
+  "helmert": {a:6378200.0, rf:298.3, ellipseName:"Helmert 1906"},
+  "hough": {a:6378270.0, rf:297.0, ellipseName:"Hough"},
+  "intl": {a:6378388.0, rf:297.0, ellipseName:"International 1909 (Hayford)"},
+  "kaula": {a:6378163.0, rf:298.24, ellipseName:"Kaula 1961"},
+  "lerch": {a:6378139.0, rf:298.257, ellipseName:"Lerch 1979"},
+  "mprts": {a:6397300.0, rf:191.0, ellipseName:"Maupertius 1738"},
+  "new_intl": {a:6378157.5, b:6356772.2, ellipseName:"New International 1967"},
+  "plessis": {a:6376523.0, rf:6355863.0, ellipseName:"Plessis 1817 (France)"},
+  "krass": {a:6378245.0, rf:298.3, ellipseName:"Krassovsky, 1942"},
+  "SEasia": {a:6378155.0, b:6356773.3205, ellipseName:"Southeast Asia"},
+  "walbeck": {a:6376896.0, b:6355834.8467, ellipseName:"Walbeck"},
+  "WGS60": {a:6378165.0, rf:298.3, ellipseName:"WGS 60"},
+  "WGS66": {a:6378145.0, rf:298.25, ellipseName:"WGS 66"},
+  "WGS72": {a:6378135.0, rf:298.26, ellipseName:"WGS 72"},
+  "WGS84": {a:6378137.0, rf:298.257223563, ellipseName:"WGS 84"},
+  "sphere": {a:6370997.0, b:6370997.0, ellipseName:"Normal Sphere (r=6370997)"}
+};
+
+Proj4js.Datum = {
+  "WGS84": {towgs84: "0,0,0", ellipse: "WGS84", datumName: "WGS84"},
+  "GGRS87": {towgs84: "-199.87,74.79,246.62", ellipse: "GRS80", datumName: "Greek_Geodetic_Reference_System_1987"},
+  "NAD83": {towgs84: "0,0,0", ellipse: "GRS80", datumName: "North_American_Datum_1983"},
+  "NAD27": {nadgrids: "@conus, at alaska, at ntv2_0.gsb, at ntv1_can.dat", ellipse: "clrk66", datumName: "North_American_Datum_1927"},
+  "potsdam": {towgs84: "606.0,23.0,413.0", ellipse: "bessel", datumName: "Potsdam Rauenberg 1950 DHDN"},
+  "carthage": {towgs84: "-263.0,6.0,431.0", ellipse: "clark80", datumName: "Carthage 1934 Tunisia"},
+  "hermannskogel": {towgs84: "653.0,-212.0,449.0", ellipse: "bessel", datumName: "Hermannskogel"},
+  "ire65": {towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "mod_airy", datumName: "Ireland 1965"},
+  "nzgd49": {towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", ellipse: "intl", datumName: "New Zealand Geodetic Datum 1949"},
+  "OSGB36": {towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", ellipse: "airy", datumName: "Airy 1830"}
+};
+
+Proj4js.WGS84 = new Proj4js.Proj('WGS84');
+Proj4js.Datum['OSB36'] = Proj4js.Datum['OSGB36']; //as returned from spatialreference.org
+
+//lookup table to go from the projection name in WKT to the Proj4js projection name
+//build this out as required
+Proj4js.wktProjections = {
+  "Lambert Tangential Conformal Conic Projection": "lcc",
+  "Mercator": "merc",
+  "Transverse_Mercator": "tmerc",
+  "Transverse Mercator": "tmerc",
+  "Lambert Azimuthal Equal Area": "laea",
+  "Universal Transverse Mercator System": "utm"
+};
+
+
+/* ======================================================================
+    projCode/aea.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                     ALBERS CONICAL EQUAL AREA 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and Northing
+		for the Albers Conical Equal Area projection.  The longitude
+		and latitude must be in radians.  The Easting and Northing
+		values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan,       	Feb, 1992
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+
+Proj4js.Proj.aea = {
+  init : function() {
+
+    if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
+       Proj4js.reportError("aeaInitEqualLatitudes");
+       return;
+    }
+    this.temp = this.b / this.a;
+    this.es = 1.0 - Math.pow(this.temp,2);
+    this.e3 = Math.sqrt(this.es);
+
+    this.sin_po=Math.sin(this.lat1);
+    this.cos_po=Math.cos(this.lat1);
+    this.t1=this.sin_po;
+    this.con = this.sin_po;
+    this.ms1 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
+    this.qs1 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
+
+    this.sin_po=Math.sin(this.lat2);
+    this.cos_po=Math.cos(this.lat2);
+    this.t2=this.sin_po;
+    this.ms2 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
+    this.qs2 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
+
+    this.sin_po=Math.sin(this.lat0);
+    this.cos_po=Math.cos(this.lat0);
+    this.t3=this.sin_po;
+    this.qs0 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
+
+    if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
+      this.ns0 = (this.ms1 * this.ms1 - this.ms2 *this.ms2)/ (this.qs2 - this.qs1);
+    } else {
+      this.ns0 = this.con;
+    }
+    this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1;
+    this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0)/this.ns0;
+  },
+
+/* Albers Conical Equal Area forward equations--mapping lat,long to x,y
+  -------------------------------------------------------------------*/
+  forward: function(p){
+
+    var lon=p.x;
+    var lat=p.y;
+
+    this.sin_phi=Math.sin(lat);
+    this.cos_phi=Math.cos(lat);
+
+    var qs = Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);
+    var rh1 =this.a * Math.sqrt(this.c - this.ns0 * qs)/this.ns0;
+    var theta = this.ns0 * Proj4js.common.adjust_lon(lon - this.long0); 
+    var x = rh1 * Math.sin(theta) + this.x0;
+    var y = this.rh - rh1 * Math.cos(theta) + this.y0;
+
+    p.x = x; 
+    p.y = y;
+    return p;
+  },
+
+
+  inverse: function(p) {
+    var rh1,qs,con,theta,lon,lat;
+
+    p.x -= this.x0;
+    p.y = this.rh - p.y + this.y0;
+    if (this.ns0 >= 0) {
+      rh1 = Math.sqrt(p.x *p.x + p.y * p.y);
+      con = 1.0;
+    } else {
+      rh1 = -Math.sqrt(p.x * p.x + p.y *p.y);
+      con = -1.0;
+    }
+    theta = 0.0;
+    if (rh1 != 0.0) {
+      theta = Math.atan2(con * p.x, con * p.y);
+    }
+    con = rh1 * this.ns0 / this.a;
+    qs = (this.c - con * con) / this.ns0;
+    if (this.e3 >= 1e-10) {
+      con = 1 - .5 * (1.0 -this.es) * Math.log((1.0 - this.e3) / (1.0 + this.e3))/this.e3;
+      if (Math.abs(Math.abs(con) - Math.abs(qs)) > .0000000001 ) {
+          lat = this.phi1z(this.e3,qs);
+      } else {
+          if (qs >= 0) {
+             lat = .5 * PI;
+          } else {
+             lat = -.5 * PI;
+          }
+      }
+    } else {
+      lat = this.phi1z(e3,qs);
+    }
+
+    lon = Proj4js.common.adjust_lon(theta/this.ns0 + this.long0);
+    p.x = lon;
+    p.y = lat;
+    return p;
+  },
+  
+/* Function to compute phi1, the latitude for the inverse of the
+   Albers Conical Equal-Area projection.
+-------------------------------------------*/
+  phi1z: function (eccent,qs) {
+    var con, com, dphi;
+    var phi = Proj4js.common.asinz(.5 * qs);
+    if (eccent < Proj4js.common.EPSLN) return phi;
+    
+    var eccnts = eccent * eccent; 
+    for (var i = 1; i <= 25; i++) {
+        sinphi = Math.sin(phi);
+        cosphi = Math.cos(phi);
+        con = eccent * sinphi; 
+        com = 1.0 - con * con;
+        dphi = .5 * com * com / cosphi * (qs / (1.0 - eccnts) - sinphi / com + .5 / eccent * Math.log((1.0 - con) / (1.0 + con)));
+        phi = phi + dphi;
+        if (Math.abs(dphi) <= 1e-7) return phi;
+    }
+    Proj4js.reportError("aea:phi1z:Convergence error");
+    return null;
+  }
+  
+};
+
+
+
+/* ======================================================================
+    projCode/sterea.js
+   ====================================================================== */
+
+
+Proj4js.Proj.sterea = {
+  dependsOn : 'gauss',
+
+  init : function() {
+    Proj4js.Proj['gauss'].init.apply(this);
+    if (!this.rc) {
+      Proj4js.reportError("sterea:init:E_ERROR_0");
+      return;
+    }
+    this.sinc0 = Math.sin(this.phic0);
+    this.cosc0 = Math.cos(this.phic0);
+    this.R2 = 2.0 * this.rc;
+    if (!this.title) this.title = "Oblique Stereographic Alternative";
+  },
+
+  forward : function(p) {
+    p.x = Proj4js.common.adjust_lon(p.x-this.long0); /* adjust del longitude */
+    Proj4js.Proj['gauss'].forward.apply(this, [p]);
+    sinc = Math.sin(p.y);
+    cosc = Math.cos(p.y);
+    cosl = Math.cos(p.x);
+    k = this.k0 * this.R2 / (1.0 + this.sinc0 * sinc + this.cosc0 * cosc * cosl);
+    p.x = k * cosc * Math.sin(p.x);
+    p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl);
+    p.x = this.a * p.x + this.x0;
+    p.y = this.a * p.y + this.y0;
+    return p;
+  },
+
+  inverse : function(p) {
+    var lon,lat;
+    p.x = (p.x - this.x0) / this.a; /* descale and de-offset */
+    p.y = (p.y - this.y0) / this.a;
+
+    p.x /= this.k0;
+    p.y /= this.k0;
+    if ( (rho = Math.sqrt(p.x*p.x + p.y*p.y)) ) {
+      c = 2.0 * Math.atan2(rho, this.R2);
+      sinc = Math.sin(c);
+      cosc = Math.cos(c);
+      lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho);
+      lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc);
+    } else {
+      lat = this.phic0;
+      lon = 0.;
+    }
+
+    p.x = lon;
+    p.y = lat;
+    Proj4js.Proj['gauss'].inverse.apply(this,[p]);
+    p.x = Proj4js.common.adjust_lon(p.x + this.long0); /* adjust longitude to CM */
+    return p;
+  }
+};
+
+/* ======================================================================
+    projCode/poly.js
+   ====================================================================== */
+
+/* Function to compute, phi4, the latitude for the inverse of the
+   Polyconic projection.
+------------------------------------------------------------*/
+function phi4z (eccent,e0,e1,e2,e3,a,b,c,phi) {
+	var sinphi, sin2ph, tanph, ml, mlp, con1, con2, con3, dphi, i;
+
+	phi = a;
+	for (i = 1; i <= 15; i++) {
+		sinphi = Math.sin(phi);
+		tanphi = Math.tan(phi);
+		c = tanphi * Math.sqrt (1.0 - eccent * sinphi * sinphi);
+		sin2ph = Math.sin (2.0 * phi);
+		/*
+		ml = e0 * *phi - e1 * sin2ph + e2 * sin (4.0 *  *phi);
+		mlp = e0 - 2.0 * e1 * cos (2.0 *  *phi) + 4.0 * e2 *  cos (4.0 *  *phi);
+		*/
+		ml = e0 * phi - e1 * sin2ph + e2 * Math.sin (4.0 *  phi) - e3 * Math.sin (6.0 * phi);
+		mlp = e0 - 2.0 * e1 * Math.cos (2.0 *  phi) + 4.0 * e2 * Math.cos (4.0 *  phi) - 6.0 * e3 * Math.cos (6.0 *  phi);
+		con1 = 2.0 * ml + c * (ml * ml + b) - 2.0 * a *  (c * ml + 1.0);
+		con2 = eccent * sin2ph * (ml * ml + b - 2.0 * a * ml) / (2.0 *c);
+		con3 = 2.0 * (a - ml) * (c * mlp - 2.0 / sin2ph) - 2.0 * mlp;
+		dphi = con1 / (con2 + con3);
+		phi += dphi;
+		if (Math.abs(dphi) <= .0000000001 ) return(phi);   
+	}
+	Proj4js.reportError("phi4z: No convergence");
+	return null;
+}
+
+
+/* Function to compute the constant e4 from the input of the eccentricity
+   of the spheroid, x.  This constant is used in the Polar Stereographic
+   projection.
+--------------------------------------------------------------------*/
+function e4fn(x) {
+	var con, com;
+	con = 1.0 + x;
+	com = 1.0 - x;
+	return (Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));
+}
+
+
+
+
+
+/*******************************************************************************
+NAME                             POLYCONIC 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Polyconic projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+Proj4js.Proj.poly = {
+
+	/* Initialize the POLYCONIC projection
+	  ----------------------------------*/
+	init: function() {
+		var temp;			/* temporary variable		*/
+		if (this.lat0=0) this.lat0=90;//this.lat0 ca
+
+		/* Place parameters in static storage for common use
+		  -------------------------------------------------*/
+		this.temp = this.b / this.a;
+		this.es = 1.0 - Math.pow(this.temp,2);// devait etre dans tmerc.js mais n y est pas donc je commente sinon retour de valeurs nulles 
+		this.e = Math.sqrt(this.es);
+		this.e0 = Proj4js.common.e0fn(this.es);
+		this.e1 = Proj4js.common.e1fn(this.es);
+		this.e2 = Proj4js.common.e2fn(this.es);
+		this.e3 = Proj4js.common.e3fn(this.es);
+		this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this.e2, this.e3, this.lat0);//si que des zeros le calcul ne se fait pas
+		//if (!this.ml0) {this.ml0=0;}
+	},
+
+
+	/* Polyconic forward equations--mapping lat,long to x,y
+	  ---------------------------------------------------*/
+	forward: function(p) {
+		var sinphi, cosphi;	/* sin and cos value				*/
+		var al;				/* temporary values				*/
+		var c;				/* temporary values				*/
+		var con, ml;		/* cone constant, small m			*/
+		var ms;				/* small m					*/
+		var x,y;
+
+		var lon=p.x;
+		var lat=p.y;	
+
+		con = Proj4js.common.adjust_lon(lon - this.long0);
+		if (Math.abs(lat) <= .0000001) {
+			x = this.x0 + this.a * con;
+			y = this.y0 - this.a * this.ml0;
+		} else {
+			sinphi = Math.sin(lat);
+			cosphi = Math.cos(lat);	   
+
+			ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
+			ms = Proj4js.common.msfnz(this.e,sinphi,cosphi);
+			con = sinphi;
+			x = this.x0 + this.a * ms * Math.sin(con)/sinphi;
+			y = this.y0 + this.a * (ml - this.ml0 + ms * (1.0 - Math.cos(con))/sinphi);
+		}
+
+		p.x=x;
+		p.y=y;   
+		return p;
+	},
+
+
+	/* Inverse equations
+	-----------------*/
+	inverse: function(p) {
+		var sin_phi, cos_phi;	/* sin and cos value				*/
+		var al;					/* temporary values				*/
+		var b;					/* temporary values				*/
+		var c;					/* temporary values				*/
+		var con, ml;			/* cone constant, small m			*/
+		var iflg;				/* error flag					*/
+		var lon,lat;
+		p.x -= this.x0;
+		p.y -= this.y0;
+		al = this.ml0 + p.y/this.a;
+		iflg = 0;
+
+		if (Math.abs(al) <= .0000001) {
+			lon = p.x/this.a + this.long0;
+			lat = 0.0;
+		} else {
+			b = al * al + (p.x/this.a) * (p.x/this.a);
+			iflg = phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);
+			if (iflg != 1) return(iflg);
+			lon = Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x * c / this.a) / Math.sin(lat)) + this.long0);
+		}
+
+		p.x=lon;
+		p.y=lat;
+		return p;
+	}
+};
+
+
+
+/* ======================================================================
+    projCode/equi.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                             EQUIRECTANGULAR 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Equirectangular projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+Proj4js.Proj.equi = {
+
+  init: function() {
+    if(!this.x0) this.x0=0;
+    if(!this.y0) this.y0=0;
+    if(!this.lat0) this.lat0=0;
+    if(!this.long0) this.long0=0;
+    ///this.t2;
+  },
+
+
+
+/* Equirectangular forward equations--mapping lat,long to x,y
+  ---------------------------------------------------------*/
+  forward: function(p) {
+
+    var lon=p.x;				
+    var lat=p.y;			
+
+    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
+    var x = this.x0 +this. a * dlon *Math.cos(this.lat0);
+    var y = this.y0 + this.a * lat;
+
+    this.t1=x;
+    this.t2=Math.cos(this.lat0);
+    p.x=x;
+    p.y=y;
+    return p;
+  },  //equiFwd()
+
+
+
+/* Equirectangular inverse equations--mapping x,y to lat/long
+  ---------------------------------------------------------*/
+  inverse: function(p) {
+
+    p.x -= this.x0;
+    p.y -= this.y0;
+    var lat = p.y /this. a;
+
+    if ( Math.abs(lat) > Proj4js.common.HALF_PI) {
+        Proj4js.reportError("equi:Inv:DataError");
+    }
+    var lon = Proj4js.common.adjust_lon(this.long0 + p.x / (this.a * Math.cos(this.lat0)));
+    p.x=lon;
+    p.y=lat;
+  }//equiInv()
+};
+
+
+/* ======================================================================
+    projCode/merc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            MERCATOR
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Mercator projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+D. Steinwand, EROS      Nov, 1991
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+//static double r_major = a;		   /* major axis 				*/
+//static double r_minor = b;		   /* minor axis 				*/
+//static double lon_center = long0;	   /* Center longitude (projection center) */
+//static double lat_origin =  lat0;	   /* center latitude			*/
+//static double e,es;		           /* eccentricity constants		*/
+//static double m1;		               /* small value m			*/
+//static double false_northing = y0;   /* y offset in meters			*/
+//static double false_easting = x0;	   /* x offset in meters			*/
+//scale_fact = k0 
+
+Proj4js.Proj.merc = {
+  init : function() {
+	//?this.temp = this.r_minor / this.r_major;
+	//this.temp = this.b / this.a;
+	//this.es = 1.0 - Math.sqrt(this.temp);
+	//this.e = Math.sqrt( this.es );
+	//?this.m1 = Math.cos(this.lat_origin) / (Math.sqrt( 1.0 - this.es * Math.sin(this.lat_origin) * Math.sin(this.lat_origin)));
+	//this.m1 = Math.cos(0.0) / (Math.sqrt( 1.0 - this.es * Math.sin(0.0) * Math.sin(0.0)));
+    if (this.lat_ts) {
+      if (this.sphere) {
+        this.k0 = Math.cos(this.lat_ts);
+      } else {
+        this.k0 = Proj4js.common.msfnz(this.es, Math.sin(this.lat_ts), Math.cos(this.lat_ts));
+      }
+    }
+  },
+
+/* Mercator forward equations--mapping lat,long to x,y
+  --------------------------------------------------*/
+
+  forward : function(p) {	
+    //alert("ll2m coords : "+coords);
+    var lon = p.x;
+    var lat = p.y;
+    // convert to radians
+    if ( lat*Proj4js.common.R2D > 90.0 && 
+          lat*Proj4js.common.R2D < -90.0 && 
+          lon*Proj4js.common.R2D > 180.0 && 
+          lon*Proj4js.common.R2D < -180.0) {
+      Proj4js.reportError("merc:forward: llInputOutOfRange: "+ lon +" : " + lat);
+      return null;
+    }
+
+    var x,y;
+    if(Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI)  <= Proj4js.common.EPSLN) {
+      Proj4js.reportError("merc:forward: ll2mAtPoles");
+      return null;
+    } else {
+      if (this.sphere) {
+        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
+        y = this.y0 + this.a * this.k0 * Math.log(Math.tan(Proj4js.common.FORTPI + 0.5*lat));
+      } else {
+        var sinphi = Math.sin(lat);
+        var ts = Proj4js.common.tsfnz(this.e,lat,sinphi);
+        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
+        y = this.y0 - this.a * this.k0 * Math.log(ts);
+      }
+      p.x = x; 
+      p.y = y;
+      return p;
+    }
+  },
+
+
+  /* Mercator inverse equations--mapping x,y to lat/long
+  --------------------------------------------------*/
+  inverse : function(p) {	
+
+    var x = p.x - this.x0;
+    var y = p.y - this.y0;
+    var lon,lat;
+
+    if (this.sphere) {
+      lat = Proj4js.common.HALF_PI - 2.0 * Math.atan(Math.exp(-y / this.a * this.k0));
+    } else {
+      var ts = Math.exp(-y / (this.a * this.k0));
+      lat = Proj4js.common.phi2z(this.e,ts);
+      if(lat == -9999) {
+        Proj4js.reportError("merc:inverse: lat = -9999");
+        return null;
+      }
+    }
+    lon = Proj4js.common.adjust_lon(this.long0+ x / (this.a * this.k0));
+
+    p.x = lon;
+    p.y = lat;
+    return p;
+  }
+};
+
+
+/* ======================================================================
+    projCode/utm.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            TRANSVERSE MERCATOR
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Transverse Mercator projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+
+/**
+  Initialize Transverse Mercator projection
+*/
+
+Proj4js.Proj.utm = {
+  dependsOn : 'tmerc',
+
+  init : function() {
+    if (!this.zone) {
+      Proj4js.reportError("utm:init: zone must be specified for UTM");
+      return;
+    }
+    this.lat0 = 0.0;
+    this.long0 = ((6 * Math.abs(this.zone)) - 183) * Proj4js.common.D2R;
+    this.x0 = 500000.0;
+    this.y0 = this.utmSouth ? 10000000.0 : 0.0;
+    this.k0 = 0.9996;
+
+    Proj4js.Proj['tmerc'].init.apply(this);
+    this.forward = Proj4js.Proj['tmerc'].forward;
+    this.inverse = Proj4js.Proj['tmerc'].inverse;
+  }
+};
+/* ======================================================================
+    projCode/eqdc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            EQUIDISTANT CONIC 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and Northing
+		for the Equidistant Conic projection.  The longitude and
+		latitude must be in radians.  The Easting and Northing values
+		will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+/* Variables common to all subroutines in this code file
+  -----------------------------------------------------*/
+
+Proj4js.Proj.eqdc = {
+
+/* Initialize the Equidistant Conic projection
+  ------------------------------------------*/
+  init: function() {
+
+    /* Place parameters in static storage for common use
+      -------------------------------------------------*/
+
+    if(!this.mode) this.mode=0;//chosen default mode
+    this.temp = this.b / this.a;
+    this.es = 1.0 - Math.pow(this.temp,2);
+    this.e = Math.sqrt(this.es);
+    this.e0 = Proj4js.common.e0fn(this.es);
+    this.e1 = Proj4js.common.e1fn(this.es);
+    this.e2 = Proj4js.common.e2fn(this.es);
+    this.e3 = Proj4js.common.e3fn(this.es);
+
+    this.sinphi=Math.sin(this.lat1);
+    this.cosphi=Math.cos(this.lat1);
+
+    this.ms1 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
+    this.ml1 = Proj4js.common.mlfn(this.e0, this.e1, this.e2,this.e3, this.lat1);
+
+    /* format B
+    ---------*/
+    if (this.mode != 0) {
+      if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
+            Proj4js.reportError("eqdc:Init:EqualLatitudes");
+            //return(81);
+       }
+       this.sinphi=Math.sin(this.lat2);
+       this.cosphi=Math.cos(this.lat2);   
+
+       this.ms2 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
+       this.ml2 = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2);
+       if (Math.abs(this.lat1 - this.lat2) >= Proj4js.common.EPSLN) {
+         this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1);
+       } else {
+          this.ns = this.sinphi;
+       }
+    } else {
+      this.ns = this.sinphi;
+    }
+    this.g = this.ml1 + this.ms1/this.ns;
+    this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this. e2, this.e3, this.lat0);
+    this.rh = this.a * (this.g - this.ml0);
+  },
+
+
+/* Equidistant Conic forward equations--mapping lat,long to x,y
+  -----------------------------------------------------------*/
+  forward: function(p) {
+    var lon=p.x;
+    var lat=p.y;
+
+    /* Forward equations
+      -----------------*/
+    var ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
+    var rh1 = this.a * (this.g - ml);
+    var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);
+
+    var x = this.x0  + rh1 * Math.sin(theta);
+    var y = this.y0 + this.rh - rh1 * Math.cos(theta);
+    p.x=x;
+    p.y=y;
+    return p;
+  },
+
+/* Inverse equations
+  -----------------*/
+  inverse: function(p) {
+    p.x -= this.x0;
+    p.y  = this.rh - p.y + this.y0;
+    var con, rh1;
+    if (this.ns >= 0) {
+       var rh1 = Math.sqrt(p.x *p.x + p.y * p.y); 
+       var con = 1.0;
+    } else {
+       rh1 = -Math.sqrt(p.x *p. x +p. y * p.y); 
+       con = -1.0;
+    }
+    var theta = 0.0;
+    if (rh1 != 0.0) theta = Math.atan2(con *p.x, con *p.y);
+    var ml = this.g - rh1 /this.a;
+    var lat = this.phi3z(ml,this.e0,this.e1,this.e2,this.e3);
+    var lon = Proj4js.common.adjust_lon(this.long0 + theta / this.ns);
+
+     p.x=lon;
+     p.y=lat;  
+     return p;
+    },
+    
+/* Function to compute latitude, phi3, for the inverse of the Equidistant
+   Conic projection.
+-----------------------------------------------------------------*/
+  phi3z: function(ml,e0,e1,e2,e3) {
+    var phi;
+    var dphi;
+
+    phi = ml;
+    for (var i = 0; i < 15; i++) {
+      dphi = (ml + e1 * Math.sin(2.0 * phi) - e2 * Math.sin(4.0 * phi) + e3 * Math.sin(6.0 * phi))/ e0 - phi;
+      phi += dphi;
+      if (Math.abs(dphi) <= .0000000001) {
+        return phi;
+      }
+    }
+    Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");
+    return null;
+  }
+
+    
+};
+/* ======================================================================
+    projCode/tmerc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            TRANSVERSE MERCATOR
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Transverse Mercator projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+
+/**
+  Initialize Transverse Mercator projection
+*/
+
+Proj4js.Proj.tmerc = {
+  init : function() {
+    this.e0 = Proj4js.common.e0fn(this.es);
+    this.e1 = Proj4js.common.e1fn(this.es);
+    this.e2 = Proj4js.common.e2fn(this.es);
+    this.e3 = Proj4js.common.e3fn(this.es);
+    this.ml0 = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
+  },
+
+  /**
+    Transverse Mercator Forward  - long/lat to x/y
+    long/lat in radians
+  */
+  forward : function(p) {
+    var lon = p.x;
+    var lat = p.y;
+
+    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0); // Delta longitude
+    var con;    // cone constant
+    var x, y;
+    var sin_phi=Math.sin(lat);
+    var cos_phi=Math.cos(lat);
+
+    if (this.sphere) {  /* spherical form */
+      var b = cos_phi * Math.sin(delta_lon);
+      if ((Math.abs(Math.abs(b) - 1.0)) < .0000000001)  {
+        Proj4js.reportError("tmerc:forward: Point projects into infinity");
+        return(93);
+      } else {
+        x = .5 * this.a * this.k0 * Math.log((1.0 + b)/(1.0 - b));
+        con = Math.acos(cos_phi * Math.cos(delta_lon)/Math.sqrt(1.0 - b*b));
+        if (lat < 0) con = - con;
+        y = this.a * this.k0 * (con - this.lat0);
+      }
+    } else {
+      var al  = cos_phi * delta_lon;
+      var als = Math.pow(al,2);
+      var c   = this.ep2 * Math.pow(cos_phi,2);
+      var tq  = Math.tan(lat);
+      var t   = Math.pow(tq,2);
+      con = 1.0 - this.es * Math.pow(sin_phi,2);
+      var n   = this.a / Math.sqrt(con);
+      var ml  = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
+
+      x = this.k0 * n * al * (1.0 + als / 6.0 * (1.0 - t + c + als / 20.0 * (5.0 - 18.0 * t + Math.pow(t,2) + 72.0 * c - 58.0 * this.ep2))) + this.x0;
+      y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24.0 * (5.0 - t + 9.0 * c + 4.0 * Math.pow(c,2) + als / 30.0 * (61.0 - 58.0 * t + Math.pow(t,2) + 600.0 * c - 330.0 * this.ep2))))) + this.y0;
+
+    }
+    p.x = x; p.y = y;
+    return p;
+  }, // tmercFwd()
+
+  /**
+    Transverse Mercator Inverse  -  x/y to long/lat
+  */
+  inverse : function(p) {
+    var con, phi;  /* temporary angles       */
+    var delta_phi; /* difference between longitudes    */
+    var i;
+    var max_iter = 6;      /* maximun number of iterations */
+    var lat, lon;
+
+    if (this.sphere) {   /* spherical form */
+      var f = Math.exp(p.x/(this.a * this.k0));
+      var g = .5 * (f - 1/f);
+      var temp = this.lat0 + p.y/(this.a * this.k0);
+      var h = Math.cos(temp);
+      con = Math.sqrt((1.0 - h * h)/(1.0 + g * g));
+      lat = Proj4js.common.asinz(con);
+      if (temp < 0)
+        lat = -lat;
+      if ((g == 0) && (h == 0)) {
+        lon = this.long0;
+      } else {
+        lon = Proj4js.common.adjust_lon(Math.atan2(g,h) + this.long0);
+      }
+    } else {    // ellipsoidal form
+      var x = p.x - this.x0;
+      var y = p.y - this.y0;
+
+      con = (this.ml0 + y / this.k0) / this.a;
+      phi = con;
+      for (i=0;true;i++) {
+        delta_phi=((con + this.e1 * Math.sin(2.0*phi) - this.e2 * Math.sin(4.0*phi) + this.e3 * Math.sin(6.0*phi)) / this.e0) - phi;
+        phi += delta_phi;
+        if (Math.abs(delta_phi) <= Proj4js.common.EPSLN) break;
+        if (i >= max_iter) {
+          Proj4js.reportError("tmerc:inverse: Latitude failed to converge");
+          return(95);
+        }
+      } // for()
+      if (Math.abs(phi) < Proj4js.common.HALF_PI) {
+        // sincos(phi, &sin_phi, &cos_phi);
+        var sin_phi=Math.sin(phi);
+        var cos_phi=Math.cos(phi);
+        var tan_phi = Math.tan(phi);
+        var c = this.ep2 * Math.pow(cos_phi,2);
+        var cs = Math.pow(c,2);
+        var t = Math.pow(tan_phi,2);
+        var ts = Math.pow(t,2);
+        con = 1.0 - this.es * Math.pow(sin_phi,2);
+        var n = this.a / Math.sqrt(con);
+        var r = n * (1.0 - this.es) / con;
+        var d = x / (n * this.k0);
+        var ds = Math.pow(d,2);
+        lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24.0 * (5.0 + 3.0 * t + 10.0 * c - 4.0 * cs - 9.0 * this.ep2 - ds / 30.0 * (61.0 + 90.0 * t + 298.0 * c + 45.0 * ts - 252.0 * this.ep2 - 3.0 * cs)));
+        lon = Proj4js.common.adjust_lon(this.long0 + (d * (1.0 - ds / 6.0 * (1.0 + 2.0 * t + c - ds / 20.0 * (5.0 - 2.0 * c + 28.0 * t - 3.0 * cs + 8.0 * this.ep2 + 24.0 * ts))) / cos_phi));
+      } else {
+        lat = Proj4js.common.HALF_PI * Proj4js.common.sign(y);
+        lon = this.long0;
+      }
+    }
+    p.x = lon;
+    p.y = lat;
+    return p;
+  } // tmercInv()
+};
+/* ======================================================================
+    defs/GOOGLE.js
+   ====================================================================== */
+
+Proj4js.defs["GOOGLE"]="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";
+Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];
+/* ======================================================================
+    projCode/gstmerc.js
+   ====================================================================== */
+
+Proj4js.Proj.gstmerc = {
+  init : function() {
+
+    // array of:  a, b, lon0, lat0, k0, x0, y0
+      var temp= this.b / this.a;
+      this.e= Math.sqrt(1.0 - temp*temp);
+      this.lc= this.long0;
+      this.rs= Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));
+      var sinz= Math.sin(this.lat0);
+      var pc= Math.asin(sinz/this.rs);
+      var sinzpc= Math.sin(pc);
+      this.cp= Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);
+      this.n2= this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);
+      this.xs= this.x0;
+      this.ys= this.y0-this.n2*pc;
+
+      if (!this.title) this.title = "Gauss Schreiber transverse mercator";
+    },
+
+
+    // forward equations--mapping lat,long to x,y
+    // -----------------------------------------------------------------
+    forward : function(p) {
+
+      var lon= p.x;
+      var lat= p.y;
+
+      var L= this.rs*(lon-this.lc);
+      var Ls= this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));
+      var lat1= Math.asin(Math.sin(L)/Proj4js.common.cosh(Ls));
+      var Ls1= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
+      p.x= this.xs+(this.n2*Ls1);
+      p.y= this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));
+      return p;
+    },
+
+  // inverse equations--mapping x,y to lat/long
+  // -----------------------------------------------------------------
+  inverse : function(p) {
+
+    var x= p.x;
+    var y= p.y;
+
+    var L= Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));
+    var lat1= Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));
+    var LC= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
+    p.x= this.lc+L/this.rs;
+    p.y= Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);
+    return p;
+  }
+
+};
+/* ======================================================================
+    projCode/ortho.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                             ORTHOGRAPHIC 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Orthographic projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+Proj4js.Proj.ortho = {
+
+  /* Initialize the Orthographic projection
+    -------------------------------------*/
+  init: function(def) {
+    //double temp;			/* temporary variable		*/
+
+    /* Place parameters in static storage for common use
+      -------------------------------------------------*/;
+    this.sin_p14=Math.sin(this.lat0);
+    this.cos_p14=Math.cos(this.lat0);	
+  },
+
+
+  /* Orthographic forward equations--mapping lat,long to x,y
+    ---------------------------------------------------*/
+  forward: function(p) {
+    var sinphi, cosphi;	/* sin and cos value				*/
+    var dlon;		/* delta longitude value			*/
+    var coslon;		/* cos of longitude				*/
+    var ksp;		/* scale factor					*/
+    var g;		
+    var lon=p.x;
+    var lat=p.y;	
+    /* Forward equations
+      -----------------*/
+    dlon = Proj4js.common.adjust_lon(lon - this.long0);
+
+    sinphi=Math.sin(lat);
+    cosphi=Math.cos(lat);	
+
+    coslon = Math.cos(dlon);
+    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
+    ksp = 1.0;
+    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
+      var x = this.a * ksp * cosphi * Math.sin(dlon);
+      var y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
+    } else {
+      Proj4js.reportError("orthoFwdPointError");
+    }
+    p.x=x;
+    p.y=y;
+    return p;
+  },
+
+
+  inverse: function(p) {
+    var rh;		/* height above ellipsoid			*/
+    var z;		/* angle					*/
+    var sinz,cosz;	/* sin of z and cos of z			*/
+    var temp;
+    var con;
+    var lon , lat;
+    /* Inverse equations
+      -----------------*/
+    p.x -= this.x0;
+    p.y -= this.y0;
+    rh = Math.sqrt(p.x * p.x + p.y * p.y);
+    if (rh > this.a + .0000001) {
+      Proj4js.reportError("orthoInvDataError");
+    }
+    z = Proj4js.common.asinz(rh / this.a);
+
+    sinz=Math.sin(z);
+    cosz=Math.cos(z);
+
+    lon = this.long0;
+    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
+      lat = this.lat0; 
+    }
+    lat = Proj4js.common.asinz(cosz * this.sin_p14 + (p.y * sinz * this.cos_p14)/rh);
+    con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
+    if (Math.abs(con) <= Proj4js.common.EPSLN) {
+       if (this.lat0 >= 0) {
+          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
+       } else {
+          lon = Proj4js.common.adjust_lon(this.long0 -Math.atan2(-p.x, p.y));
+       }
+    }
+    con = cosz - this.sin_p14 * Math.sin(lat);
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }
+};
+
+
+/* ======================================================================
+    projCode/somerc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                       SWISS OBLIQUE MERCATOR
+
+PURPOSE:	Swiss projection.
+WARNING:  X and Y are inverted (weird) in the swiss coordinate system. Not
+   here, since we want X to be horizontal and Y vertical.
+
+ALGORITHM REFERENCES
+1. "Formules et constantes pour le Calcul pour la
+ projection cylindrique conforme à axe oblique et pour la transformation entre
+ des systèmes de référence".
+ http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf
+
+*******************************************************************************/
+
+Proj4js.Proj.somerc = {
+
+  init: function() {
+    var phy0 = this.lat0;
+    this.lambda0 = this.long0;
+    var sinPhy0 = Math.sin(phy0);
+    var semiMajorAxis = this.a;
+    var invF = this.rf;
+    var flattening = 1 / invF;
+    var e2 = 2 * flattening - Math.pow(flattening, 2);
+    var e = this.e = Math.sqrt(e2);
+    this.R = this.k0 * semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2.0));
+    this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4.0));
+    this.b0 = Math.asin(sinPhy0 / this.alpha);
+    this.K = Math.log(Math.tan(Math.PI / 4.0 + this.b0 / 2.0))
+            - this.alpha
+            * Math.log(Math.tan(Math.PI / 4.0 + phy0 / 2.0))
+            + this.alpha
+            * e / 2
+            * Math.log((1 + e * sinPhy0)
+            / (1 - e * sinPhy0));
+  },
+
+
+  forward: function(p) {
+    var Sa1 = Math.log(Math.tan(Math.PI / 4.0 - p.y / 2.0));
+    var Sa2 = this.e / 2.0
+            * Math.log((1 + this.e * Math.sin(p.y))
+            / (1 - this.e * Math.sin(p.y)));
+    var S = -this.alpha * (Sa1 + Sa2) + this.K;
+
+        // spheric latitude
+    var b = 2.0 * (Math.atan(Math.exp(S)) - Math.PI / 4.0);
+
+        // spheric longitude
+    var I = this.alpha * (p.x - this.lambda0);
+
+        // psoeudo equatorial rotation
+    var rotI = Math.atan(Math.sin(I)
+            / (Math.sin(this.b0) * Math.tan(b) +
+               Math.cos(this.b0) * Math.cos(I)));
+
+    var rotB = Math.asin(Math.cos(this.b0) * Math.sin(b) -
+                         Math.sin(this.b0) * Math.cos(b) * Math.cos(I));
+
+    p.y = this.R / 2.0
+            * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB)))
+            + this.y0;
+    p.x = this.R * rotI + this.x0;
+    return p;
+  },
+
+  inverse: function(p) {
+    var Y = p.x - this.x0;
+    var X = p.y - this.y0;
+
+    var rotI = Y / this.R;
+    var rotB = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4.0);
+
+    var b = Math.asin(Math.cos(this.b0) * Math.sin(rotB)
+            + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI));
+    var I = Math.atan(Math.sin(rotI)
+            / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0)
+            * Math.tan(rotB)));
+
+    var lambda = this.lambda0 + I / this.alpha;
+
+    var S = 0.0;
+    var phy = b;
+    var prevPhy = -1000.0;
+    var iteration = 0;
+    while (Math.abs(phy - prevPhy) > 0.0000001)
+    {
+      if (++iteration > 20)
+      {
+        Proj4js.reportError("omercFwdInfinity");
+        return;
+      }
+      //S = Math.log(Math.tan(Math.PI / 4.0 + phy / 2.0));
+      S = 1.0
+              / this.alpha
+              * (Math.log(Math.tan(Math.PI / 4.0 + b / 2.0)) - this.K)
+              + this.e
+              * Math.log(Math.tan(Math.PI / 4.0
+              + Math.asin(this.e * Math.sin(phy))
+              / 2.0));
+      prevPhy = phy;
+      phy = 2.0 * Math.atan(Math.exp(S)) - Math.PI / 2.0;
+    }
+
+    p.x = lambda;
+    p.y = phy;
+    return p;
+  }
+};
+/* ======================================================================
+    projCode/stere.js
+   ====================================================================== */
+
+
+// Initialize the Stereographic projection
+
+Proj4js.Proj.stere = {
+  ssfn_: function(phit, sinphi, eccen) {
+  	sinphi *= eccen;
+  	return (Math.tan (.5 * (Proj4js.common.HALF_PI + phit)) * Math.pow((1. - sinphi) / (1. + sinphi), .5 * eccen));
+  },
+  TOL:	1.e-8,
+  NITER:	8,
+  CONV:	1.e-10,
+  S_POLE:	0,
+  N_POLE:	1,
+  OBLIQ:	2,
+  EQUIT:	3,
+
+  init : function() {
+  	this.phits = this.lat_ts ? this.lat_ts : Proj4js.common.HALF_PI;
+    var t = Math.abs(this.lat0);
+  	if ((Math.abs(t) - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
+  		this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
+  	} else {
+  		this.mode = t > Proj4js.common.EPSLN ? this.OBLIQ : this.EQUIT;
+    }
+  	this.phits = Math.abs(this.phits);
+  	if (this.es) {
+  		var X;
+
+  		switch (this.mode) {
+  		case this.N_POLE:
+  		case this.S_POLE:
+  			if (Math.abs(this.phits - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
+  				this.akm1 = 2. * this.k0 / Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));
+  			} else {
+          t = Math.sin(this.phits);
+  				this.akm1 = Math.cos(this.phits) / Proj4js.common.tsfnz(this.e, this.phits, t);
+  				t *= this.e;
+  				this.akm1 /= Math.sqrt(1. - t * t);
+  			}
+  			break;
+  		case this.EQUIT:
+  			this.akm1 = 2. * this.k0;
+  			break;
+  		case this.OBLIQ:
+  			t = Math.sin(this.lat0);
+  			X = 2. * Math.atan(this.ssfn_(this.lat0, t, this.e)) - Proj4js.common.HALF_PI;
+  			t *= this.e;
+  			this.akm1 = 2. * this.k0 * Math.cos(this.lat0) / Math.sqrt(1. - t * t);
+  			this.sinX1 = Math.sin(X);
+  			this.cosX1 = Math.cos(X);
+  			break;
+  		}
+  	} else {
+  		switch (this.mode) {
+  		case this.OBLIQ:
+  			this.sinph0 = Math.sin(this.lat0);
+  			this.cosph0 = Math.cos(this.lat0);
+  		case this.EQUIT:
+  			this.akm1 = 2. * this.k0;
+  			break;
+  		case this.S_POLE:
+  		case this.N_POLE:
+  			this.akm1 = Math.abs(this.phits - Proj4js.common.HALF_PI) >= Proj4js.common.EPSLN ?
+  			   Math.cos(this.phits) / Math.tan(Proj4js.common.FORTPI - .5 * this.phits) :
+  			   2. * this.k0 ;
+  			break;
+  		}
+  	}
+  }, 
+
+// Stereographic forward equations--mapping lat,long to x,y
+  forward: function(p) {
+    var lon = p.x;
+    lon = Proj4js.common.adjust_lon(lon - this.long0);
+    var lat = p.y;
+    var x, y;
+    
+    if (this.sphere) {
+    	var  sinphi, cosphi, coslam, sinlam;
+
+    	sinphi = Math.sin(lat);
+    	cosphi = Math.cos(lat);
+    	coslam = Math.cos(lon);
+    	sinlam = Math.sin(lon);
+    	switch (this.mode) {
+    	case this.EQUIT:
+    		y = 1. + cosphi * coslam;
+    		if (y <= Proj4js.common.EPSLN) {
+          F_ERROR;
+        }
+        y = this.akm1 / y;
+    		x = y * cosphi * sinlam;
+        y *= sinphi;
+    		break;
+    	case this.OBLIQ:
+    		y = 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
+    		if (y <= Proj4js.common.EPSLN) {
+          F_ERROR;
+        }
+        y = this.akm1 / y;
+    		x = y * cosphi * sinlam;
+    		y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
+    		break;
+    	case this.N_POLE:
+    		coslam = -coslam;
+    		lat = -lat;
+        //Note  no break here so it conitnues through S_POLE
+    	case this.S_POLE:
+    		if (Math.abs(lat - Proj4js.common.HALF_PI) < this.TOL) {
+          F_ERROR;
+        }
+        y = this.akm1 * Math.tan(Proj4js.common.FORTPI + .5 * lat);
+    		x = sinlam * y;
+    		y *= coslam;
+    		break;
+    	}
+    } else {
+    	coslam = Math.cos(lon);
+    	sinlam = Math.sin(lon);
+    	sinphi = Math.sin(lat);
+    	if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
+        X = 2. * Math.atan(this.ssfn_(lat, sinphi, this.e));
+    		sinX = Math.sin(X - Proj4js.common.HALF_PI);
+    		cosX = Math.cos(X);
+    	}
+    	switch (this.mode) {
+    	case this.OBLIQ:
+    		A = this.akm1 / (this.cosX1 * (1. + this.sinX1 * sinX + this.cosX1 * cosX * coslam));
+    		y = A * (this.cosX1 * sinX - this.sinX1 * cosX * coslam);
+    		x = A * cosX;
+    		break;
+    	case this.EQUIT:
+    		A = 2. * this.akm1 / (1. + cosX * coslam);
+    		y = A * sinX;
+    		x = A * cosX;
+    		break;
+    	case this.S_POLE:
+    		lat = -lat;
+    		coslam = - coslam;
+    		sinphi = -sinphi;
+    	case this.N_POLE:
+    		x = this.akm1 * Proj4js.common.tsfnz(this.e, lat, sinphi);
+    		y = - x * coslam;
+    		break;
+    	}
+    	x = x * sinlam;
+    }
+    p.x = x*this.a + this.x0;
+    p.y = y*this.a + this.y0;
+    return p;
+  },
+
+
+//* Stereographic inverse equations--mapping x,y to lat/long
+  inverse: function(p) {
+    var x = (p.x - this.x0)/this.a;   /* descale and de-offset */
+    var y = (p.y - this.y0)/this.a;
+    var lon, lat;
+
+    var cosphi, sinphi, tp=0.0, phi_l=0.0, rho, halfe=0.0, pi2=0.0;
+    var i;
+
+    if (this.sphere) {
+    	var  c, rh, sinc, cosc;
+
+      rh = Math.sqrt(x*x + y*y);
+      c = 2. * Math.atan(rh / this.akm1);
+    	sinc = Math.sin(c);
+    	cosc = Math.cos(c);
+    	lon = 0.;
+    	switch (this.mode) {
+    	case this.EQUIT:
+    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
+    			lat = 0.;
+    		} else {
+    			lat = Math.asin(y * sinc / rh);
+        }
+    		if (cosc != 0. || x != 0.) lon = Math.atan2(x * sinc, cosc * rh);
+    		break;
+    	case this.OBLIQ:
+    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
+    			lat = this.phi0;
+    		} else {
+    			lat = Math.asin(cosc * sinph0 + y * sinc * cosph0 / rh);
+        }
+        c = cosc - sinph0 * Math.sin(lat);
+    		if (c != 0. || x != 0.) {
+    			lon = Math.atan2(x * sinc * cosph0, c * rh);
+        }
+    		break;
+    	case this.N_POLE:
+    		y = -y;
+    	case this.S_POLE:
+    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
+    			lat = this.phi0;
+    		} else {
+    			lat = Math.asin(this.mode == this.S_POLE ? -cosc : cosc);
+        }
+    		lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
+    		break;
+    	}
+    } else {
+    	rho = Math.sqrt(x*x + y*y);
+    	switch (this.mode) {
+    	case this.OBLIQ:
+    	case this.EQUIT:
+        tp = 2. * Math.atan2(rho * this.cosX1 , this.akm1);
+    		cosphi = Math.cos(tp);
+    		sinphi = Math.sin(tp);
+        if( rho == 0.0 ) {
+    		  phi_l = Math.asin(cosphi * this.sinX1);
+        } else {
+    		  phi_l = Math.asin(cosphi * this.sinX1 + (y * sinphi * this.cosX1 / rho));
+        }
+
+    		tp = Math.tan(.5 * (Proj4js.common.HALF_PI + phi_l));
+    		x *= sinphi;
+    		y = rho * this.cosX1 * cosphi - y * this.sinX1* sinphi;
+    		pi2 = Proj4js.common.HALF_PI;
+    		halfe = .5 * this.e;
+    		break;
+    	case this.N_POLE:
+    		y = -y;
+    	case this.S_POLE:
+        tp = - rho / this.akm1;
+    		phi_l = Proj4js.common.HALF_PI - 2. * Math.atan(tp);
+    		pi2 = -Proj4js.common.HALF_PI;
+    		halfe = -.5 * this.e;
+    		break;
+    	}
+    	for (i = this.NITER; i--; phi_l = lat) { //check this
+    		sinphi = this.e * Math.sin(phi_l);
+    		lat = 2. * Math.atan(tp * Math.pow((1.+sinphi)/(1.-sinphi), halfe)) - pi2;
+    		if (Math.abs(phi_l - lat) < this.CONV) {
+    			if (this.mode == this.S_POLE) lat = -lat;
+    			lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
+          p.x = Proj4js.common.adjust_lon(lon + this.long0);
+          p.y = lat;
+    			return p;
+    		}
+    	}
+    }
+  }
+}; 
+/* ======================================================================
+    projCode/nzmg.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            NEW ZEALAND MAP GRID
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the New Zealand Map Grid projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+
+ALGORITHM REFERENCES
+
+1.  Department of Land and Survey Technical Circular 1973/32
+      http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf
+
+2.  OSG Technical Report 4.1
+      http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf
+
+
+IMPLEMENTATION NOTES
+
+The two references use different symbols for the calculated values. This
+implementation uses the variable names similar to the symbols in reference [1].
+
+The alogrithm uses different units for delta latitude and delta longitude.
+The delta latitude is assumed to be in units of seconds of arc x 10^-5.
+The delta longitude is the usual radians. Look out for these conversions.
+
+The algorithm is described using complex arithmetic. There were three
+options:
+   * find and use a Javascript library for complex arithmetic
+   * write my own complex library
+   * expand the complex arithmetic by hand to simple arithmetic
+
+This implementation has expanded the complex multiplication operations
+into parallel simple arithmetic operations for the real and imaginary parts.
+The imaginary part is way over to the right of the display; this probably
+violates every coding standard in the world, but, to me, it makes it much
+more obvious what is going on.
+
+The following complex operations are used:
+   - addition
+   - multiplication
+   - division
+   - complex number raised to integer power
+   - summation
+
+A summary of complex arithmetic operations:
+   (from http://en.wikipedia.org/wiki/Complex_arithmetic)
+   addition:       (a + bi) + (c + di) = (a + c) + (b + d)i
+   subtraction:    (a + bi) - (c + di) = (a - c) + (b - d)i
+   multiplication: (a + bi) x (c + di) = (ac - bd) + (bc + ad)i
+   division:       (a + bi) / (c + di) = [(ac + bd)/(cc + dd)] + [(bc - ad)/(cc + dd)]i
+
+The algorithm needs to calculate summations of simple and complex numbers. This is
+implemented using a for-loop, pre-loading the summed value to zero.
+
+The algorithm needs to calculate theta^2, theta^3, etc while doing a summation.
+There are three possible implementations:
+   - use Math.pow in the summation loop - except for complex numbers
+   - precalculate the values before running the loop
+   - calculate theta^n = theta^(n-1) * theta during the loop
+This implementation uses the third option for both real and complex arithmetic.
+
+For example
+   psi_n = 1;
+   sum = 0;
+   for (n = 1; n <=6; n++) {
+      psi_n1 = psi_n * psi;       // calculate psi^(n+1)
+      psi_n = psi_n1;
+      sum = sum + A[n] * psi_n;
+   }
+
+
+TEST VECTORS
+
+NZMG E, N:         2487100.638      6751049.719     metres
+NZGD49 long, lat:      172.739194       -34.444066  degrees
+
+NZMG E, N:         2486533.395      6077263.661     metres
+NZGD49 long, lat:      172.723106       -40.512409  degrees
+
+NZMG E, N:         2216746.425      5388508.765     metres
+NZGD49 long, lat:      169.172062       -46.651295  degrees
+
+Note that these test vectors convert from NZMG metres to lat/long referenced
+to NZGD49, not the more usual WGS84. The difference is about 70m N/S and about
+10m E/W.
+
+These test vectors are provided in reference [1]. Many more test
+vectors are available in
+   http://www.linz.govt.nz/docs/topography/topographicdata/placenamesdatabase/nznamesmar08.zip
+which is a catalog of names on the 260-series maps.
+
+
+EPSG CODES
+
+NZMG     EPSG:27200
+NZGD49   EPSG:4272
+
+http://spatialreference.org/ defines these as
+  Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs ";
+  Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs ";
+
+
+LICENSE
+  Copyright: Stephen Irons 2008
+  Released under terms of the LGPL as per: http://www.gnu.org/copyleft/lesser.html
+
+*******************************************************************************/
+
+
+/**
+  Initialize New Zealand Map Grip projection
+*/
+
+Proj4js.Proj.nzmg = {
+
+  /**
+   * iterations: Number of iterations to refine inverse transform.
+   *     0 -> km accuracy
+   *     1 -> m accuracy -- suitable for most mapping applications
+   *     2 -> mm accuracy
+   */
+  iterations: 1,
+
+  init : function() {
+    this.A = new Array();
+    this.A[1]  = +0.6399175073;
+    this.A[2]  = -0.1358797613;
+    this.A[3]  = +0.063294409;
+    this.A[4]  = -0.02526853;
+    this.A[5]  = +0.0117879;
+    this.A[6]  = -0.0055161;
+    this.A[7]  = +0.0026906;
+    this.A[8]  = -0.001333;
+    this.A[9]  = +0.00067;
+    this.A[10] = -0.00034;
+
+    this.B_re = new Array();        this.B_im = new Array();
+    this.B_re[1] = +0.7557853228;   this.B_im[1] =  0.0;
+    this.B_re[2] = +0.249204646;    this.B_im[2] = +0.003371507;
+    this.B_re[3] = -0.001541739;    this.B_im[3] = +0.041058560;
+    this.B_re[4] = -0.10162907;     this.B_im[4] = +0.01727609;
+    this.B_re[5] = -0.26623489;     this.B_im[5] = -0.36249218;
+    this.B_re[6] = -0.6870983;      this.B_im[6] = -1.1651967;
+
+    this.C_re = new Array();        this.C_im = new Array();
+    this.C_re[1] = +1.3231270439;   this.C_im[1] =  0.0;
+    this.C_re[2] = -0.577245789;    this.C_im[2] = -0.007809598;
+    this.C_re[3] = +0.508307513;    this.C_im[3] = -0.112208952;
+    this.C_re[4] = -0.15094762;     this.C_im[4] = +0.18200602;
+    this.C_re[5] = +1.01418179;     this.C_im[5] = +1.64497696;
+    this.C_re[6] = +1.9660549;      this.C_im[6] = +2.5127645;
+
+    this.D = new Array();
+    this.D[1] = +1.5627014243;
+    this.D[2] = +0.5185406398;
+    this.D[3] = -0.03333098;
+    this.D[4] = -0.1052906;
+    this.D[5] = -0.0368594;
+    this.D[6] = +0.007317;
+    this.D[7] = +0.01220;
+    this.D[8] = +0.00394;
+    this.D[9] = -0.0013;
+  },
+
+  /**
+    New Zealand Map Grid Forward  - long/lat to x/y
+    long/lat in radians
+  */
+  forward : function(p) {
+    var lon = p.x;
+    var lat = p.y;
+
+    var delta_lat = lat - this.lat0;
+    var delta_lon = lon - this.long0;
+
+    // 1. Calculate d_phi and d_psi    ...                          // and d_lambda
+    // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians.
+    var d_phi = delta_lat / Proj4js.common.SEC_TO_RAD * 1E-5;       var d_lambda = delta_lon;
+    var d_phi_n = 1;  // d_phi^0
+
+    var d_psi = 0;
+    for (n = 1; n <= 10; n++) {
+      d_phi_n = d_phi_n * d_phi;
+      d_psi = d_psi + this.A[n] * d_phi_n;
+    }
+
+    // 2. Calculate theta
+    var th_re = d_psi;                                              var th_im = d_lambda;
+
+    // 3. Calculate z
+    var th_n_re = 1;                                                var th_n_im = 0;  // theta^0
+    var th_n_re1;                                                   var th_n_im1;
+
+    var z_re = 0;                                                   var z_im = 0;
+    for (n = 1; n <= 6; n++) {
+      th_n_re1 = th_n_re*th_re - th_n_im*th_im;                     th_n_im1 = th_n_im*th_re + th_n_re*th_im;
+      th_n_re = th_n_re1;                                           th_n_im = th_n_im1;
+      z_re = z_re + this.B_re[n]*th_n_re - this.B_im[n]*th_n_im;    z_im = z_im + this.B_im[n]*th_n_re + this.B_re[n]*th_n_im;
+    }
+
+    // 4. Calculate easting and northing
+    x = (z_im * this.a) + this.x0;
+    y = (z_re * this.a) + this.y0;
+
+    p.x = x; p.y = y;
+
+    return p;
+  },
+
+
+  /**
+    New Zealand Map Grid Inverse  -  x/y to long/lat
+  */
+  inverse : function(p) {
+
+    var x = p.x;
+    var y = p.y;
+
+    var delta_x = x - this.x0;
+    var delta_y = y - this.y0;
+
+    // 1. Calculate z
+    var z_re = delta_y / this.a;                                              var z_im = delta_x / this.a;
+
+    // 2a. Calculate theta - first approximation gives km accuracy
+    var z_n_re = 1;                                                           var z_n_im = 0;  // z^0
+    var z_n_re1;                                                              var z_n_im1;
+
+    var th_re = 0;                                                            var th_im = 0;
+    for (n = 1; n <= 6; n++) {
+      z_n_re1 = z_n_re*z_re - z_n_im*z_im;                                    z_n_im1 = z_n_im*z_re + z_n_re*z_im;
+      z_n_re = z_n_re1;                                                       z_n_im = z_n_im1;
+      th_re = th_re + this.C_re[n]*z_n_re - this.C_im[n]*z_n_im;              th_im = th_im + this.C_im[n]*z_n_re + this.C_re[n]*z_n_im;
+    }
+
+    // 2b. Iterate to refine the accuracy of the calculation
+    //        0 iterations gives km accuracy
+    //        1 iteration gives m accuracy -- good enough for most mapping applications
+    //        2 iterations bives mm accuracy
+    for (i = 0; i < this.iterations; i++) {
+       var th_n_re = th_re;                                                      var th_n_im = th_im;
+       var th_n_re1;                                                             var th_n_im1;
+
+       var num_re = z_re;                                                        var num_im = z_im;
+       for (n = 2; n <= 6; n++) {
+         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
+         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
+         num_re = num_re + (n-1)*(this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);  num_im = num_im + (n-1)*(this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
+       }
+
+       th_n_re = 1;                                                              th_n_im = 0;
+       var den_re = this.B_re[1];                                                var den_im = this.B_im[1];
+       for (n = 2; n <= 6; n++) {
+         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
+         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
+         den_re = den_re + n * (this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);    den_im = den_im + n * (this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
+       }
+
+       // Complex division
+       var den2 = den_re*den_re + den_im*den_im;
+       th_re = (num_re*den_re + num_im*den_im) / den2;                           th_im = (num_im*den_re - num_re*den_im) / den2;
+    }
+
+    // 3. Calculate d_phi              ...                                    // and d_lambda
+    var d_psi = th_re;                                                        var d_lambda = th_im;
+    var d_psi_n = 1;  // d_psi^0
+
+    var d_phi = 0;
+    for (n = 1; n <= 9; n++) {
+       d_psi_n = d_psi_n * d_psi;
+       d_phi = d_phi + this.D[n] * d_psi_n;
+    }
+
+    // 4. Calculate latitude and longitude
+    // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians.
+    var lat = this.lat0 + (d_phi * Proj4js.common.SEC_TO_RAD * 1E5);
+    var lon = this.long0 +  d_lambda;
+
+    p.x = lon;
+    p.y = lat;
+
+    return p;
+  }
+};
+/* ======================================================================
+    projCode/mill.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                    MILLER CYLINDRICAL 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Miller Cylindrical projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE            
+----------              ----           
+T. Mittan		March, 1993
+
+This function was adapted from the Lambert Azimuthal Equal Area projection
+code (FORTRAN) in the General Cartographic Transformation Package software
+which is available from the U.S. Geological Survey National Mapping Division.
+ 
+ALGORITHM REFERENCES
+
+1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
+    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
+
+2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+3.  "Software Documentation for GCTP General Cartographic Transformation
+    Package", U.S. Geological Survey National Mapping Division, May 1982.
+*******************************************************************************/
+
+Proj4js.Proj.mill = {
+
+/* Initialize the Miller Cylindrical projection
+  -------------------------------------------*/
+  init: function() {
+    //no-op
+  },
+
+
+  /* Miller Cylindrical forward equations--mapping lat,long to x,y
+    ------------------------------------------------------------*/
+  forward: function(p) {
+    var lon=p.x;
+    var lat=p.y;
+    /* Forward equations
+      -----------------*/
+    var dlon = Proj4js.common.adjust_lon(lon -this.long0);
+    var x = this.x0 + this.a * dlon;
+    var y = this.y0 + this.a * Math.log(Math.tan((Proj4js.common.PI / 4.0) + (lat / 2.5))) * 1.25;
+
+    p.x=x;
+    p.y=y;
+    return p;
+  },//millFwd()
+
+  /* Miller Cylindrical inverse equations--mapping x,y to lat/long
+    ------------------------------------------------------------*/
+  inverse: function(p) {
+    p.x -= this.x0;
+    p.y -= this.y0;
+
+    var lon = Proj4js.common.adjust_lon(this.long0 + p.x /this.a);
+    var lat = 2.5 * (Math.atan(Math.exp(0.8*p.y/this.a)) - Proj4js.common.PI / 4.0);
+
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }//millInv()
+};
+/* ======================================================================
+    projCode/gnom.js
+   ====================================================================== */
+
+/*****************************************************************************
+NAME                             GNOMONIC
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Gnomonic Projection.
+                Implementation based on the existing sterea and ortho
+                implementations.
+
+PROGRAMMER              DATE
+----------              ----
+Richard Marsden         November 2009
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
+    Projections", University of Chicago Press 1993
+
+2.  Wolfram Mathworld "Gnomonic Projection"
+    http://mathworld.wolfram.com/GnomonicProjection.html
+    Accessed: 12th November 2009
+******************************************************************************/
+
+Proj4js.Proj.gnom = {
+
+  /* Initialize the Gnomonic projection
+    -------------------------------------*/
+  init: function(def) {
+
+    /* Place parameters in static storage for common use
+      -------------------------------------------------*/
+    this.sin_p14=Math.sin(this.lat0);
+    this.cos_p14=Math.cos(this.lat0);
+    // Approximation for projecting points to the horizon (infinity)
+    this.infinity_dist = 1000 * this.a;
+    this.rc = 1;
+  },
+
+
+  /* Gnomonic forward equations--mapping lat,long to x,y
+    ---------------------------------------------------*/
+  forward: function(p) {
+    var sinphi, cosphi;	/* sin and cos value				*/
+    var dlon;		/* delta longitude value			*/
+    var coslon;		/* cos of longitude				*/
+    var ksp;		/* scale factor					*/
+    var g;		
+    var lon=p.x;
+    var lat=p.y;	
+    /* Forward equations
+      -----------------*/
+    dlon = Proj4js.common.adjust_lon(lon - this.long0);
+
+    sinphi=Math.sin(lat);
+    cosphi=Math.cos(lat);	
+
+    coslon = Math.cos(dlon);
+    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
+    ksp = 1.0;
+    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
+      x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon) / g;
+      y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon) / g;
+    } else {
+      Proj4js.reportError("orthoFwdPointError");
+
+      // Point is in the opposing hemisphere and is unprojectable
+      // We still need to return a reasonable point, so we project 
+      // to infinity, on a bearing 
+      // equivalent to the northern hemisphere equivalent
+      // This is a reasonable approximation for short shapes and lines that 
+      // straddle the horizon.
+
+      x = this.x0 + this.infinity_dist * cosphi * Math.sin(dlon);
+      y = this.y0 + this.infinity_dist * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
+
+    }
+    p.x=x;
+    p.y=y;
+    return p;
+  },
+
+
+  inverse: function(p) {
+    var rh;		/* Rho */
+    var z;		/* angle */
+    var sinc, cosc;
+    var c;
+    var lon , lat;
+
+    /* Inverse equations
+      -----------------*/
+    p.x = (p.x - this.x0) / this.a;
+    p.y = (p.y - this.y0) / this.a;
+
+    p.x /= this.k0;
+    p.y /= this.k0;
+
+    if ( (rh = Math.sqrt(p.x * p.x + p.y * p.y)) ) {
+      c = Math.atan2(rh, this.rc);
+      sinc = Math.sin(c);
+      cosc = Math.cos(c);
+
+      lat = Proj4js.common.asinz(cosc*this.sin_p14 + (p.y*sinc*this.cos_p14) / rh);
+      lon = Math.atan2(p.x*sinc, rh*this.cos_p14*cosc - p.y*this.sin_p14*sinc);
+      lon = Proj4js.common.adjust_lon(this.long0+lon);
+    } else {
+      lat = this.phic0;
+      lon = 0.0;
+    }
+ 
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }
+};
+
+
+/* ======================================================================
+    projCode/sinu.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                  		SINUSOIDAL
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Sinusoidal projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE            
+----------              ----           
+D. Steinwand, EROS      May, 1991     
+
+This function was adapted from the Sinusoidal projection code (FORTRAN) in the 
+General Cartographic Transformation Package software which is available from 
+the U.S. Geological Survey National Mapping Division.
+ 
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  "Software Documentation for GCTP General Cartographic Transformation
+    Package", U.S. Geological Survey National Mapping Division, May 1982.
+*******************************************************************************/
+
+Proj4js.Proj.sinu = {
+
+	/* Initialize the Sinusoidal projection
+	  ------------------------------------*/
+	init: function() {
+		/* Place parameters in static storage for common use
+		  -------------------------------------------------*/
+		this.R = 6370997.0; //Radius of earth
+	},
+
+	/* Sinusoidal forward equations--mapping lat,long to x,y
+	-----------------------------------------------------*/
+	forward: function(p) {
+		var x,y,delta_lon;	
+		var lon=p.x;
+		var lat=p.y;	
+		/* Forward equations
+		-----------------*/
+		delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
+		x = this.R * delta_lon * Math.cos(lat) + this.x0;
+		y = this.R * lat + this.y0;
+
+		p.x=x;
+		p.y=y;	
+		return p;
+	},
+
+	inverse: function(p) {
+		var lat,temp,lon;	
+
+		/* Inverse equations
+		  -----------------*/
+		p.x -= this.x0;
+		p.y -= this.y0;
+		lat = p.y / this.R;
+		if (Math.abs(lat) > Proj4js.common.HALF_PI) {
+		    Proj4js.reportError("sinu:Inv:DataError");
+		}
+		temp = Math.abs(lat) - Proj4js.common.HALF_PI;
+		if (Math.abs(temp) > Proj4js.common.EPSLN) {
+			temp = this.long0+ p.x / (this.R *Math.cos(lat));
+			lon = Proj4js.common.adjust_lon(temp);
+		} else {
+			lon = this.long0;
+		}
+		  
+		p.x=lon;
+		p.y=lat;
+		return p;
+	}
+};
+
+
+/* ======================================================================
+    projCode/vandg.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                    VAN DER GRINTEN 
+
+PURPOSE:	Transforms input Easting and Northing to longitude and
+		latitude for the Van der Grinten projection.  The
+		Easting and Northing must be in meters.  The longitude
+		and latitude values will be returned in radians.
+
+PROGRAMMER              DATE            
+----------              ----           
+T. Mittan		March, 1993
+
+This function was adapted from the Van Der Grinten projection code
+(FORTRAN) in the General Cartographic Transformation Package software
+which is available from the U.S. Geological Survey National Mapping Division.
+ 
+ALGORITHM REFERENCES
+
+1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
+    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
+
+2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+3.  "Software Documentation for GCTP General Cartographic Transformation
+    Package", U.S. Geological Survey National Mapping Division, May 1982.
+*******************************************************************************/
+
+Proj4js.Proj.vandg = {
+
+/* Initialize the Van Der Grinten projection
+  ----------------------------------------*/
+	init: function() {
+		this.R = 6370997.0; //Radius of earth
+	},
+
+	forward: function(p) {
+
+		var lon=p.x;
+		var lat=p.y;	
+
+		/* Forward equations
+		-----------------*/
+		var dlon = Proj4js.common.adjust_lon(lon - this.long0);
+		var x,y;
+
+		if (Math.abs(lat) <= Proj4js.common.EPSLN) {
+			x = this.x0  + this.R * dlon;
+			y = this.y0;
+		}
+		var theta = Proj4js.common.asinz(2.0 * Math.abs(lat / Proj4js.common.PI));
+		if ((Math.abs(dlon) <= Proj4js.common.EPSLN) || (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN)) {
+			x = this.x0;
+			if (lat >= 0) {
+				y = this.y0 + Proj4js.common.PI * this.R * Math.tan(.5 * theta);
+			} else {
+				y = this.y0 + Proj4js.common.PI * this.R * - Math.tan(.5 * theta);
+			}
+			//  return(OK);
+		}
+		var al = .5 * Math.abs((Proj4js.common.PI / dlon) - (dlon / Proj4js.common.PI));
+		var asq = al * al;
+		var sinth = Math.sin(theta);
+		var costh = Math.cos(theta);
+
+		var g = costh / (sinth + costh - 1.0);
+		var gsq = g * g;
+		var m = g * (2.0 / sinth - 1.0);
+		var msq = m * m;
+		var con = Proj4js.common.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq);
+		if (dlon < 0) {
+		 con = -con;
+		}
+		x = this.x0 + con;
+		con = Math.abs(con / (Proj4js.common.PI * this.R));
+		if (lat >= 0) {
+		 y = this.y0 + Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
+		} else {
+		 y = this.y0 - Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
+		}
+		p.x = x;
+		p.y = y;
+		return p;
+	},
+
+/* Van Der Grinten inverse equations--mapping x,y to lat/long
+  ---------------------------------------------------------*/
+	inverse: function(p) {
+		var dlon;
+		var xx,yy,xys,c1,c2,c3;
+		var al,asq;
+		var a1;
+		var m1;
+		var con;
+		var th1;
+		var d;
+
+		/* inverse equations
+		-----------------*/
+		p.x -= this.x0;
+		p.y -= this.y0;
+		con = Proj4js.common.PI * this.R;
+		xx = p.x / con;
+		yy =p.y / con;
+		xys = xx * xx + yy * yy;
+		c1 = -Math.abs(yy) * (1.0 + xys);
+		c2 = c1 - 2.0 * yy * yy + xx * xx;
+		c3 = -2.0 * c1 + 1.0 + 2.0 * yy * yy + xys * xys;
+		d = yy * yy / c3 + (2.0 * c2 * c2 * c2 / c3 / c3 / c3 - 9.0 * c1 * c2 / c3 /c3) / 27.0;
+		a1 = (c1 - c2 * c2 / 3.0 / c3) / c3;
+		m1 = 2.0 * Math.sqrt( -a1 / 3.0);
+		con = ((3.0 * d) / a1) / m1;
+		if (Math.abs(con) > 1.0) {
+			if (con >= 0.0) {
+				con = 1.0;
+			} else {
+				con = -1.0;
+			}
+		}
+		th1 = Math.acos(con) / 3.0;
+		if (p.y >= 0) {
+			lat = (-m1 *Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
+		} else {
+			lat = -(-m1 * Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
+		}
+
+		if (Math.abs(xx) < Proj4js.common.EPSLN) {
+			lon = this.long0;
+		}
+		lon = Proj4js.common.adjust_lon(this.long0 + Proj4js.common.PI * (xys - 1.0 + Math.sqrt(1.0 + 2.0 * (xx * xx - yy * yy) + xys * xys)) / 2.0 / xx);
+
+		p.x=lon;
+		p.y=lat;
+		return p;
+	}
+};
+/* ======================================================================
+    projCode/cea.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                    LAMBERT CYLINDRICAL EQUAL AREA
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Lambert Cylindrical Equal Area projection.
+                This class of projection includes the Behrmann and 
+                Gall-Peters Projections.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE            
+----------              ----
+R. Marsden              August 2009
+Winwaed Software Tech LLC, http://www.winwaed.com
+
+This function was adapted from the Miller Cylindrical Projection in the Proj4JS
+library.
+
+Note: This implementation assumes a Spherical Earth. The (commented) code 
+has been included for the ellipsoidal forward transform, but derivation of 
+the ellispoidal inverse transform is beyond me. Note that most of the 
+Proj4JS implementations do NOT currently support ellipsoidal figures. 
+Therefore this is not seen as a problem - especially this lack of support 
+is explicitly stated here.
+ 
+ALGORITHM REFERENCES
+
+1.  "Cartographic Projection Procedures for the UNIX Environment - 
+     A User's Manual" by Gerald I. Evenden, USGS Open File Report 90-284
+    and Release 4 Interim Reports (2003)
+
+2.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
+    Projections", Univ. Chicago Press, 1993
+*******************************************************************************/
+
+Proj4js.Proj.cea = {
+
+/* Initialize the Cylindrical Equal Area projection
+  -------------------------------------------*/
+  init: function() {
+    //no-op
+  },
+
+
+  /* Cylindrical Equal Area forward equations--mapping lat,long to x,y
+    ------------------------------------------------------------*/
+  forward: function(p) {
+    var lon=p.x;
+    var lat=p.y;
+    /* Forward equations
+      -----------------*/
+    dlon = Proj4js.common.adjust_lon(lon -this.long0);
+    var x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
+    var y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts);
+   /* Elliptical Forward Transform
+      Not implemented due to a lack of a matchign inverse function
+    {
+      var Sin_Lat = Math.sin(lat);
+      var Rn = this.a * (Math.sqrt(1.0e0 - this.es * Sin_Lat * Sin_Lat ));
+      x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
+      y = this.y0 + Rn * Math.sin(lat) / Math.cos(this.lat_ts);
+    }
+   */
+
+
+    p.x=x;
+    p.y=y;
+    return p;
+  },//ceaFwd()
+
+  /* Cylindrical Equal Area inverse equations--mapping x,y to lat/long
+    ------------------------------------------------------------*/
+  inverse: function(p) {
+    p.x -= this.x0;
+    p.y -= this.y0;
+
+    var lon = Proj4js.common.adjust_lon( this.long0 + (p.x / this.a) / Math.cos(this.lat_ts) );
+
+    var lat = Math.asin( (p.y/this.a) * Math.cos(this.lat_ts) );
+
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }//ceaInv()
+};
+/* ======================================================================
+    projCode/eqc.js
+   ====================================================================== */
+
+/* similar to equi.js FIXME proj4 uses eqc */
+Proj4js.Proj.eqc = {
+  init : function() {
+
+      if(!this.x0) this.x0=0;
+      if(!this.y0) this.y0=0;
+      if(!this.lat0) this.lat0=0;
+      if(!this.long0) this.long0=0;
+      if(!this.lat_ts) this.lat_ts=0;
+      if (!this.title) this.title = "Equidistant Cylindrical (Plate Carre)";
+
+      this.rc= Math.cos(this.lat_ts);
+    },
+
+
+    // forward equations--mapping lat,long to x,y
+    // -----------------------------------------------------------------
+    forward : function(p) {
+
+      var lon= p.x;
+      var lat= p.y;
+
+      var dlon = Proj4js.common.adjust_lon(lon - this.long0);
+      var dlat = Proj4js.common.adjust_lat(lat - this.lat0 );
+      p.x= this.x0 + (this.a*dlon*this.rc);
+      p.y= this.y0 + (this.a*dlat        );
+      return p;
+    },
+
+  // inverse equations--mapping x,y to lat/long
+  // -----------------------------------------------------------------
+  inverse : function(p) {
+
+    var x= p.x;
+    var y= p.y;
+
+    p.x= Proj4js.common.adjust_lon(this.long0 + ((x - this.x0)/(this.a*this.rc)));
+    p.y= Proj4js.common.adjust_lat(this.lat0  + ((y - this.y0)/(this.a        )));
+    return p;
+  }
+
+};
+/* ======================================================================
+    projCode/cass.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            CASSINI
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Cassini projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+    Ported from PROJ.4.
+
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+*******************************************************************************/
+
+
+//Proj4js.defs["EPSG:28191"] = "+proj=cass +lat_0=31.73409694444445 +lon_0=35.21208055555556 +x_0=170251.555 +y_0=126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs";
+
+// Initialize the Cassini projection
+// -----------------------------------------------------------------
+
+Proj4js.Proj.cass = {
+  init : function() {
+    if (!this.sphere) {
+      this.en = this.pj_enfn(this.es)
+      this.m0 = this.pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
+    }
+  },
+
+  C1:	.16666666666666666666,
+  C2:	.00833333333333333333,
+  C3:	.04166666666666666666,
+  C4:	.33333333333333333333,
+  C5:	.06666666666666666666,
+
+
+/* Cassini forward equations--mapping lat,long to x,y
+  -----------------------------------------------------------------------*/
+  forward: function(p) {
+
+    /* Forward equations
+      -----------------*/
+    var x,y;
+    var lam=p.x;
+    var phi=p.y;
+    lam = Proj4js.common.adjust_lon(lam - this.long0);
+    
+    if (this.sphere) {
+      x = Math.asin(Math.cos(phi) * Math.sin(lam));
+      y = Math.atan2(Math.tan(phi) , Math.cos(lam)) - this.phi0;
+    } else {
+        //ellipsoid
+      this.n = Math.sin(phi);
+      this.c = Math.cos(phi);
+      y = this.pj_mlfn(phi, this.n, this.c, this.en);
+      this.n = 1./Math.sqrt(1. - this.es * this.n * this.n);
+      this.tn = Math.tan(phi); 
+      this.t = this.tn * this.tn;
+      this.a1 = lam * this.c;
+      this.c *= this.es * this.c / (1 - this.es);
+      this.a2 = this.a1 * this.a1;
+      x = this.n * this.a1 * (1. - this.a2 * this.t * (this.C1 - (8. - this.t + 8. * this.c) * this.a2 * this.C2));
+      y -= this.m0 - this.n * this.tn * this.a2 * (.5 + (5. - this.t + 6. * this.c) * this.a2 * this.C3);
+    }
+    
+    p.x = this.a*x + this.x0;
+    p.y = this.a*y + this.y0;
+    return p;
+  },//cassFwd()
+
+/* Inverse equations
+  -----------------*/
+  inverse: function(p) {
+    p.x -= this.x0;
+    p.y -= this.y0;
+    var x = p.x/this.a;
+    var y = p.y/this.a;
+    
+    if (this.sphere) {
+      this.dd = y + this.lat0;
+      phi = Math.asin(Math.sin(this.dd) * Math.cos(x));
+      lam = Math.atan2(Math.tan(x), Math.cos(this.dd));
+    } else {
+      /* ellipsoid */
+      ph1 = this.pj_inv_mlfn(this.m0 + y, this.es, this.en);
+      this.tn = Math.tan(ph1); 
+      this.t = this.tn * this.tn;
+      this.n = Math.sin(ph1);
+      this.r = 1. / (1. - this.es * this.n * this.n);
+      this.n = Math.sqrt(this.r);
+      this.r *= (1. - this.es) * this.n;
+      this.dd = x / this.n;
+      this.d2 = this.dd * this.dd;
+      phi = ph1 - (this.n * this.tn / this.r) * this.d2 * (.5 - (1. + 3. * this.t) * this.d2 * this.C3);
+      lam = this.dd * (1. + this.t * this.d2 * (-this.C4 + (1. + 3. * this.t) * this.d2 * this.C5)) / Math.cos(ph1);
+    }
+    p.x = Proj4js.common.adjust_lon(this.long0+lam);
+    p.y = phi;
+    return p;
+  },//lamazInv()
+
+
+  //code from the PROJ.4 pj_mlfn.c file;  this may be useful for other projections
+  pj_enfn: function(es) {
+    en = new Array();
+    en[0] = this.C00 - es * (this.C02 + es * (this.C04 + es * (this.C06 + es * this.C08)));
+    en[1] = es * (this.C22 - es * (this.C04 + es * (this.C06 + es * this.C08)));
+    var t = es * es;
+    en[2] = t * (this.C44 - es * (this.C46 + es * this.C48));
+    t *= es;
+    en[3] = t * (this.C66 - es * this.C68);
+    en[4] = t * es * this.C88;
+    return en;
+  },
+  
+  pj_mlfn: function(phi, sphi, cphi, en) {
+    cphi *= sphi;
+    sphi *= sphi;
+    return(en[0] * phi - cphi * (en[1] + sphi*(en[2]+ sphi*(en[3] + sphi*en[4]))));
+  },
+  
+  pj_inv_mlfn: function(arg, es, en) {
+    k = 1./(1.-es);
+    phi = arg;
+    for (i = Proj4js.common.MAX_ITER; i ; --i) { /* rarely goes over 2 iterations */
+      s = Math.sin(phi);
+      t = 1. - es * s * s;
+      //t = this.pj_mlfn(phi, s, Math.cos(phi), en) - arg;
+      //phi -= t * (t * Math.sqrt(t)) * k;
+      t = (this.pj_mlfn(phi, s, Math.cos(phi), en) - arg) * (t * Math.sqrt(t)) * k;
+      phi -= t;
+      if (Math.abs(t) < Proj4js.common.EPSLN)
+        return phi;
+    }
+    Proj4js.reportError("cass:pj_inv_mlfn: Convergence error");
+    return phi;
+  },
+
+/* meridinal distance for ellipsoid and inverse
+**	8th degree - accurate to < 1e-5 meters when used in conjuction
+**		with typical major axis values.
+**	Inverse determines phi to EPS (1e-11) radians, about 1e-6 seconds.
+*/
+  C00: 1.0,
+  C02: .25,
+  C04: .046875,
+  C06: .01953125,
+  C08: .01068115234375,
+  C22: .75,
+  C44: .46875,
+  C46: .01302083333333333333,
+  C48: .00712076822916666666,
+  C66: .36458333333333333333,
+  C68: .00569661458333333333,
+  C88: .3076171875
+
+}
+/* ======================================================================
+    projCode/gauss.js
+   ====================================================================== */
+
+
+Proj4js.Proj.gauss = {
+
+  init : function() {
+    sphi = Math.sin(this.lat0);
+    cphi = Math.cos(this.lat0);  
+    cphi *= cphi;
+    this.rc = Math.sqrt(1.0 - this.es) / (1.0 - this.es * sphi * sphi);
+    this.C = Math.sqrt(1.0 + this.es * cphi * cphi / (1.0 - this.es));
+    this.phic0 = Math.asin(sphi / this.C);
+    this.ratexp = 0.5 * this.C * this.e;
+    this.K = Math.tan(0.5 * this.phic0 + Proj4js.common.FORTPI) / (Math.pow(Math.tan(0.5*this.lat0 + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e*sphi, this.ratexp));
+  },
+
+  forward : function(p) {
+    var lon = p.x;
+    var lat = p.y;
+
+    p.y = 2.0 * Math.atan( this.K * Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e * Math.sin(lat), this.ratexp) ) - Proj4js.common.HALF_PI;
+    p.x = this.C * lon;
+    return p;
+  },
+
+  inverse : function(p) {
+    var DEL_TOL = 1e-14;
+    var lon = p.x / this.C;
+    var lat = p.y;
+    num = Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI)/this.K, 1./this.C);
+    for (var i = Proj4js.common.MAX_ITER; i>0; --i) {
+      lat = 2.0 * Math.atan(num * Proj4js.common.srat(this.e * Math.sin(p.y), -0.5 * this.e)) - Proj4js.common.HALF_PI;
+      if (Math.abs(lat - p.y) < DEL_TOL) break;
+      p.y = lat;
+    }	
+    /* convergence failed */
+    if (!i) {
+      Proj4js.reportError("gauss:inverse:convergence failed");
+      return null;
+    }
+    p.x = lon;
+    p.y = lat;
+    return p;
+  }
+};
+
+/* ======================================================================
+    projCode/omerc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                       OBLIQUE MERCATOR (HOTINE) 
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Oblique Mercator projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+T. Mittan		Mar, 1993
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+*******************************************************************************/
+
+Proj4js.Proj.omerc = {
+
+  /* Initialize the Oblique Mercator  projection
+    ------------------------------------------*/
+  init: function() {
+    if (!this.mode) this.mode=0;
+    if (!this.lon1)   {this.lon1=0;this.mode=1;}
+    if (!this.lon2)   this.lon2=0;
+    if (!this.lat2)    this.lat2=0;
+
+    /* Place parameters in static storage for common use
+      -------------------------------------------------*/
+    var temp = this.b/ this.a;
+    var es = 1.0 - Math.pow(temp,2);
+    var e = Math.sqrt(es);
+
+    this.sin_p20=Math.sin(this.lat0);
+    this.cos_p20=Math.cos(this.lat0);
+
+    this.con = 1.0 - this.es * this.sin_p20 * this.sin_p20;
+    this.com = Math.sqrt(1.0 - es);
+    this.bl = Math.sqrt(1.0 + this.es * Math.pow(this.cos_p20,4.0)/(1.0 - es));
+    this.al = this.a * this.bl * this.k0 * this.com / this.con;
+    if (Math.abs(this.lat0) < Proj4js.common.EPSLN) {
+       this.ts = 1.0;
+       this.d = 1.0;
+       this.el = 1.0;
+    } else {
+       this.ts = Proj4js.common.tsfnz(this.e,this.lat0,this.sin_p20);
+       this.con = Math.sqrt(this.con);
+       this.d = this.bl * this.com / (this.cos_p20 * this.con);
+       if ((this.d * this.d - 1.0) > 0.0) {
+          if (this.lat0 >= 0.0) {
+             this.f = this.d + Math.sqrt(this.d * this.d - 1.0);
+          } else {
+             this.f = this.d - Math.sqrt(this.d * this.d - 1.0);
+          }
+       } else {
+         this.f = this.d;
+       }
+       this.el = this.f * Math.pow(this.ts,this.bl);
+    }
+
+    //this.longc=52.60353916666667;
+
+    if (this.mode != 0) {
+       this.g = .5 * (this.f - 1.0/this.f);
+       this.gama = Proj4js.common.asinz(Math.sin(this.alpha) / this.d);
+       this.longc= this.longc - Proj4js.common.asinz(this.g * Math.tan(this.gama))/this.bl;
+
+       /* Report parameters common to format B
+       -------------------------------------*/
+       //genrpt(azimuth * R2D,"Azimuth of Central Line:    ");
+       //cenlon(lon_origin);
+      // cenlat(lat_origin);
+
+       this.con = Math.abs(this.lat0);
+       if ((this.con > Proj4js.common.EPSLN) && (Math.abs(this.con - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN)) {
+            this.singam=Math.sin(this.gama);
+            this.cosgam=Math.cos(this.gama);
+
+            this.sinaz=Math.sin(this.alpha);
+            this.cosaz=Math.cos(this.alpha);
+
+            if (this.lat0>= 0) {
+               this.u =  (this.al / this.bl) * Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
+            } else {
+               this.u =  -(this.al / this.bl) *Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
+            }
+          } else {
+            Proj4js.reportError("omerc:Init:DataError");
+          }
+       } else {
+       this.sinphi =Math. sin(this.at1);
+       this.ts1 = Proj4js.common.tsfnz(this.e,this.lat1,this.sinphi);
+       this.sinphi = Math.sin(this.lat2);
+       this.ts2 = Proj4js.common.tsfnz(this.e,this.lat2,this.sinphi);
+       this.h = Math.pow(this.ts1,this.bl);
+       this.l = Math.pow(this.ts2,this.bl);
+       this.f = this.el/this.h;
+       this.g = .5 * (this.f - 1.0/this.f);
+       this.j = (this.el * this.el - this.l * this.h)/(this.el * this.el + this.l * this.h);
+       this.p = (this.l - this.h) / (this.l + this.h);
+       this.dlon = this.lon1 - this.lon2;
+       if (this.dlon < -Proj4js.common.PI) this.lon2 = this.lon2 - 2.0 * Proj4js.common.PI;
+       if (this.dlon > Proj4js.common.PI) this.lon2 = this.lon2 + 2.0 * Proj4js.common.PI;
+       this.dlon = this.lon1 - this.lon2;
+       this.longc = .5 * (this.lon1 + this.lon2) -Math.atan(this.j * Math.tan(.5 * this.bl * this.dlon)/this.p)/this.bl;
+       this.dlon  = Proj4js.common.adjust_lon(this.lon1 - this.longc);
+       this.gama = Math.atan(Math.sin(this.bl * this.dlon)/this.g);
+       this.alpha = Proj4js.common.asinz(this.d * Math.sin(this.gama));
+
+       /* Report parameters common to format A
+       -------------------------------------*/
+
+       if (Math.abs(this.lat1 - this.lat2) <= Proj4js.common.EPSLN) {
+          Proj4js.reportError("omercInitDataError");
+          //return(202);
+       } else {
+          this.con = Math.abs(this.lat1);
+       }
+       if ((this.con <= Proj4js.common.EPSLN) || (Math.abs(this.con - HALF_PI) <= Proj4js.common.EPSLN)) {
+           Proj4js.reportError("omercInitDataError");
+                //return(202);
+       } else {
+         if (Math.abs(Math.abs(this.lat0) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN) {
+            Proj4js.reportError("omercInitDataError");
+            //return(202);
+         }
+       }
+
+       this.singam=Math.sin(this.gam);
+       this.cosgam=Math.cos(this.gam);
+
+       this.sinaz=Math.sin(this.alpha);
+       this.cosaz=Math.cos(this.alpha);  
+
+
+       if (this.lat0 >= 0) {
+          this.u =  (this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
+       } else {
+          this.u = -(this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
+       }
+     }
+  },
+
+
+  /* Oblique Mercator forward equations--mapping lat,long to x,y
+    ----------------------------------------------------------*/
+  forward: function(p) {
+    var theta;		/* angle					*/
+    var sin_phi, cos_phi;/* sin and cos value				*/
+    var b;		/* temporary values				*/
+    var c, t, tq;	/* temporary values				*/
+    var con, n, ml;	/* cone constant, small m			*/
+    var q,us,vl;
+    var ul,vs;
+    var s;
+    var dlon;
+    var ts1;
+
+    var lon=p.x;
+    var lat=p.y;
+    /* Forward equations
+      -----------------*/
+    sin_phi = Math.sin(lat);
+    dlon = Proj4js.common.adjust_lon(lon - this.longc);
+    vl = Math.sin(this.bl * dlon);
+    if (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN) {
+       ts1 = Proj4js.common.tsfnz(this.e,lat,sin_phi);
+       q = this.el / (Math.pow(ts1,this.bl));
+       s = .5 * (q - 1.0 / q);
+       t = .5 * (q + 1.0/ q);
+       ul = (s * this.singam - vl * this.cosgam) / t;
+       con = Math.cos(this.bl * dlon);
+       if (Math.abs(con) < .0000001) {
+          us = this.al * this.bl * dlon;
+       } else {
+          us = this.al * Math.atan((s * this.cosgam + vl * this.singam) / con)/this.bl;
+          if (con < 0) us = us + Proj4js.common.PI * this.al / this.bl;
+       }
+    } else {
+       if (lat >= 0) {
+          ul = this.singam;
+       } else {
+          ul = -this.singam;
+       }
+       us = this.al * lat / this.bl;
+    }
+    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN) {
+       //alert("Point projects into infinity","omer-for");
+       Proj4js.reportError("omercFwdInfinity");
+       //return(205);
+    }
+    vs = .5 * this.al * Math.log((1.0 - ul)/(1.0 + ul)) / this.bl;
+    us = us - this.u;
+    var x = this.x0 + vs * this.cosaz + us * this.sinaz;
+    var y = this.y0 + us * this.cosaz - vs * this.sinaz;
+
+    p.x=x;
+    p.y=y;
+    return p;
+  },
+
+  inverse: function(p) {
+    var delta_lon;	/* Delta longitude (Given longitude - center 	*/
+    var theta;		/* angle					*/
+    var delta_theta;	/* adjusted longitude				*/
+    var sin_phi, cos_phi;/* sin and cos value				*/
+    var b;		/* temporary values				*/
+    var c, t, tq;	/* temporary values				*/
+    var con, n, ml;	/* cone constant, small m			*/
+    var vs,us,q,s,ts1;
+    var vl,ul,bs;
+    var dlon;
+    var  flag;
+
+    /* Inverse equations
+      -----------------*/
+    p.x -= this.x0;
+    p.y -= this.y0;
+    flag = 0;
+    vs = p.x * this.cosaz - p.y * this.sinaz;
+    us = p.y * this.cosaz + p.x * this.sinaz;
+    us = us + this.u;
+    q = Math.exp(-this.bl * vs / this.al);
+    s = .5 * (q - 1.0/q);
+    t = .5 * (q + 1.0/q);
+    vl = Math.sin(this.bl * us / this.al);
+    ul = (vl * this.cosgam + s * this.singam)/t;
+    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN)
+       {
+       lon = this.longc;
+       if (ul >= 0.0) {
+          lat = Proj4js.common.HALF_PI;
+       } else {
+         lat = -Proj4js.common.HALF_PI;
+       }
+    } else {
+       con = 1.0 / this.bl;
+       ts1 =Math.pow((this.el / Math.sqrt((1.0 + ul) / (1.0 - ul))),con);
+       lat = Proj4js.common.phi2z(this.e,ts1);
+       //if (flag != 0)
+          //return(flag);
+       //~ con = Math.cos(this.bl * us /al);
+       theta = this.longc - Math.atan2((s * this.cosgam - vl * this.singam) , con)/this.bl;
+       lon = Proj4js.common.adjust_lon(theta);
+    }
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }
+};
+/* ======================================================================
+    projCode/lcc.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            LAMBERT CONFORMAL CONIC
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Lambert Conformal Conic projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+*******************************************************************************/
+
+
+//<2104> +proj=lcc +lat_1=10.16666666666667 +lat_0=10.16666666666667 +lon_0=-71.60561777777777 +k_0=1 +x0=-17044 +x0=-23139.97 +ellps=intl +units=m +no_defs  no_defs
+
+// Initialize the Lambert Conformal conic projection
+// -----------------------------------------------------------------
+
+//Proj4js.Proj.lcc = Class.create();
+Proj4js.Proj.lcc = {
+  init : function() {
+
+    // array of:  r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north
+    //double c_lat;                   /* center latitude                      */
+    //double c_lon;                   /* center longitude                     */
+    //double lat1;                    /* first standard parallel              */
+    //double lat2;                    /* second standard parallel             */
+    //double r_maj;                   /* major axis                           */
+    //double r_min;                   /* minor axis                           */
+    //double false_east;              /* x offset in meters                   */
+    //double false_north;             /* y offset in meters                   */
+
+      if (!this.lat2){this.lat2=this.lat0;}//if lat2 is not defined
+      if (!this.k0) this.k0 = 1.0;
+
+    // Standard Parallels cannot be equal and on opposite sides of the equator
+      if (Math.abs(this.lat1+this.lat2) < Proj4js.common.EPSLN) {
+        Proj4js.reportError("lcc:init: Equal Latitudes");
+        return;
+      }
+
+      var temp = this.b / this.a;
+      this.e = Math.sqrt(1.0 - temp*temp);
+
+      var sin1 = Math.sin(this.lat1);
+      var cos1 = Math.cos(this.lat1);
+      var ms1 = Proj4js.common.msfnz(this.e, sin1, cos1);
+      var ts1 = Proj4js.common.tsfnz(this.e, this.lat1, sin1);
+
+      var sin2 = Math.sin(this.lat2);
+      var cos2 = Math.cos(this.lat2);
+      var ms2 = Proj4js.common.msfnz(this.e, sin2, cos2);
+      var ts2 = Proj4js.common.tsfnz(this.e, this.lat2, sin2);
+
+      var ts0 = Proj4js.common.tsfnz(this.e, this.lat0, Math.sin(this.lat0));
+
+      if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
+        this.ns = Math.log(ms1/ms2)/Math.log(ts1/ts2);
+      } else {
+        this.ns = sin1;
+      }
+      this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns));
+      this.rh = this.a * this.f0 * Math.pow(ts0, this.ns);
+      if (!this.title) this.title = "Lambert Conformal Conic";
+    },
+
+
+    // Lambert Conformal conic forward equations--mapping lat,long to x,y
+    // -----------------------------------------------------------------
+    forward : function(p) {
+
+      var lon = p.x;
+      var lat = p.y;
+
+    // convert to radians
+      if ( lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) {
+        //lon = lon * Proj4js.common.D2R;
+        //lat = lat * Proj4js.common.D2R;
+      } else {
+        Proj4js.reportError("lcc:forward: llInputOutOfRange: "+ lon +" : " + lat);
+        return null;
+      }
+
+      var con  = Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI);
+      var ts, rh1;
+      if (con > Proj4js.common.EPSLN) {
+        ts = Proj4js.common.tsfnz(this.e, lat, Math.sin(lat) );
+        rh1 = this.a * this.f0 * Math.pow(ts, this.ns);
+      } else {
+        con = lat * this.ns;
+        if (con <= 0) {
+          Proj4js.reportError("lcc:forward: No Projection");
+          return null;
+        }
+        rh1 = 0;
+      }
+      var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);
+      p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0;
+      p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0;
+
+      return p;
+    },
+
+  // Lambert Conformal Conic inverse equations--mapping x,y to lat/long
+  // -----------------------------------------------------------------
+  inverse : function(p) {
+
+    var rh1, con, ts;
+    var lat, lon;
+    var x = (p.x - this.x0)/this.k0;
+    var y = (this.rh - (p.y - this.y0)/this.k0);
+    if (this.ns > 0) {
+      rh1 = Math.sqrt (x * x + y * y);
+      con = 1.0;
+    } else {
+      rh1 = -Math.sqrt (x * x + y * y);
+      con = -1.0;
+    }
+    var theta = 0.0;
+    if (rh1 != 0) {
+      theta = Math.atan2((con * x),(con * y));
+    }
+    if ((rh1 != 0) || (this.ns > 0.0)) {
+      con = 1.0/this.ns;
+      ts = Math.pow((rh1/(this.a * this.f0)), con);
+      lat = Proj4js.common.phi2z(this.e, ts);
+      if (lat == -9999) return null;
+    } else {
+      lat = -Proj4js.common.HALF_PI;
+    }
+    lon = Proj4js.common.adjust_lon(theta/this.ns + this.long0);
+
+    p.x = lon;
+    p.y = lat;
+    return p;
+  }
+};
+
+
+
+
+/* ======================================================================
+    projCode/laea.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                  LAMBERT AZIMUTHAL EQUAL-AREA
+ 
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the Lambert Azimuthal Equal-Area projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE            
+----------              ----           
+D. Steinwand, EROS      March, 1991   
+
+This function was adapted from the Lambert Azimuthal Equal Area projection
+code (FORTRAN) in the General Cartographic Transformation Package software
+which is available from the U.S. Geological Survey National Mapping Division.
+ 
+ALGORITHM REFERENCES
+
+1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
+    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
+
+2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+
+3.  "Software Documentation for GCTP General Cartographic Transformation
+    Package", U.S. Geological Survey National Mapping Division, May 1982.
+*******************************************************************************/
+
+Proj4js.Proj.laea = {
+  S_POLE: 1,
+  N_POLE: 2,
+  EQUIT: 3,
+  OBLIQ: 4,
+
+
+/* Initialize the Lambert Azimuthal Equal Area projection
+  ------------------------------------------------------*/
+  init: function() {
+    var t = Math.abs(this.lat0);
+    if (Math.abs(t - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
+      this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
+    } else if (Math.abs(t) < Proj4js.common.EPSLN) {
+      this.mode = this.EQUIT;
+    } else {
+      this.mode = this.OBLIQ;
+    }
+    if (this.es > 0) {
+      var sinphi;
+  
+      this.qp = Proj4js.common.qsfnz(this.e, 1.0);
+      this.mmf = .5 / (1. - this.es);
+      this.apa = this.authset(this.es);
+      switch (this.mode) {
+        case this.N_POLE:
+        case this.S_POLE:
+          this.dd = 1.;
+          break;
+        case this.EQUIT:
+          this.rq = Math.sqrt(.5 * this.qp);
+          this.dd = 1. / this.rq;
+          this.xmf = 1.;
+          this.ymf = .5 * this.qp;
+          break;
+        case this.OBLIQ:
+          this.rq = Math.sqrt(.5 * this.qp);
+          sinphi = Math.sin(this.lat0);
+          this.sinb1 = Proj4js.common.qsfnz(this.e, sinphi) / this.qp;
+          this.cosb1 = Math.sqrt(1. - this.sinb1 * this.sinb1);
+          this.dd = Math.cos(this.lat0) / (Math.sqrt(1. - this.es * sinphi * sinphi) * this.rq * this.cosb1);
+          this.ymf = (this.xmf = this.rq) / this.dd;
+          this.xmf *= this.dd;
+          break;
+      }
+    } else {
+      if (this.mode == this.OBLIQ) {
+        this.sinph0 = Math.sin(this.lat0);
+        this.cosph0 = Math.cos(this.lat0);
+      }
+    }
+  },
+
+/* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y
+  -----------------------------------------------------------------------*/
+  forward: function(p) {
+
+    /* Forward equations
+      -----------------*/
+    var x,y;
+    var lam=p.x;
+    var phi=p.y;
+    lam = Proj4js.common.adjust_lon(lam - this.long0);
+    
+    if (this.sphere) {
+        var coslam, cosphi, sinphi;
+      
+        sinphi = Math.sin(phi);
+        cosphi = Math.cos(phi);
+        coslam = Math.cos(lam);
+        switch (this.mode) {
+          case this.EQUIT:
+            y = (this.mode == this.EQUIT) ? 1. + cosphi * coslam : 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
+            if (y <= Proj4js.common.EPSLN) {
+              Proj4js.reportError("laea:fwd:y less than eps");
+              return null;
+            }
+            y = Math.sqrt(2. / y);
+            x = y * cosphi * Math.sin(lam);
+            y *= (this.mode == this.EQUIT) ? sinphi : this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
+            break;
+          case this.N_POLE:
+            coslam = -coslam;
+          case this.S_POLE:
+            if (Math.abs(phi + this.phi0) < Proj4js.common.EPSLN) {
+              Proj4js.reportError("laea:fwd:phi < eps");
+              return null;
+            }
+            y = Proj4js.common.FORTPI - phi * .5;
+            y = 2. * ((this.mode == this.S_POLE) ? Math.cos(y) : Math.sin(y));
+            x = y * Math.sin(lam);
+            y *= coslam;
+            break;
+        }
+    } else {
+        var coslam, sinlam, sinphi, q, sinb=0.0, cosb=0.0, b=0.0;
+      
+        coslam = Math.cos(lam);
+        sinlam = Math.sin(lam);
+        sinphi = Math.sin(phi);
+        q = Proj4js.common.qsfnz(this.e, sinphi);
+        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
+          sinb = q / this.qp;
+          cosb = Math.sqrt(1. - sinb * sinb);
+        }
+        switch (this.mode) {
+          case this.OBLIQ:
+            b = 1. + this.sinb1 * sinb + this.cosb1 * cosb * coslam;
+            break;
+          case this.EQUIT:
+            b = 1. + cosb * coslam;
+            break;
+          case this.N_POLE:
+            b = Proj4js.common.HALF_PI + phi;
+            q = this.qp - q;
+            break;
+          case this.S_POLE:
+            b = phi - Proj4js.common.HALF_PI;
+            q = this.qp + q;
+            break;
+        }
+        if (Math.abs(b) < Proj4js.common.EPSLN) {
+            Proj4js.reportError("laea:fwd:b < eps");
+            return null;
+        }
+        switch (this.mode) {
+          case this.OBLIQ:
+          case this.EQUIT:
+            b = Math.sqrt(2. / b);
+            if (this.mode == this.OBLIQ) {
+              y = this.ymf * b * (this.cosb1 * sinb - this.sinb1 * cosb * coslam);
+            } else {
+              y = (b = Math.sqrt(2. / (1. + cosb * coslam))) * sinb * this.ymf;
+            }
+            x = this.xmf * b * cosb * sinlam;
+            break;
+          case this.N_POLE:
+          case this.S_POLE:
+            if (q >= 0.) {
+              x = (b = Math.sqrt(q)) * sinlam;
+              y = coslam * ((this.mode == this.S_POLE) ? b : -b);
+            } else {
+              x = y = 0.;
+            }
+            break;
+        }
+    }
+
+    //v 1.0
+    /*
+    var sin_lat=Math.sin(lat);
+    var cos_lat=Math.cos(lat);
+
+    var sin_delta_lon=Math.sin(delta_lon);
+    var cos_delta_lon=Math.cos(delta_lon);
+
+    var g =this.sin_lat_o * sin_lat +this.cos_lat_o * cos_lat * cos_delta_lon;
+    if (g == -1.0) {
+      Proj4js.reportError("laea:fwd:Point projects to a circle of radius "+ 2.0 * R);
+      return null;
+    }
+    var ksp = this.a * Math.sqrt(2.0 / (1.0 + g));
+    var x = ksp * cos_lat * sin_delta_lon + this.x0;
+    var y = ksp * (this.cos_lat_o * sin_lat - this.sin_lat_o * cos_lat * cos_delta_lon) + this.y0;
+    */
+    p.x = this.a*x + this.x0;
+    p.y = this.a*y + this.y0;
+    return p;
+  },//lamazFwd()
+
+/* Inverse equations
+  -----------------*/
+  inverse: function(p) {
+    p.x -= this.x0;
+    p.y -= this.y0;
+    var x = p.x/this.a;
+    var y = p.y/this.a;
+    
+    if (this.sphere) {
+        var  cosz=0.0, rh, sinz=0.0;
+      
+        rh = Math.sqrt(x*x + y*y);
+        var phi = rh * .5;
+        if (phi > 1.) {
+          Proj4js.reportError("laea:Inv:DataError");
+          return null;
+        }
+        phi = 2. * Math.asin(phi);
+        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
+          sinz = Math.sin(phi);
+          cosz = Math.cos(phi);
+        }
+        switch (this.mode) {
+        case this.EQUIT:
+          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? 0. : Math.asin(y * sinz / rh);
+          x *= sinz;
+          y = cosz * rh;
+          break;
+        case this.OBLIQ:
+          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? this.phi0 : Math.asin(cosz * sinph0 + y * sinz * cosph0 / rh);
+          x *= sinz * cosph0;
+          y = (cosz - Math.sin(phi) * sinph0) * rh;
+          break;
+        case this.N_POLE:
+          y = -y;
+          phi = Proj4js.common.HALF_PI - phi;
+          break;
+        case this.S_POLE:
+          phi -= Proj4js.common.HALF_PI;
+          break;
+        }
+        lam = (y == 0. && (this.mode == this.EQUIT || this.mode == this.OBLIQ)) ? 0. : Math.atan2(x, y);
+    } else {
+        var cCe, sCe, q, rho, ab=0.0;
+      
+        switch (this.mode) {
+          case this.EQUIT:
+          case this.OBLIQ:
+            x /= this.dd;
+            y *=  this.dd;
+            rho = Math.sqrt(x*x + y*y);
+            if (rho < Proj4js.common.EPSLN) {
+              p.x = 0.;
+              p.y = this.phi0;
+              return p;
+            }
+            sCe = 2. * Math.asin(.5 * rho / this.rq);
+            cCe = Math.cos(sCe);
+            x *= (sCe = Math.sin(sCe));
+            if (this.mode == this.OBLIQ) {
+              ab = cCe * this.sinb1 + y * sCe * this.cosb1 / rho
+              q = this.qp * ab;
+              y = rho * this.cosb1 * cCe - y * this.sinb1 * sCe;
+            } else {
+              ab = y * sCe / rho;
+              q = this.qp * ab;
+              y = rho * cCe;
+            }
+            break;
+          case this.N_POLE:
+            y = -y;
+          case this.S_POLE:
+            q = (x * x + y * y);
+            if (!q ) {
+              p.x = 0.;
+              p.y = this.phi0;
+              return p;
+            }
+            /*
+            q = this.qp - q;
+            */
+            ab = 1. - q / this.qp;
+            if (this.mode == this.S_POLE) {
+              ab = - ab;
+            }
+            break;
+        }
+        lam = Math.atan2(x, y);
+        phi = this.authlat(Math.asin(ab), this.apa);
+    }
+
+    /*
+    var Rh = Math.Math.sqrt(p.x *p.x +p.y * p.y);
+    var temp = Rh / (2.0 * this.a);
+
+    if (temp > 1) {
+      Proj4js.reportError("laea:Inv:DataError");
+      return null;
+    }
+
+    var z = 2.0 * Proj4js.common.asinz(temp);
+    var sin_z=Math.sin(z);
+    var cos_z=Math.cos(z);
+
+    var lon =this.long0;
+    if (Math.abs(Rh) > Proj4js.common.EPSLN) {
+       var lat = Proj4js.common.asinz(this.sin_lat_o * cos_z +this. cos_lat_o * sin_z *p.y / Rh);
+       var temp =Math.abs(this.lat0) - Proj4js.common.HALF_PI;
+       if (Math.abs(temp) > Proj4js.common.EPSLN) {
+          temp = cos_z -this.sin_lat_o * Math.sin(lat);
+          if(temp!=0.0) lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x*sin_z*this.cos_lat_o,temp*Rh));
+       } else if (this.lat0 < 0.0) {
+          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x,p.y));
+       } else {
+          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
+       }
+    } else {
+      lat = this.lat0;
+    }
+    */
+    //return(OK);
+    p.x = Proj4js.common.adjust_lon(this.long0+lam);
+    p.y = phi;
+    return p;
+  },//lamazInv()
+  
+/* determine latitude from authalic latitude */
+  P00: .33333333333333333333,
+  P01: .17222222222222222222,
+  P02: .10257936507936507936,
+  P10: .06388888888888888888,
+  P11: .06640211640211640211,
+  P20: .01641501294219154443,
+  
+  authset: function(es) {
+    var t;
+    var APA = new Array();
+    APA[0] = es * this.P00;
+    t = es * es;
+    APA[0] += t * this.P01;
+    APA[1] = t * this.P10;
+    t *= es;
+    APA[0] += t * this.P02;
+    APA[1] += t * this.P11;
+    APA[2] = t * this.P20;
+    return APA;
+  },
+  
+  authlat: function(beta, APA) {
+    var t = beta+beta;
+    return(beta + APA[0] * Math.sin(t) + APA[1] * Math.sin(t+t) + APA[2] * Math.sin(t+t+t));
+  }
+  
+};
+
+
+
+/* ======================================================================
+    projCode/aeqd.js
+   ====================================================================== */
+
+Proj4js.Proj.aeqd = {
+
+  init : function() {
+    this.sin_p12=Math.sin(this.lat0);
+    this.cos_p12=Math.cos(this.lat0);
+  },
+
+  forward: function(p) {
+    var lon=p.x;
+    var lat=p.y;
+    var ksp;
+
+    var sinphi=Math.sin(p.y);
+    var cosphi=Math.cos(p.y); 
+    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
+    var coslon = Math.cos(dlon);
+    var g = this.sin_p12 * sinphi + this.cos_p12 * cosphi * coslon;
+    if (Math.abs(Math.abs(g) - 1.0) < Proj4js.common.EPSLN) {
+       ksp = 1.0;
+       if (g < 0.0) {
+         Proj4js.reportError("aeqd:Fwd:PointError");
+         return;
+       }
+    } else {
+       var z = Math.acos(g);
+       ksp = z/Math.sin(z);
+    }
+    p.x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon);
+    p.y = this.y0 + this.a * ksp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * coslon);
+    return p;
+  },
+
+  inverse: function(p){
+    p.x -= this.x0;
+    p.y -= this.y0;
+
+    var rh = Math.sqrt(p.x * p.x + p.y *p.y);
+    if (rh > (2.0 * Proj4js.common.HALF_PI * this.a)) {
+       Proj4js.reportError("aeqdInvDataError");
+       return;
+    }
+    var z = rh / this.a;
+
+    var sinz=Math.sin(z);
+    var cosz=Math.cos(z);
+
+    var lon = this.long0;
+    var lat;
+    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
+      lat = this.lat0;
+    } else {
+      lat = Proj4js.common.asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh);
+      var con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
+      if (Math.abs(con) <= Proj4js.common.EPSLN) {
+        if (lat0 >= 0.0) {
+          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x , -p.y));
+        } else {
+          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x , p.y));
+        }
+      } else {
+        con = cosz - this.sin_p12 * Math.sin(lat);
+        if ((Math.abs(con) < Proj4js.common.EPSLN) && (Math.abs(p.x) < Proj4js.common.EPSLN)) {
+           //no-op, just keep the lon value as is
+        } else {
+          var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh));
+          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh)));
+        }
+      }
+    }
+
+    p.x = lon;
+    p.y = lat;
+    return p;
+  } 
+};
+/* ======================================================================
+    projCode/moll.js
+   ====================================================================== */
+
+/*******************************************************************************
+NAME                            MOLLWEIDE
+
+PURPOSE:	Transforms input longitude and latitude to Easting and
+		Northing for the MOllweide projection.  The
+		longitude and latitude must be in radians.  The Easting
+		and Northing values will be returned in meters.
+
+PROGRAMMER              DATE
+----------              ----
+D. Steinwand, EROS      May, 1991;  Updated Sept, 1992; Updated Feb, 1993
+S. Nelson, EDC		Jun, 2993;	Made corrections in precision and
+					number of iterations.
+
+ALGORITHM REFERENCES
+
+1.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
+    U.S. Geological Survey Professional Paper 1453 , United State Government
+    Printing Office, Washington D.C., 1989.
+
+2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
+    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
+    State Government Printing Office, Washington D.C., 1987.
+*******************************************************************************/
+
+Proj4js.Proj.moll = {
+
+  /* Initialize the Mollweide projection
+    ------------------------------------*/
+  init: function(){
+    //no-op
+  },
+
+  /* Mollweide forward equations--mapping lat,long to x,y
+    ----------------------------------------------------*/
+  forward: function(p) {
+
+    /* Forward equations
+      -----------------*/
+    var lon=p.x;
+    var lat=p.y;
+
+    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
+    var theta = lat;
+    var con = Proj4js.common.PI * Math.sin(lat);
+
+    /* Iterate using the Newton-Raphson method to find theta
+      -----------------------------------------------------*/
+    for (var i=0;true;i++) {
+       var delta_theta = -(theta + Math.sin(theta) - con)/ (1.0 + Math.cos(theta));
+       theta += delta_theta;
+       if (Math.abs(delta_theta) < Proj4js.common.EPSLN) break;
+       if (i >= 50) {
+          Proj4js.reportError("moll:Fwd:IterationError");
+         //return(241);
+       }
+    }
+    theta /= 2.0;
+
+    /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting"
+       this is done here because of precision problems with "cos(theta)"
+       --------------------------------------------------------------------------*/
+    if (Proj4js.common.PI/2 - Math.abs(lat) < Proj4js.common.EPSLN) delta_lon =0;
+    var x = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0;
+    var y = 1.4142135623731 * this.a * Math.sin(theta) + this.y0;
+
+    p.x=x;
+    p.y=y;
+    return p;
+  },
+
+  inverse: function(p){
+    var theta;
+    var arg;
+
+    /* Inverse equations
+      -----------------*/
+    p.x-= this.x0;
+    //~ p.y -= this.y0;
+    var arg = p.y /  (1.4142135623731 * this.a);
+
+    /* Because of division by zero problems, 'arg' can not be 1.0.  Therefore
+       a number very close to one is used instead.
+       -------------------------------------------------------------------*/
+    if(Math.abs(arg) > 0.999999999999) arg=0.999999999999;
+    var theta =Math.asin(arg);
+    var lon = Proj4js.common.adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta))));
+    if(lon < (-Proj4js.common.PI)) lon= -Proj4js.common.PI;
+    if(lon > Proj4js.common.PI) lon= Proj4js.common.PI;
+    arg = (2.0 * theta + Math.sin(2.0 * theta)) / Proj4js.common.PI;
+    if(Math.abs(arg) > 1.0)arg=1.0;
+    var lat = Math.asin(arg);
+    //return(OK);
+
+    p.x=lon;
+    p.y=lat;
+    return p;
+  }
+};
+

Copied: trunk/lib/Proj4js/proj4js-compressed.js (from rev 2363, trunk/lib/proj4js-compressed.js)
===================================================================
--- trunk/lib/Proj4js/proj4js-compressed.js	                        (rev 0)
+++ trunk/lib/Proj4js/proj4js-compressed.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,224 @@
+/*
+  proj4js.js -- Javascript reprojection library. 
+  
+  Authors:      Mike Adair madairATdmsolutions.ca
+                Richard Greenwood richATgreenwoodmap.com
+                Didier Richard didier.richardATign.fr
+                Stephen Irons
+  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html 
+                Note: This program is an almost direct port of the C library
+                Proj4.
+*/
+Proj4js={defaultDatum:'WGS84',transform:function(source,dest,point){if(!source.readyToUse){this.reportError("Proj4js initialization for:"+source.srsCode+" not yet complete");return point;}
+if(!dest.readyToUse){this.reportError("Proj4js initialization for:"+dest.srsCode+" not yet complete");return point;}
+if((source.srsProjNumber=="900913"&&dest.datumCode!="WGS84")||(dest.srsProjNumber=="900913"&&source.datumCode!="WGS84")){var wgs84=Proj4js.WGS84;this.transform(source,wgs84,point);source=wgs84;}
+if(source.projName=="longlat"){point.x*=Proj4js.common.D2R;point.y*=Proj4js.common.D2R;}else{if(source.to_meter){point.x*=source.to_meter;point.y*=source.to_meter;}
+source.inverse(point);}
+if(source.from_greenwich){point.x+=source.from_greenwich;}
+point=this.datum_transform(source.datum,dest.datum,point);if(dest.from_greenwich){point.x-=dest.from_greenwich;}
+if(dest.projName=="longlat"){point.x*=Proj4js.common.R2D;point.y*=Proj4js.common.R2D;}else{dest.forward(point);if(dest.to_meter){point.x/=dest.to_meter;point.y/=dest.to_meter;}}
+return point;},datum_transform:function(source,dest,point){if(source.compare_datums(dest)){return point;}
+if(source.datum_type==Proj4js.common.PJD_NODATUM||dest.datum_type==Proj4js.common.PJD_NODATUM){return point;}
+if(source.datum_type==Proj4js.common.PJD_GRIDSHIFT)
+{alert("ERROR: Grid shift transformations are not implemented yet.");}
+if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
+{alert("ERROR: Grid shift transformations are not implemented yet.");}
+if(source.es!=dest.es||source.a!=dest.a||source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM||dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM)
+{source.geodetic_to_geocentric(point);if(source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM){source.geocentric_to_wgs84(point);}
+if(dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM){dest.geocentric_from_wgs84(point);}
+dest.geocentric_to_geodetic(point);}
+if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
+{alert("ERROR: Grid shift transformations are not implemented yet.");}
+return point;},reportError:function(msg){},extend:function(destination,source){destination=destination||{};if(source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}}
+return destination;},Class:function(){var Class=function(){this.initialize.apply(this,arguments);};var extended={};var parent;for(var i=0;i<arguments.length;++i){if(typeof arguments[i]=="function"){parent=arguments[i].prototype;}else{parent=arguments[i];}
+Proj4js.extend(extended,parent);}
+Class.prototype=extended;return Class;},bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},scriptName:"proj4js-compressed.js",defsLookupService:'http://spatialreference.org/ref',libPath:null,getScriptLocation:function(){if(this.libPath)return this.libPath;var scriptName=this.scriptName;var scriptNameLen=scriptName.length;var scripts=document.getElementsByTagName('script');for(var i=0;i<scripts.length;i++){var src=scripts[i].getAttribute('src');if(src){var index=src.lastIndexOf(scriptName);if((index>-1)&&(index+scriptNameLen==src.length)){this.libPath=src.slice(0,-scriptNameLen);break;}}}
+return this.libPath||"";},loadScript:function(url,onload,onfail,loadCheck){var script=document.createElement('script');script.defer=false;script.type="text/javascript";script.id=url;script.src=url;script.onload=onload;script.onerror=onfail;script.loadCheck=loadCheck;if(/MSIE/.test(navigator.userAgent)){script.onreadystatechange=this.checkReadyState;}
+document.getElementsByTagName('head')[0].appendChild(script);},checkReadyState:function(){if(this.readyState=='loaded'){if(!this.loadCheck()){this.onerror();}else{this.onload();}}}};Proj4js.Proj=Proj4js.Class({readyToUse:false,title:null,projName:null,units:null,datum:null,x0:0,y0:0,localCS:false,initialize:function(srsCode){this.srsCodeInput=srsCode;if((srsCode.indexOf('GEOGCS')>=0)||(srsCode.indexOf('GEOCCS')>=0)||(srsCode.indexOf('PROJCS')>=0)||(srsCode.indexOf('LOCAL_CS')>=0)){this.parseWKT(srsCode);this.datum=new Proj4js.datum(this);this.loadProjCode(this.projName);return;}
+if(srsCode.indexOf('urn:')==0){var urn=srsCode.split(':');if((urn[1]=='ogc'||urn[1]=='x-ogc')&&(urn[2]=='def')&&(urn[3]=='crs')){srsCode=urn[4]+':'+urn[urn.length-1];}}else if(srsCode.indexOf('http://')==0){var url=srsCode.split('#');if(url[0].match(/epsg.org/)){srsCode='EPSG:'+url[1];}else if(url[0].match(/RIG.xml/)){srsCode='IGNF:'+url[1];}}
+this.srsCode=srsCode.toUpperCase();if(this.srsCode.indexOf("EPSG")==0){this.srsCode=this.srsCode;this.srsAuth='epsg';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("IGNF")==0){this.srsCode=this.srsCode;this.srsAuth='IGNF';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("CRS")==0){this.srsCode=this.srsCode;this.srsAuth='CRS';this.srsProjNumber=this.srsCode.substring(4);}else{this.srsAuth='';this.srsProjNumber=this.srsCode;}
+this.loadProjDefinition();},loadProjDefinition:function(){if(Proj4js.defs[this.srsCode]){this.defsLoaded();return;}
+var url=Proj4js.getScriptLocation()+'defs/'+this.srsAuth.toUpperCase()+this.srsProjNumber+'.js';Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.loadFromService,this),Proj4js.bind(this.checkDefsLoaded,this));},loadFromService:function(){var url=Proj4js.defsLookupService+'/'+this.srsAuth+'/'+this.srsProjNumber+'/proj4js/';Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.defsFailed,this),Proj4js.bind(this.checkDefsLoaded,this));},defsLoaded:function(){this.parseDefs();this.loadProjCode(this.projName);},checkDefsLoaded:function(){if(Proj4js.defs[this.srsCode]){return true;}else{return false;}},defsFailed:function(){Proj4js.reportError('failed to load projection definition for: '+this.srsCode);Proj4js.defs[this.srsCode]=Proj4js.defs['WGS84'];this.defsLoaded();},loadProjCode:function(projName){if(Proj4js.Proj[projName]){this.initTransforms();return;}
+var url=Proj4js.getScriptLocation()+'projCode/'+projName+'.js';Proj4js.loadScript(url,Proj4js.bind(this.loadProjCodeSuccess,this,projName),Proj4js.bind(this.loadProjCodeFailure,this,projName),Proj4js.bind(this.checkCodeLoaded,this,projName));},loadProjCodeSuccess:function(projName){if(Proj4js.Proj[projName].dependsOn){this.loadProjCode(Proj4js.Proj[projName].dependsOn);}else{this.initTransforms();}},loadProjCodeFailure:function(projName){Proj4js.reportError("failed to find projection file for: "+projName);},checkCodeLoaded:function(projName){if(Proj4js.Proj[projName]){return true;}else{return false;}},initTransforms:function(){Proj4js.extend(this,Proj4js.Proj[this.projName]);this.init();this.readyToUse=true;},wktRE:/^(\w+)\[(.*)\]$/,parseWKT:function(wkt){var wktMatch=wkt.match(this.wktRE);if(!wktMatch)return;var wktObject=wktMatch[1];var wktContent=wktMatch[2];var wktTemp=wktContent.split(",");var wktName=wktTemp.shift();wktName=wktName.replace(/^\"/,"");wktName=wktName.rep
 lace(/\"$/,"");var wktArray=new Array();var bkCount=0;var obj="";for(var i=0;i<wktTemp.length;++i){var token=wktTemp[i];for(var j=0;j<token.length;++j){if(token.charAt(j)=="[")++bkCount;if(token.charAt(j)=="]")--bkCount;}
+obj+=token;if(bkCount===0){wktArray.push(obj);obj="";}else{obj+=",";}}
+switch(wktObject){case'LOCAL_CS':this.projName='identity'
+this.localCS=true;this.srsCode=wktName;break;case'GEOGCS':this.projName='longlat'
+this.geocsCode=wktName;if(!this.srsCode)this.srsCode=wktName;break;case'PROJCS':this.srsCode=wktName;break;case'GEOCCS':break;case'PROJECTION':this.projName=Proj4js.wktProjections[wktName]
+break;case'DATUM':this.datumName=wktName;break;case'LOCAL_DATUM':this.datumCode='none';break;case'SPHEROID':this.ellps=wktName;this.a=parseFloat(wktArray.shift());this.rf=parseFloat(wktArray.shift());break;case'PRIMEM':this.from_greenwich=parseFloat(wktArray.shift());break;case'UNIT':this.units=wktName;this.unitsPerMeter=parseFloat(wktArray.shift());break;case'PARAMETER':var name=wktName;var value=parseFloat(parseFloat(wktArray.shift()));switch(name){case'false_easting':this.x0=value;break;case'false_northing':this.y0=value;break;case'scale_factor':this.k0=value;break;case'central_meridian':this.long0=value;break;case'latitude_of_origin':this.lat0=value;break;case'more_here':break;default:break;}
+break;case'TOWGS84':this.datum_params=wktArray;break;case'MORE_HERE':break;default:break;}
+for(var i=0;i<wktArray.length;++i){this.parseWKT(wktArray[i]);}},parseDefs:function(){this.defData=Proj4js.defs[this.srsCode];var paramName,paramVal;if(!this.defData){return;}
+var paramArray=this.defData.split("+");for(var prop=0;prop<paramArray.length;prop++){var property=paramArray[prop].split("=");paramName=property[0].toLowerCase();paramVal=property[1];switch(paramName.replace(/\s/gi,"")){case"":break;case"title":this.title=paramVal;break;case"proj":this.projName=paramVal.replace(/\s/gi,"");break;case"units":this.units=paramVal.replace(/\s/gi,"");break;case"datum":this.datumCode=paramVal.replace(/\s/gi,"");break;case"nadgrids":this.nagrids=paramVal.replace(/\s/gi,"");break;case"ellps":this.ellps=paramVal.replace(/\s/gi,"");break;case"a":this.a=parseFloat(paramVal);break;case"b":this.b=parseFloat(paramVal);break;case"rf":this.rf=parseFloat(paramVal);break;case"lat_0":this.lat0=paramVal*Proj4js.common.D2R;break;case"lat_1":this.lat1=paramVal*Proj4js.common.D2R;break;case"lat_2":this.lat2=paramVal*Proj4js.common.D2R;break;case"lat_ts":this.lat_ts=paramVal*Proj4js.common.D2R;break;case"lon_0":this.long0=paramVal*Proj4js.common.D2R;break;case"alpha
 ":this.alpha=parseFloat(paramVal)*Proj4js.common.D2R;break;case"lonc":this.longc=paramVal*Proj4js.common.D2R;break;case"x_0":this.x0=parseFloat(paramVal);break;case"y_0":this.y0=parseFloat(paramVal);break;case"k_0":this.k0=parseFloat(paramVal);break;case"k":this.k0=parseFloat(paramVal);break;case"r_a":this.R_A=true;break;case"zone":this.zone=parseInt(paramVal);break;case"south":this.utmSouth=true;break;case"towgs84":this.datum_params=paramVal.split(",");break;case"to_meter":this.to_meter=parseFloat(paramVal);break;case"from_greenwich":this.from_greenwich=paramVal*Proj4js.common.D2R;break;case"pm":paramVal=paramVal.replace(/\s/gi,"");this.from_greenwich=Proj4js.PrimeMeridian[paramVal]?Proj4js.PrimeMeridian[paramVal]:parseFloat(paramVal);this.from_greenwich*=Proj4js.common.D2R;break;case"no_defs":break;default:}}
+this.deriveConstants();},deriveConstants:function(){if(this.nagrids=='@null')this.datumCode='none';if(this.datumCode&&this.datumCode!='none'){var datumDef=Proj4js.Datum[this.datumCode];if(datumDef){this.datum_params=datumDef.towgs84?datumDef.towgs84.split(','):null;this.ellps=datumDef.ellipse;this.datumName=datumDef.datumName?datumDef.datumName:this.datumCode;}}
+if(!this.a){var ellipse=Proj4js.Ellipsoid[this.ellps]?Proj4js.Ellipsoid[this.ellps]:Proj4js.Ellipsoid['WGS84'];Proj4js.extend(this,ellipse);}
+if(this.rf&&!this.b)this.b=(1.0-1.0/this.rf)*this.a;if(Math.abs(this.a-this.b)<Proj4js.common.EPSLN){this.sphere=true;this.b=this.a;}
+this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=(this.a2-this.b2)/this.a2;this.e=Math.sqrt(this.es);if(this.R_A){this.a*=1.-this.es*(Proj4js.common.SIXTH+this.es*(Proj4js.common.RA4+this.es*Proj4js.common.RA6));this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=0.;}
+this.ep2=(this.a2-this.b2)/this.b2;if(!this.k0)this.k0=1.0;this.datum=new Proj4js.datum(this);}});Proj4js.Proj.longlat={init:function(){},forward:function(pt){return pt;},inverse:function(pt){return pt;}};Proj4js.Proj.identity=Proj4js.Proj.longlat;Proj4js.defs={'WGS84':"+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4326':"+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4269':"+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",'EPSG:3785':"+title= Google Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"};Proj4js.defs['GOOGLE']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:900913']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:102113']=Proj4js.defs['EPSG:3785'];Proj4js.common={PI:3.141592653589793238,HALF_PI:1.570796326794896619,TWO_PI:6.28318
 5307179586477,FORTPI:0.78539816339744833,R2D:57.29577951308232088,D2R:0.01745329251994329577,SEC_TO_RAD:4.84813681109535993589914102357e-6,EPSLN:1.0e-10,MAX_ITER:20,COS_67P5:0.38268343236508977,AD_C:1.0026000,PJD_UNKNOWN:0,PJD_3PARAM:1,PJD_7PARAM:2,PJD_GRIDSHIFT:3,PJD_WGS84:4,PJD_NODATUM:5,SRS_WGS84_SEMIMAJOR:6378137.0,SIXTH:.1666666666666666667,RA4:.04722222222222222222,RA6:.02215608465608465608,RV4:.06944444444444444444,RV6:.04243827160493827160,msfnz:function(eccent,sinphi,cosphi){var con=eccent*sinphi;return cosphi/(Math.sqrt(1.0-con*con));},tsfnz:function(eccent,phi,sinphi){var con=eccent*sinphi;var com=.5*eccent;con=Math.pow(((1.0-con)/(1.0+con)),com);return(Math.tan(.5*(this.HALF_PI-phi))/con);},phi2z:function(eccent,ts){var eccnth=.5*eccent;var con,dphi;var phi=this.HALF_PI-2*Math.atan(ts);for(var i=0;i<=15;i++){con=eccent*Math.sin(phi);dphi=this.HALF_PI-2*Math.atan(ts*(Math.pow(((1.0-con)/(1.0+con)),eccnth)))-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001)return phi;}
+alert("phi2z has NoConvergence");return(-9999);},qsfnz:function(eccent,sinphi){var con;if(eccent>1.0e-7){con=eccent*sinphi;return((1.0-eccent*eccent)*(sinphi/(1.0-con*con)-(.5/eccent)*Math.log((1.0-con)/(1.0+con))));}else{return(2.0*sinphi);}},asinz:function(x){if(Math.abs(x)>1.0){x=(x>1.0)?1.0:-1.0;}
+return Math.asin(x);},e0fn:function(x){return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},e1fn:function(x){return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},e2fn:function(x){return(0.05859375*x*x*(1.0+0.75*x));},e3fn:function(x){return(x*x*x*(35.0/3072.0));},mlfn:function(e0,e1,e2,e3,phi){return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},srat:function(esinp,exp){return(Math.pow((1.0-esinp)/(1.0+esinp),exp));},sign:function(x){if(x<0.0)return(-1);else return(1);},adjust_lon:function(x){x=(Math.abs(x)<this.PI)?x:(x-(this.sign(x)*this.TWO_PI));return x;},adjust_lat:function(x){x=(Math.abs(x)<this.HALF_PI)?x:(x-(this.sign(x)*this.PI));return x;},latiso:function(eccent,phi,sinphi){if(Math.abs(phi)>this.HALF_PI)return+Number.NaN;if(phi==this.HALF_PI)return Number.POSITIVE_INFINITY;if(phi==-1.0*this.HALF_PI)return-1.0*Number.POSITIVE_INFINITY;var con=eccent*sinphi;return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;},fL:
 function(x,L){return 2.0*Math.atan(x*Math.exp(L))-this.HALF_PI;},invlatiso:function(eccent,ts){var phi=this.fL(1.0,ts);var Iphi=0.0;var con=0.0;do{Iphi=phi;con=eccent*Math.sin(Iphi);phi=this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)}while(Math.abs(phi-Iphi)>1.0e-12);return phi;},sinh:function(x)
+{var r=Math.exp(x);r=(r-1.0/r)/2.0;return r;},cosh:function(x)
+{var r=Math.exp(x);r=(r+1.0/r)/2.0;return r;},tanh:function(x)
+{var r=Math.exp(x);r=(r-1.0/r)/(r+1.0/r);return r;},asinh:function(x)
+{var s=(x>=0?1.0:-1.0);return s*(Math.log(Math.abs(x)+Math.sqrt(x*x+1.0)));},acosh:function(x)
+{return 2.0*Math.log(Math.sqrt((x+1.0)/2.0)+Math.sqrt((x-1.0)/2.0));},atanh:function(x)
+{return Math.log((x-1.0)/(x+1.0))/2.0;},gN:function(a,e,sinphi)
+{var temp=e*sinphi;return a/Math.sqrt(1.0-temp*temp);}};Proj4js.datum=Proj4js.Class({initialize:function(proj){this.datum_type=Proj4js.common.PJD_WGS84;if(proj.datumCode&&proj.datumCode=='none'){this.datum_type=Proj4js.common.PJD_NODATUM;}
+if(proj&&proj.datum_params){for(var i=0;i<proj.datum_params.length;i++){proj.datum_params[i]=parseFloat(proj.datum_params[i]);}
+if(proj.datum_params[0]!=0||proj.datum_params[1]!=0||proj.datum_params[2]!=0){this.datum_type=Proj4js.common.PJD_3PARAM;}
+if(proj.datum_params.length>3){if(proj.datum_params[3]!=0||proj.datum_params[4]!=0||proj.datum_params[5]!=0||proj.datum_params[6]!=0){this.datum_type=Proj4js.common.PJD_7PARAM;proj.datum_params[3]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[4]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[5]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[6]=(proj.datum_params[6]/1000000.0)+1.0;}}}
+if(proj){this.a=proj.a;this.b=proj.b;this.es=proj.es;this.ep2=proj.ep2;this.datum_params=proj.datum_params;}},compare_datums:function(dest){if(this.datum_type!=dest.datum_type){return false;}else if(this.a!=dest.a||Math.abs(this.es-dest.es)>0.000000000050){return false;}else if(this.datum_type==Proj4js.common.PJD_3PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]);}else if(this.datum_type==Proj4js.common.PJD_7PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]&&this.datum_params[3]==dest.datum_params[3]&&this.datum_params[4]==dest.datum_params[4]&&this.datum_params[5]==dest.datum_params[5]&&this.datum_params[6]==dest.datum_params[6]);}else if(this.datum_type==Proj4js.common.PJD_GRIDSHIFT){return strcmp(pj_param(this.params,"snadgrids").s,pj_param(dest.params,"snadgrids").s)==0;}else{return 
 true;}},geodetic_to_geocentric:function(p){var Longitude=p.x;var Latitude=p.y;var Height=p.z?p.z:0;var X;var Y;var Z;var Error_Code=0;var Rn;var Sin_Lat;var Sin2_Lat;var Cos_Lat;if(Latitude<-Proj4js.common.HALF_PI&&Latitude>-1.001*Proj4js.common.HALF_PI){Latitude=-Proj4js.common.HALF_PI;}else if(Latitude>Proj4js.common.HALF_PI&&Latitude<1.001*Proj4js.common.HALF_PI){Latitude=Proj4js.common.HALF_PI;}else if((Latitude<-Proj4js.common.HALF_PI)||(Latitude>Proj4js.common.HALF_PI)){Proj4js.reportError('geocent:lat out of range:'+Latitude);return null;}
+if(Longitude>Proj4js.common.PI)Longitude-=(2*Proj4js.common.PI);Sin_Lat=Math.sin(Latitude);Cos_Lat=Math.cos(Latitude);Sin2_Lat=Sin_Lat*Sin_Lat;Rn=this.a/(Math.sqrt(1.0e0-this.es*Sin2_Lat));X=(Rn+Height)*Cos_Lat*Math.cos(Longitude);Y=(Rn+Height)*Cos_Lat*Math.sin(Longitude);Z=((Rn*(1-this.es))+Height)*Sin_Lat;p.x=X;p.y=Y;p.z=Z;return Error_Code;},geocentric_to_geodetic:function(p){var genau=1.E-12;var genau2=(genau*genau);var maxiter=30;var P;var RR;var CT;var ST;var RX;var RK;var RN;var CPHI0;var SPHI0;var CPHI;var SPHI;var SDPHI;var At_Pole;var iter;var X=p.x;var Y=p.y;var Z=p.z?p.z:0.0;var Longitude;var Latitude;var Height;At_Pole=false;P=Math.sqrt(X*X+Y*Y);RR=Math.sqrt(X*X+Y*Y+Z*Z);if(P/this.a<genau){At_Pole=true;Longitude=0.0;if(RR/this.a<genau){Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}else{Longitude=Math.atan2(Y,X);}
+CT=Z/RR;ST=P/RR;RX=1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);CPHI0=ST*(1.0-this.es)*RX;SPHI0=CT*RX;iter=0;do
+{iter++;RN=this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);Height=P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);RK=this.es*RN/(RN+Height);RX=1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);CPHI=ST*(1.0-RK)*RX;SPHI=CT*RX;SDPHI=SPHI*CPHI0-CPHI*SPHI0;CPHI0=CPHI;SPHI0=SPHI;}
+while(SDPHI*SDPHI>genau2&&iter<maxiter);Latitude=Math.atan(SPHI/Math.abs(CPHI));p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_geodetic_noniter:function(p){var X=p.x;var Y=p.y;var Z=p.z?p.z:0;var Longitude;var Latitude;var Height;var W;var W2;var T0;var T1;var S0;var S1;var Sin_B0;var Sin3_B0;var Cos_B0;var Sin_p1;var Cos_p1;var Rn;var Sum;var At_Pole;X=parseFloat(X);Y=parseFloat(Y);Z=parseFloat(Z);At_Pole=false;if(X!=0.0)
+{Longitude=Math.atan2(Y,X);}
+else
+{if(Y>0)
+{Longitude=Proj4js.common.HALF_PI;}
+else if(Y<0)
+{Longitude=-Proj4js.common.HALF_PI;}
+else
+{At_Pole=true;Longitude=0.0;if(Z>0.0)
+{Latitude=Proj4js.common.HALF_PI;}
+else if(Z<0.0)
+{Latitude=-Proj4js.common.HALF_PI;}
+else
+{Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}}
+W2=X*X+Y*Y;W=Math.sqrt(W2);T0=Z*Proj4js.common.AD_C;S0=Math.sqrt(T0*T0+W2);Sin_B0=T0/S0;Cos_B0=W/S0;Sin3_B0=Sin_B0*Sin_B0*Sin_B0;T1=Z+this.b*this.ep2*Sin3_B0;Sum=W-this.a*this.es*Cos_B0*Cos_B0*Cos_B0;S1=Math.sqrt(T1*T1+Sum*Sum);Sin_p1=T1/S1;Cos_p1=Sum/S1;Rn=this.a/Math.sqrt(1.0-this.es*Sin_p1*Sin_p1);if(Cos_p1>=Proj4js.common.COS_67P5)
+{Height=W/Cos_p1-Rn;}
+else if(Cos_p1<=-Proj4js.common.COS_67P5)
+{Height=W/-Cos_p1-Rn;}
+else
+{Height=Z/Sin_p1+Rn*(this.es-1.0);}
+if(At_Pole==false)
+{Latitude=Math.atan(Sin_p1/Cos_p1);}
+p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
+{p.x+=this.datum_params[0];p.y+=this.datum_params[1];p.z+=this.datum_params[2];}
+else if(this.datum_type==Proj4js.common.PJD_7PARAM)
+{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_out=M_BF*(p.x-Rz_BF*p.y+Ry_BF*p.z)+Dx_BF;var y_out=M_BF*(Rz_BF*p.x+p.y-Rx_BF*p.z)+Dy_BF;var z_out=M_BF*(-Ry_BF*p.x+Rx_BF*p.y+p.z)+Dz_BF;p.x=x_out;p.y=y_out;p.z=z_out;}},geocentric_from_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
+{p.x-=this.datum_params[0];p.y-=this.datum_params[1];p.z-=this.datum_params[2];}
+else if(this.datum_type==Proj4js.common.PJD_7PARAM)
+{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_tmp=(p.x-Dx_BF)/M_BF;var y_tmp=(p.y-Dy_BF)/M_BF;var z_tmp=(p.z-Dz_BF)/M_BF;p.x=x_tmp+Rz_BF*y_tmp-Ry_BF*z_tmp;p.y=-Rz_BF*x_tmp+y_tmp+Rx_BF*z_tmp;p.z=Ry_BF*x_tmp-Rx_BF*y_tmp+z_tmp;}}});Proj4js.Point=Proj4js.Class({initialize:function(x,y,z){if(typeof x=='object'){this.x=x[0];this.y=x[1];this.z=x[2]||0.0;}else if(typeof x=='string'){var coords=x.split(',');this.x=parseFloat(coords[0]);this.y=parseFloat(coords[1]);this.z=parseFloat(coords[2])||0.0;}else{this.x=x;this.y=y;this.z=z||0.0;}},clone:function(){return new Proj4js.Point(this.x,this.y,this.z);},toString:function(){return("x="+this.x+",y="+this.y);},toShortString:function(){return(this.x+", "+this.y);}});Proj4js.PrimeMeridian={"greenwich":0.0,"lisbon":-9.131906111111,"paris":2.337229166667,"bogota":-74.
 080916666667,"madrid":-3.687938888889,"rome":12.452333333333,"bern":7.439583333333,"jakarta":106.807719444444,"ferro":-17.666666666667,"brussels":4.367975,"stockholm":18.058277777778,"athens":23.7163375,"oslo":10.722916666667};Proj4js.Ellipsoid={"MERIT":{a:6378137.0,rf:298.257,ellipseName:"MERIT 1983"},"SGS85":{a:6378136.0,rf:298.257,ellipseName:"Soviet Geodetic System 85"},"GRS80":{a:6378137.0,rf:298.257222101,ellipseName:"GRS 1980(IUGG, 1980)"},"IAU76":{a:6378140.0,rf:298.257,ellipseName:"IAU 1976"},"airy":{a:6377563.396,b:6356256.910,ellipseName:"Airy 1830"},"APL4.":{a:6378137,rf:298.25,ellipseName:"Appl. Physics. 1965"},"NWL9D":{a:6378145.0,rf:298.25,ellipseName:"Naval Weapons Lab., 1965"},"mod_airy":{a:6377340.189,b:6356034.446,ellipseName:"Modified Airy"},"andrae":{a:6377104.43,rf:300.0,ellipseName:"Andrae 1876 (Den., Iclnd.)"},"aust_SA":{a:6378160.0,rf:298.25,ellipseName:"Australian Natl & S. Amer. 1969"},"GRS67":{a:6378160.0,rf:298.2471674270,ellipseName:"GRS 67(IUGG
  1967)"},"bessel":{a:6377397.155,rf:299.1528128,ellipseName:"Bessel 1841"},"bess_nam":{a:6377483.865,rf:299.1528128,ellipseName:"Bessel 1841 (Namibia)"},"clrk66":{a:6378206.4,b:6356583.8,ellipseName:"Clarke 1866"},"clrk80":{a:6378249.145,rf:293.4663,ellipseName:"Clarke 1880 mod."},"CPM":{a:6375738.7,rf:334.29,ellipseName:"Comm. des Poids et Mesures 1799"},"delmbr":{a:6376428.0,rf:311.5,ellipseName:"Delambre 1810 (Belgium)"},"engelis":{a:6378136.05,rf:298.2566,ellipseName:"Engelis 1985"},"evrst30":{a:6377276.345,rf:300.8017,ellipseName:"Everest 1830"},"evrst48":{a:6377304.063,rf:300.8017,ellipseName:"Everest 1948"},"evrst56":{a:6377301.243,rf:300.8017,ellipseName:"Everest 1956"},"evrst69":{a:6377295.664,rf:300.8017,ellipseName:"Everest 1969"},"evrstSS":{a:6377298.556,rf:300.8017,ellipseName:"Everest (Sabah & Sarawak)"},"fschr60":{a:6378166.0,rf:298.3,ellipseName:"Fischer (Mercury Datum) 1960"},"fschr60m":{a:6378155.0,rf:298.3,ellipseName:"Fischer 1960"},"fschr68":{a:6378150.0
 ,rf:298.3,ellipseName:"Fischer 1968"},"helmert":{a:6378200.0,rf:298.3,ellipseName:"Helmert 1906"},"hough":{a:6378270.0,rf:297.0,ellipseName:"Hough"},"intl":{a:6378388.0,rf:297.0,ellipseName:"International 1909 (Hayford)"},"kaula":{a:6378163.0,rf:298.24,ellipseName:"Kaula 1961"},"lerch":{a:6378139.0,rf:298.257,ellipseName:"Lerch 1979"},"mprts":{a:6397300.0,rf:191.0,ellipseName:"Maupertius 1738"},"new_intl":{a:6378157.5,b:6356772.2,ellipseName:"New International 1967"},"plessis":{a:6376523.0,rf:6355863.0,ellipseName:"Plessis 1817 (France)"},"krass":{a:6378245.0,rf:298.3,ellipseName:"Krassovsky, 1942"},"SEasia":{a:6378155.0,b:6356773.3205,ellipseName:"Southeast Asia"},"walbeck":{a:6376896.0,b:6355834.8467,ellipseName:"Walbeck"},"WGS60":{a:6378165.0,rf:298.3,ellipseName:"WGS 60"},"WGS66":{a:6378145.0,rf:298.25,ellipseName:"WGS 66"},"WGS72":{a:6378135.0,rf:298.26,ellipseName:"WGS 72"},"WGS84":{a:6378137.0,rf:298.257223563,ellipseName:"WGS 84"},"sphere":{a:6370997.0,b:6370997.0,el
 lipseName:"Normal Sphere (r=6370997)"}};Proj4js.Datum={"WGS84":{towgs84:"0,0,0",ellipse:"WGS84",datumName:"WGS84"},"GGRS87":{towgs84:"-199.87,74.79,246.62",ellipse:"GRS80",datumName:"Greek_Geodetic_Reference_System_1987"},"NAD83":{towgs84:"0,0,0",ellipse:"GRS80",datumName:"North_American_Datum_1983"},"NAD27":{nadgrids:"@conus, at alaska, at ntv2_0.gsb, at ntv1_can.dat",ellipse:"clrk66",datumName:"North_American_Datum_1927"},"potsdam":{towgs84:"606.0,23.0,413.0",ellipse:"bessel",datumName:"Potsdam Rauenberg 1950 DHDN"},"carthage":{towgs84:"-263.0,6.0,431.0",ellipse:"clark80",datumName:"Carthage 1934 Tunisia"},"hermannskogel":{towgs84:"653.0,-212.0,449.0",ellipse:"bessel",datumName:"Hermannskogel"},"ire65":{towgs84:"482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15",ellipse:"mod_airy",datumName:"Ireland 1965"},"nzgd49":{towgs84:"59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993",ellipse:"intl",datumName:"New Zealand Geodetic Datum 1949"},"OSGB36":{towgs84:"446.448,-125.157,542.060,0.1502,0.2
 470,0.8421,-20.4894",ellipse:"airy",datumName:"Airy 1830"}};Proj4js.WGS84=new Proj4js.Proj('WGS84');Proj4js.Datum['OSB36']=Proj4js.Datum['OSGB36'];Proj4js.wktProjections={"Lambert Tangential Conformal Conic Projection":"lcc","Mercator":"merc","Transverse_Mercator":"tmerc","Transverse Mercator":"tmerc","Lambert Azimuthal Equal Area":"laea","Universal Transverse Mercator System":"utm"};Proj4js.Proj.aea={init:function(){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("aeaInitEqualLatitudes");return;}
+this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e3=Math.sqrt(this.es);this.sin_po=Math.sin(this.lat1);this.cos_po=Math.cos(this.lat1);this.t1=this.sin_po;this.con=this.sin_po;this.ms1=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs1=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat2);this.cos_po=Math.cos(this.lat2);this.t2=this.sin_po;this.ms2=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs2=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat0);this.cos_po=Math.cos(this.lat0);this.t3=this.sin_po;this.qs0=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);if(Math.abs(this.lat1-this.lat2)>Proj4js.common.EPSLN){this.ns0=(this.ms1*this.ms1-this.ms2*this.ms2)/(this.qs2-this.qs1);}else{this.ns0=this.con;}
+this.c=this.ms1*this.ms1+this.ns0*this.qs1;this.rh=this.a*Math.sqrt(this.c-this.ns0*this.qs0)/this.ns0;},forward:function(p){var lon=p.x;var lat=p.y;this.sin_phi=Math.sin(lat);this.cos_phi=Math.cos(lat);var qs=Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);var rh1=this.a*Math.sqrt(this.c-this.ns0*qs)/this.ns0;var theta=this.ns0*Proj4js.common.adjust_lon(lon-this.long0);var x=rh1*Math.sin(theta)+this.x0;var y=this.rh-rh1*Math.cos(theta)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var rh1,qs,con,theta,lon,lat;p.x-=this.x0;p.y=this.rh-p.y+this.y0;if(this.ns0>=0){rh1=Math.sqrt(p.x*p.x+p.y*p.y);con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
+theta=0.0;if(rh1!=0.0){theta=Math.atan2(con*p.x,con*p.y);}
+con=rh1*this.ns0/this.a;qs=(this.c-con*con)/this.ns0;if(this.e3>=1e-10){con=1-.5*(1.0-this.es)*Math.log((1.0-this.e3)/(1.0+this.e3))/this.e3;if(Math.abs(Math.abs(con)-Math.abs(qs))>.0000000001){lat=this.phi1z(this.e3,qs);}else{if(qs>=0){lat=.5*PI;}else{lat=-.5*PI;}}}else{lat=this.phi1z(e3,qs);}
+lon=Proj4js.common.adjust_lon(theta/this.ns0+this.long0);p.x=lon;p.y=lat;return p;},phi1z:function(eccent,qs){var con,com,dphi;var phi=Proj4js.common.asinz(.5*qs);if(eccent<Proj4js.common.EPSLN)return phi;var eccnts=eccent*eccent;for(var i=1;i<=25;i++){sinphi=Math.sin(phi);cosphi=Math.cos(phi);con=eccent*sinphi;com=1.0-con*con;dphi=.5*com*com/cosphi*(qs/(1.0-eccnts)-sinphi/com+.5/eccent*Math.log((1.0-con)/(1.0+con)));phi=phi+dphi;if(Math.abs(dphi)<=1e-7)return phi;}
+Proj4js.reportError("aea:phi1z:Convergence error");return null;}};Proj4js.Proj.sterea={dependsOn:'gauss',init:function(){Proj4js.Proj['gauss'].init.apply(this);if(!this.rc){Proj4js.reportError("sterea:init:E_ERROR_0");return;}
+this.sinc0=Math.sin(this.phic0);this.cosc0=Math.cos(this.phic0);this.R2=2.0*this.rc;if(!this.title)this.title="Oblique Stereographic Alternative";},forward:function(p){p.x=Proj4js.common.adjust_lon(p.x-this.long0);Proj4js.Proj['gauss'].forward.apply(this,[p]);sinc=Math.sin(p.y);cosc=Math.cos(p.y);cosl=Math.cos(p.x);k=this.k0*this.R2/(1.0+this.sinc0*sinc+this.cosc0*cosc*cosl);p.x=k*cosc*Math.sin(p.x);p.y=k*(this.cosc0*sinc-this.sinc0*cosc*cosl);p.x=this.a*p.x+this.x0;p.y=this.a*p.y+this.y0;return p;},inverse:function(p){var lon,lat;p.x=(p.x-this.x0)/this.a;p.y=(p.y-this.y0)/this.a;p.x/=this.k0;p.y/=this.k0;if((rho=Math.sqrt(p.x*p.x+p.y*p.y))){c=2.0*Math.atan2(rho,this.R2);sinc=Math.sin(c);cosc=Math.cos(c);lat=Math.asin(cosc*this.sinc0+p.y*sinc*this.cosc0/rho);lon=Math.atan2(p.x*sinc,rho*this.cosc0*cosc-p.y*this.sinc0*sinc);}else{lat=this.phic0;lon=0.;}
+p.x=lon;p.y=lat;Proj4js.Proj['gauss'].inverse.apply(this,[p]);p.x=Proj4js.common.adjust_lon(p.x+this.long0);return p;}};function phi4z(eccent,e0,e1,e2,e3,a,b,c,phi){var sinphi,sin2ph,tanph,ml,mlp,con1,con2,con3,dphi,i;phi=a;for(i=1;i<=15;i++){sinphi=Math.sin(phi);tanphi=Math.tan(phi);c=tanphi*Math.sqrt(1.0-eccent*sinphi*sinphi);sin2ph=Math.sin(2.0*phi);ml=e0*phi-e1*sin2ph+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi);mlp=e0-2.0*e1*Math.cos(2.0*phi)+4.0*e2*Math.cos(4.0*phi)-6.0*e3*Math.cos(6.0*phi);con1=2.0*ml+c*(ml*ml+b)-2.0*a*(c*ml+1.0);con2=eccent*sin2ph*(ml*ml+b-2.0*a*ml)/(2.0*c);con3=2.0*(a-ml)*(c*mlp-2.0/sin2ph)-2.0*mlp;dphi=con1/(con2+con3);phi+=dphi;if(Math.abs(dphi)<=.0000000001)return(phi);}
+Proj4js.reportError("phi4z: No convergence");return null;}
+function e4fn(x){var con,com;con=1.0+x;com=1.0-x;return(Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));}
+Proj4js.Proj.poly={init:function(){var temp;if(this.lat0=0)this.lat0=90;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var sinphi,cosphi;var al;var c;var con,ml;var ms;var x,y;var lon=p.x;var lat=p.y;con=Proj4js.common.adjust_lon(lon-this.long0);if(Math.abs(lat)<=.0000001){x=this.x0+this.a*con;y=this.y0-this.a*this.ml0;}else{sinphi=Math.sin(lat);cosphi=Math.cos(lat);ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);ms=Proj4js.common.msfnz(this.e,sinphi,cosphi);con=sinphi;x=this.x0+this.a*ms*Math.sin(con)/sinphi;y=this.y0+this.a*(ml-this.ml0+ms*(1.0-Math.cos(con))/sinphi);}
+p.x=x;p.y=y;return p;},inverse:function(p){var sin_phi,cos_phi;var al;var b;var c;var con,ml;var iflg;var lon,lat;p.x-=this.x0;p.y-=this.y0;al=this.ml0+p.y/this.a;iflg=0;if(Math.abs(al)<=.0000001){lon=p.x/this.a+this.long0;lat=0.0;}else{b=al*al+(p.x/this.a)*(p.x/this.a);iflg=phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);if(iflg!=1)return(iflg);lon=Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x*c/this.a)/Math.sin(lat))+this.long0);}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.equi={init:function(){if(!this.x0)this.x0=0;if(!this.y0)this.y0=0;if(!this.lat0)this.lat0=0;if(!this.long0)this.long0=0;},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon*Math.cos(this.lat0);var y=this.y0+this.a*lat;this.t1=x;this.t2=Math.cos(this.lat0);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lat=p.y/this.a;if(Math.abs(lat)>Proj4js.common.HALF_PI){Proj4js.reportError("equi:Inv:DataError");}
+var lon=Proj4js.common.adjust_lon(this.long0+p.x/(this.a*Math.cos(this.lat0)));p.x=lon;p.y=lat;}};Proj4js.Proj.merc={init:function(){if(this.lat_ts){if(this.sphere){this.k0=Math.cos(this.lat_ts);}else{this.k0=Proj4js.common.msfnz(this.es,Math.sin(this.lat_ts),Math.cos(this.lat_ts));}}},forward:function(p){var lon=p.x;var lat=p.y;if(lat*Proj4js.common.R2D>90.0&&lat*Proj4js.common.R2D<-90.0&&lon*Proj4js.common.R2D>180.0&&lon*Proj4js.common.R2D<-180.0){Proj4js.reportError("merc:forward: llInputOutOfRange: "+lon+" : "+lat);return null;}
+var x,y;if(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN){Proj4js.reportError("merc:forward: ll2mAtPoles");return null;}else{if(this.sphere){x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0+this.a*this.k0*Math.log(Math.tan(Proj4js.common.FORTPI+0.5*lat));}else{var sinphi=Math.sin(lat);var ts=Proj4js.common.tsfnz(this.e,lat,sinphi);x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0-this.a*this.k0*Math.log(ts);}
+p.x=x;p.y=y;return p;}},inverse:function(p){var x=p.x-this.x0;var y=p.y-this.y0;var lon,lat;if(this.sphere){lat=Proj4js.common.HALF_PI-2.0*Math.atan(Math.exp(-y/this.a*this.k0));}else{var ts=Math.exp(-y/(this.a*this.k0));lat=Proj4js.common.phi2z(this.e,ts);if(lat==-9999){Proj4js.reportError("merc:inverse: lat = -9999");return null;}}
+lon=Proj4js.common.adjust_lon(this.long0+x/(this.a*this.k0));p.x=lon;p.y=lat;return p;}};Proj4js.Proj.utm={dependsOn:'tmerc',init:function(){if(!this.zone){Proj4js.reportError("utm:init: zone must be specified for UTM");return;}
+this.lat0=0.0;this.long0=((6*Math.abs(this.zone))-183)*Proj4js.common.D2R;this.x0=500000.0;this.y0=this.utmSouth?10000000.0:0.0;this.k0=0.9996;Proj4js.Proj['tmerc'].init.apply(this);this.forward=Proj4js.Proj['tmerc'].forward;this.inverse=Proj4js.Proj['tmerc'].inverse;}};Proj4js.Proj.eqdc={init:function(){if(!this.mode)this.mode=0;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.sinphi=Math.sin(this.lat1);this.cosphi=Math.cos(this.lat1);this.ms1=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml1=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat1);if(this.mode!=0){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("eqdc:Init:EqualLatitudes");}
+this.sinphi=Math.sin(this.lat2);this.cosphi=Math.cos(this.lat2);this.ms2=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml2=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat2);if(Math.abs(this.lat1-this.lat2)>=Proj4js.common.EPSLN){this.ns=(this.ms1-this.ms2)/(this.ml2-this.ml1);}else{this.ns=this.sinphi;}}else{this.ns=this.sinphi;}
+this.g=this.ml1+this.ms1/this.ns;this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);this.rh=this.a*(this.g-this.ml0);},forward:function(p){var lon=p.x;var lat=p.y;var ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);var rh1=this.a*(this.g-ml);var theta=this.ns*Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+rh1*Math.sin(theta);var y=this.y0+this.rh-rh1*Math.cos(theta);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y=this.rh-p.y+this.y0;var con,rh1;if(this.ns>=0){var rh1=Math.sqrt(p.x*p.x+p.y*p.y);var con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
+var theta=0.0;if(rh1!=0.0)theta=Math.atan2(con*p.x,con*p.y);var ml=this.g-rh1/this.a;var lat=this.phi3z(ml,this.e0,this.e1,this.e2,this.e3);var lon=Proj4js.common.adjust_lon(this.long0+theta/this.ns);p.x=lon;p.y=lat;return p;},phi3z:function(ml,e0,e1,e2,e3){var phi;var dphi;phi=ml;for(var i=0;i<15;i++){dphi=(ml+e1*Math.sin(2.0*phi)-e2*Math.sin(4.0*phi)+e3*Math.sin(6.0*phi))/e0-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001){return phi;}}
+Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");return null;}};Proj4js.Proj.tmerc={init:function(){this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var lon=p.x;var lat=p.y;var delta_lon=Proj4js.common.adjust_lon(lon-this.long0);var con;var x,y;var sin_phi=Math.sin(lat);var cos_phi=Math.cos(lat);if(this.sphere){var b=cos_phi*Math.sin(delta_lon);if((Math.abs(Math.abs(b)-1.0))<.0000000001){Proj4js.reportError("tmerc:forward: Point projects into infinity");return(93);}else{x=.5*this.a*this.k0*Math.log((1.0+b)/(1.0-b));con=Math.acos(cos_phi*Math.cos(delta_lon)/Math.sqrt(1.0-b*b));if(lat<0)con=-con;y=this.a*this.k0*(con-this.lat0);}}else{var al=cos_phi*delta_lon;var als=Math.pow(al,2);var c=this.ep2*Math.pow(cos_phi,2);var tq=Math.tan(lat);var t=Math.
 pow(tq,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var ml=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);x=this.k0*n*al*(1.0+als/6.0*(1.0-t+c+als/20.0*(5.0-18.0*t+Math.pow(t,2)+72.0*c-58.0*this.ep2)))+this.x0;y=this.k0*(ml-this.ml0+n*tq*(als*(0.5+als/24.0*(5.0-t+9.0*c+4.0*Math.pow(c,2)+als/30.0*(61.0-58.0*t+Math.pow(t,2)+600.0*c-330.0*this.ep2)))))+this.y0;}
+p.x=x;p.y=y;return p;},inverse:function(p){var con,phi;var delta_phi;var i;var max_iter=6;var lat,lon;if(this.sphere){var f=Math.exp(p.x/(this.a*this.k0));var g=.5*(f-1/f);var temp=this.lat0+p.y/(this.a*this.k0);var h=Math.cos(temp);con=Math.sqrt((1.0-h*h)/(1.0+g*g));lat=Proj4js.common.asinz(con);if(temp<0)
+lat=-lat;if((g==0)&&(h==0)){lon=this.long0;}else{lon=Proj4js.common.adjust_lon(Math.atan2(g,h)+this.long0);}}else{var x=p.x-this.x0;var y=p.y-this.y0;con=(this.ml0+y/this.k0)/this.a;phi=con;for(i=0;true;i++){delta_phi=((con+this.e1*Math.sin(2.0*phi)-this.e2*Math.sin(4.0*phi)+this.e3*Math.sin(6.0*phi))/this.e0)-phi;phi+=delta_phi;if(Math.abs(delta_phi)<=Proj4js.common.EPSLN)break;if(i>=max_iter){Proj4js.reportError("tmerc:inverse: Latitude failed to converge");return(95);}}
+if(Math.abs(phi)<Proj4js.common.HALF_PI){var sin_phi=Math.sin(phi);var cos_phi=Math.cos(phi);var tan_phi=Math.tan(phi);var c=this.ep2*Math.pow(cos_phi,2);var cs=Math.pow(c,2);var t=Math.pow(tan_phi,2);var ts=Math.pow(t,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var r=n*(1.0-this.es)/con;var d=x/(n*this.k0);var ds=Math.pow(d,2);lat=phi-(n*tan_phi*ds/r)*(0.5-ds/24.0*(5.0+3.0*t+10.0*c-4.0*cs-9.0*this.ep2-ds/30.0*(61.0+90.0*t+298.0*c+45.0*ts-252.0*this.ep2-3.0*cs)));lon=Proj4js.common.adjust_lon(this.long0+(d*(1.0-ds/6.0*(1.0+2.0*t+c-ds/20.0*(5.0-2.0*c+28.0*t-3.0*cs+8.0*this.ep2+24.0*ts)))/cos_phi));}else{lat=Proj4js.common.HALF_PI*Proj4js.common.sign(y);lon=this.long0;}}
+p.x=lon;p.y=lat;return p;}};Proj4js.defs["GOOGLE"]="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];Proj4js.Proj.gstmerc={init:function(){var temp=this.b/this.a;this.e=Math.sqrt(1.0-temp*temp);this.lc=this.long0;this.rs=Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));var sinz=Math.sin(this.lat0);var pc=Math.asin(sinz/this.rs);var sinzpc=Math.sin(pc);this.cp=Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);this.n2=this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);this.xs=this.x0;this.ys=this.y0-this.n2*pc;if(!this.title)this.title="Gauss Schreiber transverse mercator";},forward:function(p){var lon=p.x;var lat=p.y;var L=this.rs*(lon-this.lc);var Ls=this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));var lat1=Math.asin(Math.sin(L)/Proj4js.common.cosh(
 Ls));var Ls1=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.xs+(this.n2*Ls1);p.y=this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));return p;},inverse:function(p){var x=p.x;var y=p.y;var L=Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));var lat1=Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));var LC=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.lc+L/this.rs;p.y=Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);return p;}};Proj4js.Proj.ortho={init:function(def){;this.sin_p14=Math.sin(this.lat0);this.cos_p14=Math.cos(this.lat0);},forward:function(p){var sinphi,cosphi;var dlon;var coslon;var ksp;var g;var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslon=Math.cos(dlon);g=this.sin_p14*sinphi+this.cos_p14*cosphi*coslon;ksp=1.0;if((g>0)||(Math.abs(g)<=Proj4js.common.EPSLN)){var x=this.a*ksp*cosphi*Math.sin(dlon);
 var y=this.y0+this.a*ksp*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon);}else{Proj4js.reportError("orthoFwdPointError");}
+p.x=x;p.y=y;return p;},inverse:function(p){var rh;var z;var sinz,cosz;var temp;var con;var lon,lat;p.x-=this.x0;p.y-=this.y0;rh=Math.sqrt(p.x*p.x+p.y*p.y);if(rh>this.a+.0000001){Proj4js.reportError("orthoInvDataError");}
+z=Proj4js.common.asinz(rh/this.a);sinz=Math.sin(z);cosz=Math.cos(z);lon=this.long0;if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.lat0;}
+lat=Proj4js.common.asinz(cosz*this.sin_p14+(p.y*sinz*this.cos_p14)/rh);con=Math.abs(this.lat0)-Proj4js.common.HALF_PI;if(Math.abs(con)<=Proj4js.common.EPSLN){if(this.lat0>=0){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x,-p.y));}else{lon=Proj4js.common.adjust_lon(this.long0-Math.atan2(-p.x,p.y));}}
+con=cosz-this.sin_p14*Math.sin(lat);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.somerc={init:function(){var phy0=this.lat0;this.lambda0=this.long0;var sinPhy0=Math.sin(phy0);var semiMajorAxis=this.a;var invF=this.rf;var flattening=1/invF;var e2=2*flattening-Math.pow(flattening,2);var e=this.e=Math.sqrt(e2);this.R=this.k0*semiMajorAxis*Math.sqrt(1-e2)/(1-e2*Math.pow(sinPhy0,2.0));this.alpha=Math.sqrt(1+e2/(1-e2)*Math.pow(Math.cos(phy0),4.0));this.b0=Math.asin(sinPhy0/this.alpha);this.K=Math.log(Math.tan(Math.PI/4.0+this.b0/2.0))
+-this.alpha*Math.log(Math.tan(Math.PI/4.0+phy0/2.0))
++this.alpha*e/2*Math.log((1+e*sinPhy0)/(1-e*sinPhy0));},forward:function(p){var Sa1=Math.log(Math.tan(Math.PI/4.0-p.y/2.0));var Sa2=this.e/2.0*Math.log((1+this.e*Math.sin(p.y))/(1-this.e*Math.sin(p.y)));var S=-this.alpha*(Sa1+Sa2)+this.K;var b=2.0*(Math.atan(Math.exp(S))-Math.PI/4.0);var I=this.alpha*(p.x-this.lambda0);var rotI=Math.atan(Math.sin(I)/(Math.sin(this.b0)*Math.tan(b)+
+Math.cos(this.b0)*Math.cos(I)));var rotB=Math.asin(Math.cos(this.b0)*Math.sin(b)-
+Math.sin(this.b0)*Math.cos(b)*Math.cos(I));p.y=this.R/2.0*Math.log((1+Math.sin(rotB))/(1-Math.sin(rotB)))
++this.y0;p.x=this.R*rotI+this.x0;return p;},inverse:function(p){var Y=p.x-this.x0;var X=p.y-this.y0;var rotI=Y/this.R;var rotB=2*(Math.atan(Math.exp(X/this.R))-Math.PI/4.0);var b=Math.asin(Math.cos(this.b0)*Math.sin(rotB)
++Math.sin(this.b0)*Math.cos(rotB)*Math.cos(rotI));var I=Math.atan(Math.sin(rotI)/(Math.cos(this.b0)*Math.cos(rotI)-Math.sin(this.b0)*Math.tan(rotB)));var lambda=this.lambda0+I/this.alpha;var S=0.0;var phy=b;var prevPhy=-1000.0;var iteration=0;while(Math.abs(phy-prevPhy)>0.0000001)
+{if(++iteration>20)
+{Proj4js.reportError("omercFwdInfinity");return;}
+S=1.0/this.alpha*(Math.log(Math.tan(Math.PI/4.0+b/2.0))-this.K)
++this.e*Math.log(Math.tan(Math.PI/4.0
++Math.asin(this.e*Math.sin(phy))/2.0));prevPhy=phy;phy=2.0*Math.atan(Math.exp(S))-Math.PI/2.0;}
+p.x=lambda;p.y=phy;return p;}};Proj4js.Proj.stere={ssfn_:function(phit,sinphi,eccen){sinphi*=eccen;return(Math.tan(.5*(Proj4js.common.HALF_PI+phit))*Math.pow((1.-sinphi)/(1.+sinphi),.5*eccen));},TOL:1.e-8,NITER:8,CONV:1.e-10,S_POLE:0,N_POLE:1,OBLIQ:2,EQUIT:3,init:function(){this.phits=this.lat_ts?this.lat_ts:Proj4js.common.HALF_PI;var t=Math.abs(this.lat0);if((Math.abs(t)-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.mode=this.lat0<0.?this.S_POLE:this.N_POLE;}else{this.mode=t>Proj4js.common.EPSLN?this.OBLIQ:this.EQUIT;}
+this.phits=Math.abs(this.phits);if(this.es){var X;switch(this.mode){case this.N_POLE:case this.S_POLE:if(Math.abs(this.phits-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.akm1=2.*this.k0/Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));}else{t=Math.sin(this.phits);this.akm1=Math.cos(this.phits)/Proj4js.common.tsfnz(this.e,this.phits,t);t*=this.e;this.akm1/=Math.sqrt(1.-t*t);}
+break;case this.EQUIT:this.akm1=2.*this.k0;break;case this.OBLIQ:t=Math.sin(this.lat0);X=2.*Math.atan(this.ssfn_(this.lat0,t,this.e))-Proj4js.common.HALF_PI;t*=this.e;this.akm1=2.*this.k0*Math.cos(this.lat0)/Math.sqrt(1.-t*t);this.sinX1=Math.sin(X);this.cosX1=Math.cos(X);break;}}else{switch(this.mode){case this.OBLIQ:this.sinph0=Math.sin(this.lat0);this.cosph0=Math.cos(this.lat0);case this.EQUIT:this.akm1=2.*this.k0;break;case this.S_POLE:case this.N_POLE:this.akm1=Math.abs(this.phits-Proj4js.common.HALF_PI)>=Proj4js.common.EPSLN?Math.cos(this.phits)/Math.tan(Proj4js.common.FORTPI-.5*this.phits):2.*this.k0;break;}}},forward:function(p){var lon=p.x;lon=Proj4js.common.adjust_lon(lon-this.long0);var lat=p.y;var x,y;if(this.sphere){var sinphi,cosphi,coslam,sinlam;sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslam=Math.cos(lon);sinlam=Math.sin(lon);switch(this.mode){case this.EQUIT:y=1.+cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
+y=this.akm1/y;x=y*cosphi*sinlam;y*=sinphi;break;case this.OBLIQ:y=1.+this.sinph0*sinphi+this.cosph0*cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
+y=this.akm1/y;x=y*cosphi*sinlam;y*=this.cosph0*sinphi-this.sinph0*cosphi*coslam;break;case this.N_POLE:coslam=-coslam;lat=-lat;case this.S_POLE:if(Math.abs(lat-Proj4js.common.HALF_PI)<this.TOL){F_ERROR;}
+y=this.akm1*Math.tan(Proj4js.common.FORTPI+.5*lat);x=sinlam*y;y*=coslam;break;}}else{coslam=Math.cos(lon);sinlam=Math.sin(lon);sinphi=Math.sin(lat);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){X=2.*Math.atan(this.ssfn_(lat,sinphi,this.e));sinX=Math.sin(X-Proj4js.common.HALF_PI);cosX=Math.cos(X);}
+switch(this.mode){case this.OBLIQ:A=this.akm1/(this.cosX1*(1.+this.sinX1*sinX+this.cosX1*cosX*coslam));y=A*(this.cosX1*sinX-this.sinX1*cosX*coslam);x=A*cosX;break;case this.EQUIT:A=2.*this.akm1/(1.+cosX*coslam);y=A*sinX;x=A*cosX;break;case this.S_POLE:lat=-lat;coslam=-coslam;sinphi=-sinphi;case this.N_POLE:x=this.akm1*Proj4js.common.tsfnz(this.e,lat,sinphi);y=-x*coslam;break;}
+x=x*sinlam;}
+p.x=x*this.a+this.x0;p.y=y*this.a+this.y0;return p;},inverse:function(p){var x=(p.x-this.x0)/this.a;var y=(p.y-this.y0)/this.a;var lon,lat;var cosphi,sinphi,tp=0.0,phi_l=0.0,rho,halfe=0.0,pi2=0.0;var i;if(this.sphere){var c,rh,sinc,cosc;rh=Math.sqrt(x*x+y*y);c=2.*Math.atan(rh/this.akm1);sinc=Math.sin(c);cosc=Math.cos(c);lon=0.;switch(this.mode){case this.EQUIT:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=0.;}else{lat=Math.asin(y*sinc/rh);}
+if(cosc!=0.||x!=0.)lon=Math.atan2(x*sinc,cosc*rh);break;case this.OBLIQ:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.phi0;}else{lat=Math.asin(cosc*sinph0+y*sinc*cosph0/rh);}
+c=cosc-sinph0*Math.sin(lat);if(c!=0.||x!=0.){lon=Math.atan2(x*sinc*cosph0,c*rh);}
+break;case this.N_POLE:y=-y;case this.S_POLE:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.phi0;}else{lat=Math.asin(this.mode==this.S_POLE?-cosc:cosc);}
+lon=(x==0.&&y==0.)?0.:Math.atan2(x,y);break;}}else{rho=Math.sqrt(x*x+y*y);switch(this.mode){case this.OBLIQ:case this.EQUIT:tp=2.*Math.atan2(rho*this.cosX1,this.akm1);cosphi=Math.cos(tp);sinphi=Math.sin(tp);if(rho==0.0){phi_l=Math.asin(cosphi*this.sinX1);}else{phi_l=Math.asin(cosphi*this.sinX1+(y*sinphi*this.cosX1/rho));}
+tp=Math.tan(.5*(Proj4js.common.HALF_PI+phi_l));x*=sinphi;y=rho*this.cosX1*cosphi-y*this.sinX1*sinphi;pi2=Proj4js.common.HALF_PI;halfe=.5*this.e;break;case this.N_POLE:y=-y;case this.S_POLE:tp=-rho/this.akm1;phi_l=Proj4js.common.HALF_PI-2.*Math.atan(tp);pi2=-Proj4js.common.HALF_PI;halfe=-.5*this.e;break;}
+for(i=this.NITER;i--;phi_l=lat){sinphi=this.e*Math.sin(phi_l);lat=2.*Math.atan(tp*Math.pow((1.+sinphi)/(1.-sinphi),halfe))-pi2;if(Math.abs(phi_l-lat)<this.CONV){if(this.mode==this.S_POLE)lat=-lat;lon=(x==0.&&y==0.)?0.:Math.atan2(x,y);p.x=Proj4js.common.adjust_lon(lon+this.long0);p.y=lat;return p;}}}}};Proj4js.Proj.nzmg={iterations:1,init:function(){this.A=new Array();this.A[1]=+0.6399175073;this.A[2]=-0.1358797613;this.A[3]=+0.063294409;this.A[4]=-0.02526853;this.A[5]=+0.0117879;this.A[6]=-0.0055161;this.A[7]=+0.0026906;this.A[8]=-0.001333;this.A[9]=+0.00067;this.A[10]=-0.00034;this.B_re=new Array();this.B_im=new Array();this.B_re[1]=+0.7557853228;this.B_im[1]=0.0;this.B_re[2]=+0.249204646;this.B_im[2]=+0.003371507;this.B_re[3]=-0.001541739;this.B_im[3]=+0.041058560;this.B_re[4]=-0.10162907;this.B_im[4]=+0.01727609;this.B_re[5]=-0.26623489;this.B_im[5]=-0.36249218;this.B_re[6]=-0.6870983;this.B_im[6]=-1.1651967;this.C_re=new Array();this.C_im=new Array();this.C_re[1]=+1.3231
 270439;this.C_im[1]=0.0;this.C_re[2]=-0.577245789;this.C_im[2]=-0.007809598;this.C_re[3]=+0.508307513;this.C_im[3]=-0.112208952;this.C_re[4]=-0.15094762;this.C_im[4]=+0.18200602;this.C_re[5]=+1.01418179;this.C_im[5]=+1.64497696;this.C_re[6]=+1.9660549;this.C_im[6]=+2.5127645;this.D=new Array();this.D[1]=+1.5627014243;this.D[2]=+0.5185406398;this.D[3]=-0.03333098;this.D[4]=-0.1052906;this.D[5]=-0.0368594;this.D[6]=+0.007317;this.D[7]=+0.01220;this.D[8]=+0.00394;this.D[9]=-0.0013;},forward:function(p){var lon=p.x;var lat=p.y;var delta_lat=lat-this.lat0;var delta_lon=lon-this.long0;var d_phi=delta_lat/Proj4js.common.SEC_TO_RAD*1E-5;var d_lambda=delta_lon;var d_phi_n=1;var d_psi=0;for(n=1;n<=10;n++){d_phi_n=d_phi_n*d_phi;d_psi=d_psi+this.A[n]*d_phi_n;}
+var th_re=d_psi;var th_im=d_lambda;var th_n_re=1;var th_n_im=0;var th_n_re1;var th_n_im1;var z_re=0;var z_im=0;for(n=1;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;z_re=z_re+this.B_re[n]*th_n_re-this.B_im[n]*th_n_im;z_im=z_im+this.B_im[n]*th_n_re+this.B_re[n]*th_n_im;}
+x=(z_im*this.a)+this.x0;y=(z_re*this.a)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var x=p.x;var y=p.y;var delta_x=x-this.x0;var delta_y=y-this.y0;var z_re=delta_y/this.a;var z_im=delta_x/this.a;var z_n_re=1;var z_n_im=0;var z_n_re1;var z_n_im1;var th_re=0;var th_im=0;for(n=1;n<=6;n++){z_n_re1=z_n_re*z_re-z_n_im*z_im;z_n_im1=z_n_im*z_re+z_n_re*z_im;z_n_re=z_n_re1;z_n_im=z_n_im1;th_re=th_re+this.C_re[n]*z_n_re-this.C_im[n]*z_n_im;th_im=th_im+this.C_im[n]*z_n_re+this.C_re[n]*z_n_im;}
+for(i=0;i<this.iterations;i++){var th_n_re=th_re;var th_n_im=th_im;var th_n_re1;var th_n_im1;var num_re=z_re;var num_im=z_im;for(n=2;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;num_re=num_re+(n-1)*(this.B_re[n]*th_n_re-this.B_im[n]*th_n_im);num_im=num_im+(n-1)*(this.B_im[n]*th_n_re+this.B_re[n]*th_n_im);}
+th_n_re=1;th_n_im=0;var den_re=this.B_re[1];var den_im=this.B_im[1];for(n=2;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;den_re=den_re+n*(this.B_re[n]*th_n_re-this.B_im[n]*th_n_im);den_im=den_im+n*(this.B_im[n]*th_n_re+this.B_re[n]*th_n_im);}
+var den2=den_re*den_re+den_im*den_im;th_re=(num_re*den_re+num_im*den_im)/den2;th_im=(num_im*den_re-num_re*den_im)/den2;}
+var d_psi=th_re;var d_lambda=th_im;var d_psi_n=1;var d_phi=0;for(n=1;n<=9;n++){d_psi_n=d_psi_n*d_psi;d_phi=d_phi+this.D[n]*d_psi_n;}
+var lat=this.lat0+(d_phi*Proj4js.common.SEC_TO_RAD*1E5);var lon=this.long0+d_lambda;p.x=lon;p.y=lat;return p;}};Proj4js.Proj.mill={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon;var y=this.y0+this.a*Math.log(Math.tan((Proj4js.common.PI/4.0)+(lat/2.5)))*1.25;p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lon=Proj4js.common.adjust_lon(this.long0+p.x/this.a);var lat=2.5*(Math.atan(Math.exp(0.8*p.y/this.a))-Proj4js.common.PI/4.0);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.gnom={init:function(def){this.sin_p14=Math.sin(this.lat0);this.cos_p14=Math.cos(this.lat0);this.infinity_dist=1000*this.a;this.rc=1;},forward:function(p){var sinphi,cosphi;var dlon;var coslon;var ksp;var g;var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslon=Math.cos(dlon);g=this.sin_p14*sinphi+this.cos_p14*cosphi*coslon;ksp=1.0;if
 ((g>0)||(Math.abs(g)<=Proj4js.common.EPSLN)){x=this.x0+this.a*ksp*cosphi*Math.sin(dlon)/g;y=this.y0+this.a*ksp*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon)/g;}else{Proj4js.reportError("orthoFwdPointError");x=this.x0+this.infinity_dist*cosphi*Math.sin(dlon);y=this.y0+this.infinity_dist*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon);}
+p.x=x;p.y=y;return p;},inverse:function(p){var rh;var z;var sinc,cosc;var c;var lon,lat;p.x=(p.x-this.x0)/this.a;p.y=(p.y-this.y0)/this.a;p.x/=this.k0;p.y/=this.k0;if((rh=Math.sqrt(p.x*p.x+p.y*p.y))){c=Math.atan2(rh,this.rc);sinc=Math.sin(c);cosc=Math.cos(c);lat=Proj4js.common.asinz(cosc*this.sin_p14+(p.y*sinc*this.cos_p14)/rh);lon=Math.atan2(p.x*sinc,rh*this.cos_p14*cosc-p.y*this.sin_p14*sinc);lon=Proj4js.common.adjust_lon(this.long0+lon);}else{lat=this.phic0;lon=0.0;}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.sinu={init:function(){this.R=6370997.0;},forward:function(p){var x,y,delta_lon;var lon=p.x;var lat=p.y;delta_lon=Proj4js.common.adjust_lon(lon-this.long0);x=this.R*delta_lon*Math.cos(lat)+this.x0;y=this.R*lat+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var lat,temp,lon;p.x-=this.x0;p.y-=this.y0;lat=p.y/this.R;if(Math.abs(lat)>Proj4js.common.HALF_PI){Proj4js.reportError("sinu:Inv:DataError");}
+temp=Math.abs(lat)-Proj4js.common.HALF_PI;if(Math.abs(temp)>Proj4js.common.EPSLN){temp=this.long0+p.x/(this.R*Math.cos(lat));lon=Proj4js.common.adjust_lon(temp);}else{lon=this.long0;}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.vandg={init:function(){this.R=6370997.0;},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x,y;if(Math.abs(lat)<=Proj4js.common.EPSLN){x=this.x0+this.R*dlon;y=this.y0;}
+var theta=Proj4js.common.asinz(2.0*Math.abs(lat/Proj4js.common.PI));if((Math.abs(dlon)<=Proj4js.common.EPSLN)||(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN)){x=this.x0;if(lat>=0){y=this.y0+Proj4js.common.PI*this.R*Math.tan(.5*theta);}else{y=this.y0+Proj4js.common.PI*this.R*-Math.tan(.5*theta);}}
+var al=.5*Math.abs((Proj4js.common.PI/dlon)-(dlon/Proj4js.common.PI));var asq=al*al;var sinth=Math.sin(theta);var costh=Math.cos(theta);var g=costh/(sinth+costh-1.0);var gsq=g*g;var m=g*(2.0/sinth-1.0);var msq=m*m;var con=Proj4js.common.PI*this.R*(al*(g-msq)+Math.sqrt(asq*(g-msq)*(g-msq)-(msq+asq)*(gsq-msq)))/(msq+asq);if(dlon<0){con=-con;}
+x=this.x0+con;con=Math.abs(con/(Proj4js.common.PI*this.R));if(lat>=0){y=this.y0+Proj4js.common.PI*this.R*Math.sqrt(1.0-con*con-2.0*al*con);}else{y=this.y0-Proj4js.common.PI*this.R*Math.sqrt(1.0-con*con-2.0*al*con);}
+p.x=x;p.y=y;return p;},inverse:function(p){var dlon;var xx,yy,xys,c1,c2,c3;var al,asq;var a1;var m1;var con;var th1;var d;p.x-=this.x0;p.y-=this.y0;con=Proj4js.common.PI*this.R;xx=p.x/con;yy=p.y/con;xys=xx*xx+yy*yy;c1=-Math.abs(yy)*(1.0+xys);c2=c1-2.0*yy*yy+xx*xx;c3=-2.0*c1+1.0+2.0*yy*yy+xys*xys;d=yy*yy/c3+(2.0*c2*c2*c2/c3/c3/c3-9.0*c1*c2/c3/c3)/27.0;a1=(c1-c2*c2/3.0/c3)/c3;m1=2.0*Math.sqrt(-a1/3.0);con=((3.0*d)/a1)/m1;if(Math.abs(con)>1.0){if(con>=0.0){con=1.0;}else{con=-1.0;}}
+th1=Math.acos(con)/3.0;if(p.y>=0){lat=(-m1*Math.cos(th1+Proj4js.common.PI/3.0)-c2/3.0/c3)*Proj4js.common.PI;}else{lat=-(-m1*Math.cos(th1+Proj4js.common.PI/3.0)-c2/3.0/c3)*Proj4js.common.PI;}
+if(Math.abs(xx)<Proj4js.common.EPSLN){lon=this.long0;}
+lon=Proj4js.common.adjust_lon(this.long0+Proj4js.common.PI*(xys-1.0+Math.sqrt(1.0+2.0*(xx*xx-yy*yy)+xys*xys))/2.0/xx);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.cea={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon*Math.cos(this.lat_ts);var y=this.y0+this.a*Math.sin(lat)/Math.cos(this.lat_ts);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lon=Proj4js.common.adjust_lon(this.long0+(p.x/this.a)/Math.cos(this.lat_ts));var lat=Math.asin((p.y/this.a)*Math.cos(this.lat_ts));p.x=lon;p.y=lat;return p;}};Proj4js.Proj.eqc={init:function(){if(!this.x0)this.x0=0;if(!this.y0)this.y0=0;if(!this.lat0)this.lat0=0;if(!this.long0)this.long0=0;if(!this.lat_ts)this.lat_ts=0;if(!this.title)this.title="Equidistant Cylindrical (Plate Carre)";this.rc=Math.cos(this.lat_ts);},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var dlat=Proj4js.common.
 adjust_lat(lat-this.lat0);p.x=this.x0+(this.a*dlon*this.rc);p.y=this.y0+(this.a*dlat);return p;},inverse:function(p){var x=p.x;var y=p.y;p.x=Proj4js.common.adjust_lon(this.long0+((x-this.x0)/(this.a*this.rc)));p.y=Proj4js.common.adjust_lat(this.lat0+((y-this.y0)/(this.a)));return p;}};Proj4js.Proj.cass={init:function(){if(!this.sphere){this.en=this.pj_enfn(this.es)
+this.m0=this.pj_mlfn(this.lat0,Math.sin(this.lat0),Math.cos(this.lat0),this.en);}},C1:.16666666666666666666,C2:.00833333333333333333,C3:.04166666666666666666,C4:.33333333333333333333,C5:.06666666666666666666,forward:function(p){var x,y;var lam=p.x;var phi=p.y;lam=Proj4js.common.adjust_lon(lam-this.long0);if(this.sphere){x=Math.asin(Math.cos(phi)*Math.sin(lam));y=Math.atan2(Math.tan(phi),Math.cos(lam))-this.phi0;}else{this.n=Math.sin(phi);this.c=Math.cos(phi);y=this.pj_mlfn(phi,this.n,this.c,this.en);this.n=1./Math.sqrt(1.-this.es*this.n*this.n);this.tn=Math.tan(phi);this.t=this.tn*this.tn;this.a1=lam*this.c;this.c*=this.es*this.c/(1-this.es);this.a2=this.a1*this.a1;x=this.n*this.a1*(1.-this.a2*this.t*(this.C1-(8.-this.t+8.*this.c)*this.a2*this.C2));y-=this.m0-this.n*this.tn*this.a2*(.5+(5.-this.t+6.*this.c)*this.a2*this.C3);}
+p.x=this.a*x+this.x0;p.y=this.a*y+this.y0;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var x=p.x/this.a;var y=p.y/this.a;if(this.sphere){this.dd=y+this.lat0;phi=Math.asin(Math.sin(this.dd)*Math.cos(x));lam=Math.atan2(Math.tan(x),Math.cos(this.dd));}else{ph1=this.pj_inv_mlfn(this.m0+y,this.es,this.en);this.tn=Math.tan(ph1);this.t=this.tn*this.tn;this.n=Math.sin(ph1);this.r=1./(1.-this.es*this.n*this.n);this.n=Math.sqrt(this.r);this.r*=(1.-this.es)*this.n;this.dd=x/this.n;this.d2=this.dd*this.dd;phi=ph1-(this.n*this.tn/this.r)*this.d2*(.5-(1.+3.*this.t)*this.d2*this.C3);lam=this.dd*(1.+this.t*this.d2*(-this.C4+(1.+3.*this.t)*this.d2*this.C5))/Math.cos(ph1);}
+p.x=Proj4js.common.adjust_lon(this.long0+lam);p.y=phi;return p;},pj_enfn:function(es){en=new Array();en[0]=this.C00-es*(this.C02+es*(this.C04+es*(this.C06+es*this.C08)));en[1]=es*(this.C22-es*(this.C04+es*(this.C06+es*this.C08)));var t=es*es;en[2]=t*(this.C44-es*(this.C46+es*this.C48));t*=es;en[3]=t*(this.C66-es*this.C68);en[4]=t*es*this.C88;return en;},pj_mlfn:function(phi,sphi,cphi,en){cphi*=sphi;sphi*=sphi;return(en[0]*phi-cphi*(en[1]+sphi*(en[2]+sphi*(en[3]+sphi*en[4]))));},pj_inv_mlfn:function(arg,es,en){k=1./(1.-es);phi=arg;for(i=Proj4js.common.MAX_ITER;i;--i){s=Math.sin(phi);t=1.-es*s*s;t=(this.pj_mlfn(phi,s,Math.cos(phi),en)-arg)*(t*Math.sqrt(t))*k;phi-=t;if(Math.abs(t)<Proj4js.common.EPSLN)
+return phi;}
+Proj4js.reportError("cass:pj_inv_mlfn: Convergence error");return phi;},C00:1.0,C02:.25,C04:.046875,C06:.01953125,C08:.01068115234375,C22:.75,C44:.46875,C46:.01302083333333333333,C48:.00712076822916666666,C66:.36458333333333333333,C68:.00569661458333333333,C88:.3076171875}
+Proj4js.Proj.gauss={init:function(){sphi=Math.sin(this.lat0);cphi=Math.cos(this.lat0);cphi*=cphi;this.rc=Math.sqrt(1.0-this.es)/(1.0-this.es*sphi*sphi);this.C=Math.sqrt(1.0+this.es*cphi*cphi/(1.0-this.es));this.phic0=Math.asin(sphi/this.C);this.ratexp=0.5*this.C*this.e;this.K=Math.tan(0.5*this.phic0+Proj4js.common.FORTPI)/(Math.pow(Math.tan(0.5*this.lat0+Proj4js.common.FORTPI),this.C)*Proj4js.common.srat(this.e*sphi,this.ratexp));},forward:function(p){var lon=p.x;var lat=p.y;p.y=2.0*Math.atan(this.K*Math.pow(Math.tan(0.5*lat+Proj4js.common.FORTPI),this.C)*Proj4js.common.srat(this.e*Math.sin(lat),this.ratexp))-Proj4js.common.HALF_PI;p.x=this.C*lon;return p;},inverse:function(p){var DEL_TOL=1e-14;var lon=p.x/this.C;var lat=p.y;num=Math.pow(Math.tan(0.5*lat+Proj4js.common.FORTPI)/this.K,1./this.C);for(var i=Proj4js.common.MAX_ITER;i>0;--i){lat=2.0*Math.atan(num*Proj4js.common.srat(this.e*Math.sin(p.y),-0.5*this.e))-Proj4js.common.HALF_PI;if(Math.abs(lat-p.y)<DEL_TOL)break;p.y=l
 at;}
+if(!i){Proj4js.reportError("gauss:inverse:convergence failed");return null;}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.omerc={init:function(){if(!this.mode)this.mode=0;if(!this.lon1){this.lon1=0;this.mode=1;}
+if(!this.lon2)this.lon2=0;if(!this.lat2)this.lat2=0;var temp=this.b/this.a;var es=1.0-Math.pow(temp,2);var e=Math.sqrt(es);this.sin_p20=Math.sin(this.lat0);this.cos_p20=Math.cos(this.lat0);this.con=1.0-this.es*this.sin_p20*this.sin_p20;this.com=Math.sqrt(1.0-es);this.bl=Math.sqrt(1.0+this.es*Math.pow(this.cos_p20,4.0)/(1.0-es));this.al=this.a*this.bl*this.k0*this.com/this.con;if(Math.abs(this.lat0)<Proj4js.common.EPSLN){this.ts=1.0;this.d=1.0;this.el=1.0;}else{this.ts=Proj4js.common.tsfnz(this.e,this.lat0,this.sin_p20);this.con=Math.sqrt(this.con);this.d=this.bl*this.com/(this.cos_p20*this.con);if((this.d*this.d-1.0)>0.0){if(this.lat0>=0.0){this.f=this.d+Math.sqrt(this.d*this.d-1.0);}else{this.f=this.d-Math.sqrt(this.d*this.d-1.0);}}else{this.f=this.d;}
+this.el=this.f*Math.pow(this.ts,this.bl);}
+if(this.mode!=0){this.g=.5*(this.f-1.0/this.f);this.gama=Proj4js.common.asinz(Math.sin(this.alpha)/this.d);this.longc=this.longc-Proj4js.common.asinz(this.g*Math.tan(this.gama))/this.bl;this.con=Math.abs(this.lat0);if((this.con>Proj4js.common.EPSLN)&&(Math.abs(this.con-Proj4js.common.HALF_PI)>Proj4js.common.EPSLN)){this.singam=Math.sin(this.gama);this.cosgam=Math.cos(this.gama);this.sinaz=Math.sin(this.alpha);this.cosaz=Math.cos(this.alpha);if(this.lat0>=0){this.u=(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}else{this.u=-(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}}else{Proj4js.reportError("omerc:Init:DataError");}}else{this.sinphi=Math.sin(this.at1);this.ts1=Proj4js.common.tsfnz(this.e,this.lat1,this.sinphi);this.sinphi=Math.sin(this.lat2);this.ts2=Proj4js.common.tsfnz(this.e,this.lat2,this.sinphi);this.h=Math.pow(this.ts1,this.bl);this.l=Math.pow(this.ts2,this.bl);this.f=this.el/this.h;this.g=.5*(this.f-1.0/this.f);this.j=
 (this.el*this.el-this.l*this.h)/(this.el*this.el+this.l*this.h);this.p=(this.l-this.h)/(this.l+this.h);this.dlon=this.lon1-this.lon2;if(this.dlon<-Proj4js.common.PI)this.lon2=this.lon2-2.0*Proj4js.common.PI;if(this.dlon>Proj4js.common.PI)this.lon2=this.lon2+2.0*Proj4js.common.PI;this.dlon=this.lon1-this.lon2;this.longc=.5*(this.lon1+this.lon2)-Math.atan(this.j*Math.tan(.5*this.bl*this.dlon)/this.p)/this.bl;this.dlon=Proj4js.common.adjust_lon(this.lon1-this.longc);this.gama=Math.atan(Math.sin(this.bl*this.dlon)/this.g);this.alpha=Proj4js.common.asinz(this.d*Math.sin(this.gama));if(Math.abs(this.lat1-this.lat2)<=Proj4js.common.EPSLN){Proj4js.reportError("omercInitDataError");}else{this.con=Math.abs(this.lat1);}
+if((this.con<=Proj4js.common.EPSLN)||(Math.abs(this.con-HALF_PI)<=Proj4js.common.EPSLN)){Proj4js.reportError("omercInitDataError");}else{if(Math.abs(Math.abs(this.lat0)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN){Proj4js.reportError("omercInitDataError");}}
+this.singam=Math.sin(this.gam);this.cosgam=Math.cos(this.gam);this.sinaz=Math.sin(this.alpha);this.cosaz=Math.cos(this.alpha);if(this.lat0>=0){this.u=(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}else{this.u=-(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}}},forward:function(p){var theta;var sin_phi,cos_phi;var b;var c,t,tq;var con,n,ml;var q,us,vl;var ul,vs;var s;var dlon;var ts1;var lon=p.x;var lat=p.y;sin_phi=Math.sin(lat);dlon=Proj4js.common.adjust_lon(lon-this.longc);vl=Math.sin(this.bl*dlon);if(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)>Proj4js.common.EPSLN){ts1=Proj4js.common.tsfnz(this.e,lat,sin_phi);q=this.el/(Math.pow(ts1,this.bl));s=.5*(q-1.0/q);t=.5*(q+1.0/q);ul=(s*this.singam-vl*this.cosgam)/t;con=Math.cos(this.bl*dlon);if(Math.abs(con)<.0000001){us=this.al*this.bl*dlon;}else{us=this.al*Math.atan((s*this.cosgam+vl*this.singam)/con)/this.bl;if(con<0)us=us+Proj4js.common.PI*this.al/this.bl;}}else{if(lat>=0){ul=thi
 s.singam;}else{ul=-this.singam;}
+us=this.al*lat/this.bl;}
+if(Math.abs(Math.abs(ul)-1.0)<=Proj4js.common.EPSLN){Proj4js.reportError("omercFwdInfinity");}
+vs=.5*this.al*Math.log((1.0-ul)/(1.0+ul))/this.bl;us=us-this.u;var x=this.x0+vs*this.cosaz+us*this.sinaz;var y=this.y0+us*this.cosaz-vs*this.sinaz;p.x=x;p.y=y;return p;},inverse:function(p){var delta_lon;var theta;var delta_theta;var sin_phi,cos_phi;var b;var c,t,tq;var con,n,ml;var vs,us,q,s,ts1;var vl,ul,bs;var dlon;var flag;p.x-=this.x0;p.y-=this.y0;flag=0;vs=p.x*this.cosaz-p.y*this.sinaz;us=p.y*this.cosaz+p.x*this.sinaz;us=us+this.u;q=Math.exp(-this.bl*vs/this.al);s=.5*(q-1.0/q);t=.5*(q+1.0/q);vl=Math.sin(this.bl*us/this.al);ul=(vl*this.cosgam+s*this.singam)/t;if(Math.abs(Math.abs(ul)-1.0)<=Proj4js.common.EPSLN)
+{lon=this.longc;if(ul>=0.0){lat=Proj4js.common.HALF_PI;}else{lat=-Proj4js.common.HALF_PI;}}else{con=1.0/this.bl;ts1=Math.pow((this.el/Math.sqrt((1.0+ul)/(1.0-ul))),con);lat=Proj4js.common.phi2z(this.e,ts1);theta=this.longc-Math.atan2((s*this.cosgam-vl*this.singam),con)/this.bl;lon=Proj4js.common.adjust_lon(theta);}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.lcc={init:function(){if(!this.lat2){this.lat2=this.lat0;}
+if(!this.k0)this.k0=1.0;if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("lcc:init: Equal Latitudes");return;}
+var temp=this.b/this.a;this.e=Math.sqrt(1.0-temp*temp);var sin1=Math.sin(this.lat1);var cos1=Math.cos(this.lat1);var ms1=Proj4js.common.msfnz(this.e,sin1,cos1);var ts1=Proj4js.common.tsfnz(this.e,this.lat1,sin1);var sin2=Math.sin(this.lat2);var cos2=Math.cos(this.lat2);var ms2=Proj4js.common.msfnz(this.e,sin2,cos2);var ts2=Proj4js.common.tsfnz(this.e,this.lat2,sin2);var ts0=Proj4js.common.tsfnz(this.e,this.lat0,Math.sin(this.lat0));if(Math.abs(this.lat1-this.lat2)>Proj4js.common.EPSLN){this.ns=Math.log(ms1/ms2)/Math.log(ts1/ts2);}else{this.ns=sin1;}
+this.f0=ms1/(this.ns*Math.pow(ts1,this.ns));this.rh=this.a*this.f0*Math.pow(ts0,this.ns);if(!this.title)this.title="Lambert Conformal Conic";},forward:function(p){var lon=p.x;var lat=p.y;if(lat<=90.0&&lat>=-90.0&&lon<=180.0&&lon>=-180.0){}else{Proj4js.reportError("lcc:forward: llInputOutOfRange: "+lon+" : "+lat);return null;}
+var con=Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI);var ts,rh1;if(con>Proj4js.common.EPSLN){ts=Proj4js.common.tsfnz(this.e,lat,Math.sin(lat));rh1=this.a*this.f0*Math.pow(ts,this.ns);}else{con=lat*this.ns;if(con<=0){Proj4js.reportError("lcc:forward: No Projection");return null;}
+rh1=0;}
+var theta=this.ns*Proj4js.common.adjust_lon(lon-this.long0);p.x=this.k0*(rh1*Math.sin(theta))+this.x0;p.y=this.k0*(this.rh-rh1*Math.cos(theta))+this.y0;return p;},inverse:function(p){var rh1,con,ts;var lat,lon;var x=(p.x-this.x0)/this.k0;var y=(this.rh-(p.y-this.y0)/this.k0);if(this.ns>0){rh1=Math.sqrt(x*x+y*y);con=1.0;}else{rh1=-Math.sqrt(x*x+y*y);con=-1.0;}
+var theta=0.0;if(rh1!=0){theta=Math.atan2((con*x),(con*y));}
+if((rh1!=0)||(this.ns>0.0)){con=1.0/this.ns;ts=Math.pow((rh1/(this.a*this.f0)),con);lat=Proj4js.common.phi2z(this.e,ts);if(lat==-9999)return null;}else{lat=-Proj4js.common.HALF_PI;}
+lon=Proj4js.common.adjust_lon(theta/this.ns+this.long0);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.laea={S_POLE:1,N_POLE:2,EQUIT:3,OBLIQ:4,init:function(){var t=Math.abs(this.lat0);if(Math.abs(t-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.mode=this.lat0<0.?this.S_POLE:this.N_POLE;}else if(Math.abs(t)<Proj4js.common.EPSLN){this.mode=this.EQUIT;}else{this.mode=this.OBLIQ;}
+if(this.es>0){var sinphi;this.qp=Proj4js.common.qsfnz(this.e,1.0);this.mmf=.5/(1.-this.es);this.apa=this.authset(this.es);switch(this.mode){case this.N_POLE:case this.S_POLE:this.dd=1.;break;case this.EQUIT:this.rq=Math.sqrt(.5*this.qp);this.dd=1./this.rq;this.xmf=1.;this.ymf=.5*this.qp;break;case this.OBLIQ:this.rq=Math.sqrt(.5*this.qp);sinphi=Math.sin(this.lat0);this.sinb1=Proj4js.common.qsfnz(this.e,sinphi)/this.qp;this.cosb1=Math.sqrt(1.-this.sinb1*this.sinb1);this.dd=Math.cos(this.lat0)/(Math.sqrt(1.-this.es*sinphi*sinphi)*this.rq*this.cosb1);this.ymf=(this.xmf=this.rq)/this.dd;this.xmf*=this.dd;break;}}else{if(this.mode==this.OBLIQ){this.sinph0=Math.sin(this.lat0);this.cosph0=Math.cos(this.lat0);}}},forward:function(p){var x,y;var lam=p.x;var phi=p.y;lam=Proj4js.common.adjust_lon(lam-this.long0);if(this.sphere){var coslam,cosphi,sinphi;sinphi=Math.sin(phi);cosphi=Math.cos(phi);coslam=Math.cos(lam);switch(this.mode){case this.EQUIT:y=(this.mode==this.EQUIT)?1.+cosphi*co
 slam:1.+this.sinph0*sinphi+this.cosph0*cosphi*coslam;if(y<=Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:y less than eps");return null;}
+y=Math.sqrt(2./y);x=y*cosphi*Math.sin(lam);y*=(this.mode==this.EQUIT)?sinphi:this.cosph0*sinphi-this.sinph0*cosphi*coslam;break;case this.N_POLE:coslam=-coslam;case this.S_POLE:if(Math.abs(phi+this.phi0)<Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:phi < eps");return null;}
+y=Proj4js.common.FORTPI-phi*.5;y=2.*((this.mode==this.S_POLE)?Math.cos(y):Math.sin(y));x=y*Math.sin(lam);y*=coslam;break;}}else{var coslam,sinlam,sinphi,q,sinb=0.0,cosb=0.0,b=0.0;coslam=Math.cos(lam);sinlam=Math.sin(lam);sinphi=Math.sin(phi);q=Proj4js.common.qsfnz(this.e,sinphi);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){sinb=q/this.qp;cosb=Math.sqrt(1.-sinb*sinb);}
+switch(this.mode){case this.OBLIQ:b=1.+this.sinb1*sinb+this.cosb1*cosb*coslam;break;case this.EQUIT:b=1.+cosb*coslam;break;case this.N_POLE:b=Proj4js.common.HALF_PI+phi;q=this.qp-q;break;case this.S_POLE:b=phi-Proj4js.common.HALF_PI;q=this.qp+q;break;}
+if(Math.abs(b)<Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:b < eps");return null;}
+switch(this.mode){case this.OBLIQ:case this.EQUIT:b=Math.sqrt(2./b);if(this.mode==this.OBLIQ){y=this.ymf*b*(this.cosb1*sinb-this.sinb1*cosb*coslam);}else{y=(b=Math.sqrt(2./(1.+cosb*coslam)))*sinb*this.ymf;}
+x=this.xmf*b*cosb*sinlam;break;case this.N_POLE:case this.S_POLE:if(q>=0.){x=(b=Math.sqrt(q))*sinlam;y=coslam*((this.mode==this.S_POLE)?b:-b);}else{x=y=0.;}
+break;}}
+p.x=this.a*x+this.x0;p.y=this.a*y+this.y0;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var x=p.x/this.a;var y=p.y/this.a;if(this.sphere){var cosz=0.0,rh,sinz=0.0;rh=Math.sqrt(x*x+y*y);var phi=rh*.5;if(phi>1.){Proj4js.reportError("laea:Inv:DataError");return null;}
+phi=2.*Math.asin(phi);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){sinz=Math.sin(phi);cosz=Math.cos(phi);}
+switch(this.mode){case this.EQUIT:phi=(Math.abs(rh)<=Proj4js.common.EPSLN)?0.:Math.asin(y*sinz/rh);x*=sinz;y=cosz*rh;break;case this.OBLIQ:phi=(Math.abs(rh)<=Proj4js.common.EPSLN)?this.phi0:Math.asin(cosz*sinph0+y*sinz*cosph0/rh);x*=sinz*cosph0;y=(cosz-Math.sin(phi)*sinph0)*rh;break;case this.N_POLE:y=-y;phi=Proj4js.common.HALF_PI-phi;break;case this.S_POLE:phi-=Proj4js.common.HALF_PI;break;}
+lam=(y==0.&&(this.mode==this.EQUIT||this.mode==this.OBLIQ))?0.:Math.atan2(x,y);}else{var cCe,sCe,q,rho,ab=0.0;switch(this.mode){case this.EQUIT:case this.OBLIQ:x/=this.dd;y*=this.dd;rho=Math.sqrt(x*x+y*y);if(rho<Proj4js.common.EPSLN){p.x=0.;p.y=this.phi0;return p;}
+sCe=2.*Math.asin(.5*rho/this.rq);cCe=Math.cos(sCe);x*=(sCe=Math.sin(sCe));if(this.mode==this.OBLIQ){ab=cCe*this.sinb1+y*sCe*this.cosb1/rho
+q=this.qp*ab;y=rho*this.cosb1*cCe-y*this.sinb1*sCe;}else{ab=y*sCe/rho;q=this.qp*ab;y=rho*cCe;}
+break;case this.N_POLE:y=-y;case this.S_POLE:q=(x*x+y*y);if(!q){p.x=0.;p.y=this.phi0;return p;}
+ab=1.-q/this.qp;if(this.mode==this.S_POLE){ab=-ab;}
+break;}
+lam=Math.atan2(x,y);phi=this.authlat(Math.asin(ab),this.apa);}
+p.x=Proj4js.common.adjust_lon(this.long0+lam);p.y=phi;return p;},P00:.33333333333333333333,P01:.17222222222222222222,P02:.10257936507936507936,P10:.06388888888888888888,P11:.06640211640211640211,P20:.01641501294219154443,authset:function(es){var t;var APA=new Array();APA[0]=es*this.P00;t=es*es;APA[0]+=t*this.P01;APA[1]=t*this.P10;t*=es;APA[0]+=t*this.P02;APA[1]+=t*this.P11;APA[2]=t*this.P20;return APA;},authlat:function(beta,APA){var t=beta+beta;return(beta+APA[0]*Math.sin(t)+APA[1]*Math.sin(t+t)+APA[2]*Math.sin(t+t+t));}};Proj4js.Proj.aeqd={init:function(){this.sin_p12=Math.sin(this.lat0);this.cos_p12=Math.cos(this.lat0);},forward:function(p){var lon=p.x;var lat=p.y;var ksp;var sinphi=Math.sin(p.y);var cosphi=Math.cos(p.y);var dlon=Proj4js.common.adjust_lon(lon-this.long0);var coslon=Math.cos(dlon);var g=this.sin_p12*sinphi+this.cos_p12*cosphi*coslon;if(Math.abs(Math.abs(g)-1.0)<Proj4js.common.EPSLN){ksp=1.0;if(g<0.0){Proj4js.reportError("aeqd:Fwd:PointError");return;}}else
 {var z=Math.acos(g);ksp=z/Math.sin(z);}
+p.x=this.x0+this.a*ksp*cosphi*Math.sin(dlon);p.y=this.y0+this.a*ksp*(this.cos_p12*sinphi-this.sin_p12*cosphi*coslon);return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var rh=Math.sqrt(p.x*p.x+p.y*p.y);if(rh>(2.0*Proj4js.common.HALF_PI*this.a)){Proj4js.reportError("aeqdInvDataError");return;}
+var z=rh/this.a;var sinz=Math.sin(z);var cosz=Math.cos(z);var lon=this.long0;var lat;if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.lat0;}else{lat=Proj4js.common.asinz(cosz*this.sin_p12+(p.y*sinz*this.cos_p12)/rh);var con=Math.abs(this.lat0)-Proj4js.common.HALF_PI;if(Math.abs(con)<=Proj4js.common.EPSLN){if(lat0>=0.0){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x,-p.y));}else{lon=Proj4js.common.adjust_lon(this.long0-Math.atan2(-p.x,p.y));}}else{con=cosz-this.sin_p12*Math.sin(lat);if((Math.abs(con)<Proj4js.common.EPSLN)&&(Math.abs(p.x)<Proj4js.common.EPSLN)){}else{var temp=Math.atan2((p.x*sinz*this.cos_p12),(con*rh));lon=Proj4js.common.adjust_lon(this.long0+Math.atan2((p.x*sinz*this.cos_p12),(con*rh)));}}}
+p.x=lon;p.y=lat;return p;}};Proj4js.Proj.moll={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;var delta_lon=Proj4js.common.adjust_lon(lon-this.long0);var theta=lat;var con=Proj4js.common.PI*Math.sin(lat);for(var i=0;true;i++){var delta_theta=-(theta+Math.sin(theta)-con)/(1.0+Math.cos(theta));theta+=delta_theta;if(Math.abs(delta_theta)<Proj4js.common.EPSLN)break;if(i>=50){Proj4js.reportError("moll:Fwd:IterationError");}}
+theta/=2.0;if(Proj4js.common.PI/2-Math.abs(lat)<Proj4js.common.EPSLN)delta_lon=0;var x=0.900316316158*this.a*delta_lon*Math.cos(theta)+this.x0;var y=1.4142135623731*this.a*Math.sin(theta)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var theta;var arg;p.x-=this.x0;var arg=p.y/(1.4142135623731*this.a);if(Math.abs(arg)>0.999999999999)arg=0.999999999999;var theta=Math.asin(arg);var lon=Proj4js.common.adjust_lon(this.long0+(p.x/(0.900316316158*this.a*Math.cos(theta))));if(lon<(-Proj4js.common.PI))lon=-Proj4js.common.PI;if(lon>Proj4js.common.PI)lon=Proj4js.common.PI;arg=(2.0*theta+Math.sin(2.0*theta))/Proj4js.common.PI;if(Math.abs(arg)>1.0)arg=1.0;var lat=Math.asin(arg);p.x=lon;p.y=lat;return p;}};
\ No newline at end of file

Modified: trunk/lib/fusion.js
===================================================================
--- trunk/lib/fusion.js	2011-04-14 19:43:29 UTC (rev 2370)
+++ trunk/lib/fusion.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -1380,8 +1380,8 @@
     if (!Fusion._singleFile) {
         var coreScripts = ['lib/OpenLayers/OpenLayers.js',
                             'lib/OLpatch.js',
-                            'lib/jxlib.uncompressed.js',
-                            'lib/proj4js-compressed.js',
+                            'lib/jxLib/jxlib.uncompressed.js',
+                            'lib/Proj4js/proj4js-compressed.js',
                             'lib/EventMgr.js',
                             'lib/Error.js',
                             'lib/ApplicationDefinition.js',

Added: trunk/lib/jxLib/jxlib.js
===================================================================
--- trunk/lib/jxLib/jxlib.js	                        (rev 0)
+++ trunk/lib/jxLib/jxlib.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,30 @@
+/******************************************************************************
+ * 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, 3.0alpha
+ * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+var MooTools={version:"1.2.5dev",build:"%build%"};var Native=function(K){K=K||{};var A=K.name;var I=K.legacy;var B=K.protect;var C=K.implement;var H=K.generics;var F=K.initialize;var G=K.afterImplement||function(){};var D=F||I;H=H!==false;D.constructor=Native;D.$family={name:"native"};if(I&&F){D.prototype=I.prototype}D.prototype.constructor=D;if(A){var E=A.toLowerCase();D.prototype.$family={name:E};Native.typize(D,E)}var J=function(N,L,O,M){if(!B||M||!N.prototype[L]){N.prototype[L]=O}if(H){Native.genericize(N,L,B)}G.call(N,L,O);return N};D.alias=function(N,L,P){if(typeof N=="string"){var O=this.prototype[N];if((N=O)){return J(this,L,N,P)}}for(var M in N){this.alias(M,N[M],L)}return this};D.implement=function(M,L,O){if(typeof M=="string"){return J(this,M,L,O)}for(var N in M){J(this,N,M[N],L)}return this};if(C){D.implement(C)}return D};Native.genericize=function(B,C,A){if((!A||!B[C])&&typeof B.prototype[C]=="function"){B[C]=function(){var D=Array.prototype.slice.call(arguments
 );return B.prototype[C].apply(D.shift(),D)}}};Native.implement=function(D,C){for(var B=0,A=D.length;B<A;B++){D[B].implement(C)}};Native.typize=function(A,B){if(!A.type){A.type=function(C){return($type(C)===B)}}};(function(){var A={Array:Array,Date:Date,Function:Function,Number:Number,RegExp:RegExp,String:String};for(var G in A){new Native({name:G,initialize:A[G],protect:true})}var D={"boolean":Boolean,"native":Native,object:Object};for(var C in D){Native.typize(D[C],C)}var F={Array:["concat","indexOf","join","lastIndexOf","pop","push","reverse","shift","slice","sort","splice","toString","unshift","valueOf"],String:["charAt","charCodeAt","concat","indexOf","lastIndexOf","match","replace","search","slice","split","substr","substring","toLowerCase","toUpperCase","valueOf"]};for(var E in F){for(var B=F[E].length;B--;){Native.genericize(A[E],F[E][B],true)}}})();var Hash=new Native({name:"Hash",initialize:function(A){if($type(A)=="hash"){A=$unlink(A.getClean())}for(var B in A){thi
 s[B]=A[B]}return this}});Hash.implement({forEach:function(B,C){for(var A in this){if(this.hasOwnProperty(A)){B.call(C,this[A],A,this)}}},getClean:function(){var B={};for(var A in this){if(this.hasOwnProperty(A)){B[A]=this[A]}}return B},getLength:function(){var B=0;for(var A in this){if(this.hasOwnProperty(A)){B++}}return B}});Hash.alias("forEach","each");Array.implement({forEach:function(C,D){for(var B=0,A=this.length;B<A;B++){C.call(D,this[B],B,this)}}});Array.alias("forEach","each");function $A(B){if(B.item){var A=B.length,C=new Array(A);while(A--){C[A]=B[A]}return C}return Array.prototype.slice.call(B)}function $arguments(A){return function(){return arguments[A]}}function $chk(A){return !!(A||A===0)}function $clear(A){clearTimeout(A);clearInterval(A);return null}function $defined(A){return(A!=undefined)}function $each(C,B,D){var A=$type(C);((A=="arguments"||A=="collection"||A=="array")?Array:Hash).each(C,B,D)}function $empty(){}function $extend(C,A){for(var B in (A||{})){
 C[B]=A[B]}return C}function $H(A){return new Hash(A)}function $lambda(A){return($type(A)=="function")?A:function(){return A}}function $merge(){var A=Array.slice(arguments);A.unshift({});return $mixin.apply(null,A)}function $mixin(E){for(var D=1,A=arguments.length;D<A;D++){var B=arguments[D];if($type(B)!="object"){continue}for(var C in B){var G=B[C],F=E[C];E[C]=(F&&$type(G)=="object"&&$type(F)=="object")?$mixin(F,G):$unlink(G)}}return E}function $pick(){for(var B=0,A=arguments.length;B<A;B++){if(arguments[B]!=undefined){return arguments[B]}}return null}function $random(B,A){return Math.floor(Math.random()*(A-B+1)+B)}function $splat(B){var A=$type(B);return(A)?((A!="array"&&A!="arguments")?[B]:B):[]}var $time=Date.now||function(){return +new Date};function $try(){for(var B=0,A=arguments.length;B<A;B++){try{return arguments[B]()}catch(C){}}return null}function $type(A){if(A==undefined){return false}if(A.$family){return(A.$family.name=="number"&&!isFinite(A))?false:A.$family.nam
 e}if(A.nodeName){switch(A.nodeType){case 1:return"element";case 3:return(/\S/).test(A.nodeValue)?"textnode":"whitespace"}}else{if(typeof A.length=="number"){if(A.callee){return"arguments"}else{if(A.item){return"collection"}}}}return typeof A}function $unlink(C){var B;switch($type(C)){case"object":B={};for(var E in C){B[E]=$unlink(C[E])}break;case"hash":B=new Hash(C);break;case"array":B=[];for(var D=0,A=C.length;D<A;D++){B[D]=$unlink(C[D])}break;default:return C}return B}var Browser=$merge({Engine:{name:"unknown",version:0},Platform:{name:(window.orientation!=undefined)?"ipod":(navigator.platform.match(/mac|win|linux/i)||["other"])[0].toLowerCase()},Features:{xpath:!!(document.evaluate),air:!!(window.runtime),query:!!(document.querySelector)},Plugins:{},Engines:{presto:function(){return(!window.opera)?false:((arguments.callee.caller)?960:((document.getElementsByClassName)?950:925))},trident:function(){return(!window.ActiveXObject)?false:((window.XMLHttpRequest)?((document.que
 rySelectorAll)?6:5):4)},webkit:function(){return(navigator.taintEnabled)?false:((Browser.Features.xpath)?((Browser.Features.query)?525:420):419)},gecko:function(){return(!document.getBoxObjectFor&&window.mozInnerScreenX==null)?false:((document.getElementsByClassName)?19:18)}}},Browser||{});Browser.Platform[Browser.Platform.name]=true;Browser.detect=function(){for(var B in this.Engines){var A=this.Engines[B]();if(A){this.Engine={name:B,version:A};this.Engine[B]=this.Engine[B+A]=true;break}}return{name:B,version:A}};Browser.detect();Browser.Request=function(){return $try(function(){return new XMLHttpRequest()},function(){return new ActiveXObject("MSXML2.XMLHTTP")},function(){return new ActiveXObject("Microsoft.XMLHTTP")})};Browser.Features.xhr=!!(Browser.Request());Browser.Plugins.Flash=(function(){var A=($try(function(){return navigator.plugins["Shockwave Flash"].description},function(){return new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")})||"0 r0
 ").match(/\d+/g);return{version:parseInt(A[0]||0+"."+A[1],10)||0,build:parseInt(A[2],10)||0}})();function $exec(B){if(!B){return B}if(window.execScript){window.execScript(B)}else{var A=document.createElement("script");A.setAttribute("type","text/javascript");A[(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerText":"text"]=B;document.head.appendChild(A);document.head.removeChild(A)}return B}Native.UID=1;var $uid=(Browser.Engine.trident)?function(A){return(A.uid||(A.uid=[Native.UID++]))[0]}:function(A){return A.uid||(A.uid=Native.UID++)};var Window=new Native({name:"Window",legacy:(Browser.Engine.trident)?null:window.Window,initialize:function(A){$uid(A);if(!A.Element){A.Element=$empty;if(Browser.Engine.webkit){A.document.createElement("iframe")}A.Element.prototype=(Browser.Engine.webkit)?window["[[DOMElement.prototype]]"]:{}}A.document.window=A;return $extend(A,Window.Prototype)},afterImplement:function(B,A){window[B]=Window.Prototype[B]=A}});Window.Prototype={$famil
 y:{name:"window"}};new Window(window);var Document=new Native({name:"Document",legacy:(Browser.Engine.trident)?null:window.Document,initialize:function(A){$uid(A);A.head=A.getElementsByTagName("head")[0];A.html=A.getElementsByTagName("html")[0];if(Browser.Engine.trident&&Browser.Engine.version<=4){$try(function(){A.execCommand("BackgroundImageCache",false,true)})}if(Browser.Engine.trident){A.window.attachEvent("onunload",function(){A.window.detachEvent("onunload",arguments.callee);A.head=A.html=A.window=null})}return $extend(A,Document.Prototype)},afterImplement:function(B,A){document[B]=Document.Prototype[B]=A}});Document.Prototype={$family:{name:"document"}};new Document(document);Array.implement({every:function(C,D){for(var B=0,A=this.length;B<A;B++){if(!C.call(D,this[B],B,this)){return false}}return true},filter:function(D,E){var C=[];for(var B=0,A=this.length;B<A;B++){if(D.call(E,this[B],B,this)){C.push(this[B])}}return C},clean:function(){return this.filter($defined)},
 indexOf:function(C,D){var A=this.length;for(var B=(D<0)?Math.max(0,A+D):D||0;B<A;B++){if(this[B]===C){return B}}return -1},map:function(D,E){var C=[];for(var B=0,A=this.length;B<A;B++){C[B]=D.call(E,this[B],B,this)}return C},some:function(C,D){for(var B=0,A=this.length;B<A;B++){if(C.call(D,this[B],B,this)){return true}}return false},associate:function(C){var D={},B=Math.min(this.length,C.length);for(var A=0;A<B;A++){D[C[A]]=this[A]}return D},link:function(C){var A={};for(var E=0,B=this.length;E<B;E++){for(var D in C){if(C[D](this[E])){A[D]=this[E];delete C[D];break}}}return A},contains:function(A,B){return this.indexOf(A,B)!=-1},extend:function(C){for(var B=0,A=C.length;B<A;B++){this.push(C[B])}return this},getLast:function(){return(this.length)?this[this.length-1]:null},getRandom:function(){return(this.length)?this[$random(0,this.length-1)]:null},include:function(A){if(!this.contains(A)){this.push(A)}return this},combine:function(C){for(var B=0,A=C.length;B<A;B++){this.incl
 ude(C[B])}return this},erase:function(B){for(var A=this.length;A--;A){if(this[A]===B){this.splice(A,1)}}return this},empty:function(){this.length=0;return this},flatten:function(){var D=[];for(var B=0,A=this.length;B<A;B++){var C=$type(this[B]);if(!C){continue}D=D.concat((C=="array"||C=="collection"||C=="arguments")?Array.flatten(this[B]):this[B])}return D},hexToRgb:function(B){if(this.length!=3){return null}var A=this.map(function(C){if(C.length==1){C+=C}return C.toInt(16)});return(B)?A:"rgb("+A+")"},rgbToHex:function(D){if(this.length<3){return null}if(this.length==4&&this[3]==0&&!D){return"transparent"}var B=[];for(var A=0;A<3;A++){var C=(this[A]-0).toString(16);B.push((C.length==1)?"0"+C:C)}return(D)?B:"#"+B.join("")}});Function.implement({extend:function(A){for(var B in A){this[B]=A[B]}return this},create:function(B){var A=this;B=B||{};return function(D){var C=B.arguments;C=(C!=undefined)?$splat(C):Array.slice(arguments,(B.event)?1:0);if(B.event){C=[D||window.event].ext
 end(C)}var E=function(){return A.apply(B.bind||null,C)};if(B.delay){return setTimeout(E,B.delay)}if(B.periodical){return setInterval(E,B.periodical)}if(B.attempt){return $try(E)}return E()}},run:function(A,B){return this.apply(B,$splat(A))},pass:function(A,B){return this.create({bind:B,arguments:A})},bind:function(B,A){return this.create({bind:B,arguments:A})},bindWithEvent:function(B,A){return this.create({bind:B,arguments:A,event:true})},attempt:function(A,B){return this.create({bind:B,arguments:A,attempt:true})()},delay:function(B,C,A){return this.create({bind:C,arguments:A,delay:B})()},periodical:function(C,B,A){return this.create({bind:B,arguments:A,periodical:C})()}});Number.implement({limit:function(B,A){return Math.min(A,Math.max(B,this))},round:function(A){A=Math.pow(10,A||0);return Math.round(this*A)/A},times:function(B,C){for(var A=0;A<this;A++){B.call(C,A,this)}},toFloat:function(){return parseFloat(this)},toInt:function(A){return parseInt(this,A||10)}});Number.a
 lias("times","each");(function(B){var A={};B.each(function(C){if(!Number[C]){A[C]=function(){return Math[C].apply(null,[this].concat($A(arguments)))}}});Number.implement(A)})(["abs","acos","asin","atan","atan2","ceil","cos","exp","floor","log","max","min","pow","sin","sqrt","tan"]);String.implement({test:function(A,B){return((typeof A=="string")?new RegExp(A,B):A).test(this)},contains:function(A,B){return(B)?(B+this+B).indexOf(B+A+B)>-1:this.indexOf(A)>-1},trim:function(){return this.replace(/^\s+|\s+$/g,"")},clean:function(){return this.replace(/\s+/g," ").trim()},camelCase:function(){return this.replace(/-\D/g,function(A){return A.charAt(1).toUpperCase()})},hyphenate:function(){return this.replace(/[A-Z]/g,function(A){return("-"+A.charAt(0).toLowerCase())})},capitalize:function(){return this.replace(/\b[a-z]/g,function(A){return A.toUpperCase()})},escapeRegExp:function(){return this.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")},toInt:function(A){return parseInt(this,A||10)},
 toFloat:function(){return parseFloat(this)},hexToRgb:function(B){var A=this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);return(A)?A.slice(1).hexToRgb(B):null},rgbToHex:function(B){var A=this.match(/\d{1,3}/g);return(A)?A.rgbToHex(B):null},stripScripts:function(B){var A="";var C=this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi,function(){A+=arguments[1]+"\n";return""});if(B===true){$exec(A)}else{if($type(B)=="function"){B(A,C)}}return C},substitute:function(A,B){return this.replace(B||(/\\?\{([^{}]+)\}/g),function(D,C){if(D.charAt(0)=="\\"){return D.slice(1)}return(A[C]!=undefined)?A[C]:""})}});Hash.implement({has:Object.prototype.hasOwnProperty,keyOf:function(B){for(var A in this){if(this.hasOwnProperty(A)&&this[A]===B){return A}}return null},hasValue:function(A){return(Hash.keyOf(this,A)!==null)},extend:function(A){Hash.each(A||{},function(C,B){Hash.set(this,B,C)},this);return this},combine:function(A){Hash.each(A||{},function(C,B){Hash.include(this,B,C)},this);return this},
 erase:function(A){if(this.hasOwnProperty(A)){delete this[A]}return this},get:function(A){return(this.hasOwnProperty(A))?this[A]:null},set:function(A,B){if(!this[A]||this.hasOwnProperty(A)){this[A]=B}return this},empty:function(){Hash.each(this,function(B,A){delete this[A]},this);return this},include:function(A,B){if(this[A]==undefined){this[A]=B}return this},map:function(B,C){var A=new Hash;Hash.each(this,function(E,D){A.set(D,B.call(C,E,D,this))},this);return A},filter:function(B,C){var A=new Hash;Hash.each(this,function(E,D){if(B.call(C,E,D,this)){A.set(D,E)}},this);return A},every:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&!B.call(C,this[A],A)){return false}}return true},some:function(B,C){for(var A in this){if(this.hasOwnProperty(A)&&B.call(C,this[A],A)){return true}}return false},getKeys:function(){var A=[];Hash.each(this,function(C,B){A.push(B)});return A},getValues:function(){var A=[];Hash.each(this,function(B){A.push(B)});return A},toQueryString:func
 tion(A){var B=[];Hash.each(this,function(F,E){if(A){E=A+"["+E+"]"}var D;switch($type(F)){case"object":D=Hash.toQueryString(F,E);break;case"array":var C={};F.each(function(H,G){C[G]=H});D=Hash.toQueryString(C,E);break;default:D=E+"="+encodeURIComponent(F)}if(F!=undefined){B.push(D)}});return B.join("&")}});Hash.alias({keyOf:"indexOf",hasValue:"contains"});var Event=new Native({name:"Event",initialize:function(A,F){F=F||window;var K=F.document;A=A||F.event;if(A.$extended){return A}this.$extended=true;var J=A.type;var G=A.target||A.srcElement;while(G&&G.nodeType==3){G=G.parentNode}if(J.test(/key/)){var B=A.which||A.keyCode;var M=Event.Keys.keyOf(B);if(J=="keydown"){var D=B-111;if(D>0&&D<13){M="f"+D}}M=M||String.fromCharCode(B).toLowerCase()}else{if(J.match(/(click|mouse|menu)/i)){K=(!K.compatMode||K.compatMode=="CSS1Compat")?K.html:K.body;var I={x:A.pageX||A.clientX+K.scrollLeft,y:A.pageY||A.clientY+K.scrollTop};var C={x:(A.pageX)?A.pageX-F.pageXOffset:A.clientX,y:(A.pageY)?A.p
 ageY-F.pageYOffset:A.clientY};if(J.match(/DOMMouseScroll|mousewheel/)){var H=(A.wheelDelta)?A.wheelDelta/120:-(A.detail||0)/3}var E=(A.which==3)||(A.button==2);var L=null;if(J.match(/over|out/)){switch(J){case"mouseover":L=A.relatedTarget||A.fromElement;break;case"mouseout":L=A.relatedTarget||A.toElement}if(!(function(){while(L&&L.nodeType==3){L=L.parentNode}return true}).create({attempt:Browser.Engine.gecko})()){L=false}}}}return $extend(this,{event:A,type:J,page:I,client:C,rightClick:E,wheel:H,relatedTarget:L,target:G,code:B,key:M,shift:A.shiftKey,control:A.ctrlKey,alt:A.altKey,meta:A.metaKey})}});Event.Keys=new Hash({enter:13,up:38,down:40,left:37,right:39,esc:27,space:32,backspace:8,tab:9,"delete":46});Event.implement({stop:function(){return this.stopPropagation().preventDefault()},stopPropagation:function(){if(this.event.stopPropagation){this.event.stopPropagation()}else{this.event.cancelBubble=true}return this},preventDefault:function(){if(this.event.preventDefault){th
 is.event.preventDefault()}else{this.event.returnValue=false}return this}});function Class(B){if(B instanceof Function){B={initialize:B}}var A=function(){Object.reset(this);if(A._prototyping){return this}this._current=$empty;var C=(this.initialize)?this.initialize.apply(this,arguments):this;delete this._current;delete this.caller;return C}.extend(this);A.implement(B);A.constructor=Class;A.prototype.constructor=A;return A}Function.prototype.protect=function(){this._protected=true;return this};Object.reset=function(A,C){if(C==null){for(var E in A){Object.reset(A,E)}return A}delete A[C];switch($type(A[C])){case"object":var D=function(){};D.prototype=A[C];var B=new D;A[C]=Object.reset(B);break;case"array":A[C]=$unlink(A[C]);break}return A};new Native({name:"Class",initialize:Class}).extend({instantiate:function(B){B._prototyping=true;var A=new B;delete B._prototyping;return A},wrap:function(A,B,C){if(C._origin){C=C._origin}return function(){if(C._protected&&this._current==null){t
 hrow new Error('The method "'+B+'" cannot be called.')}var E=this.caller,F=this._current;this.caller=F;this._current=arguments.callee;var D=C.apply(this,arguments);this._current=F;this.caller=E;return D}.extend({_owner:A,_origin:C,_name:B})}});Class.implement({implement:function(A,D){if($type(A)=="object"){for(var E in A){this.implement(E,A[E])}return this}var F=Class.Mutators[A];if(F){D=F.call(this,D);if(D==null){return this}}var C=this.prototype;switch($type(D)){case"function":if(D._hidden){return this}C[A]=Class.wrap(this,A,D);break;case"object":var B=C[A];if($type(B)=="object"){$mixin(B,D)}else{C[A]=$unlink(D)}break;case"array":C[A]=$unlink(D);break;default:C[A]=D}return this}});Class.Mutators={Extends:function(A){this.parent=A;this.prototype=Class.instantiate(A);this.implement("parent",function(){var B=this.caller._name,C=this.caller._owner.parent.prototype[B];if(!C){throw new Error('The method "'+B+'" has no parent.')}return C.apply(this,arguments)}.protect())},Impleme
 nts:function(A){$splat(A).each(function(B){if(B instanceof Function){B=Class.instantiate(B)}this.implement(B)},this)}};var Chain=new Class({$chain:[],chain:function(){this.$chain.extend(Array.flatten(arguments));return this},callChain:function(){return(this.$chain.length)?this.$chain.shift().apply(this,arguments):false},clearChain:function(){this.$chain.empty();return this}});var Events=new Class({$events:{},addEvent:function(C,B,A){C=Events.removeOn(C);if(B!=$empty){this.$events[C]=this.$events[C]||[];this.$events[C].include(B);if(A){B.internal=true}}return this},addEvents:function(A){for(var B in A){this.addEvent(B,A[B])}return this},fireEvent:function(C,B,A){C=Events.removeOn(C);if(!this.$events||!this.$events[C]){return this}this.$events[C].each(function(D){D.create({bind:this,delay:A,"arguments":B})()},this);return this},removeEvent:function(B,A){B=Events.removeOn(B);if(!this.$events[B]){return this}if(!A.internal){this.$events[B].erase(A)}return this},removeEvents:func
 tion(C){var D;if($type(C)=="object"){for(D in C){this.removeEvent(D,C[D])}return this}if(C){C=Events.removeOn(C)}for(D in this.$events){if(C&&C!=D){continue}var B=this.$events[D];for(var A=B.length;A--;A){this.removeEvent(D,B[A])}}return this}});Events.removeOn=function(A){return A.replace(/^on([A-Z])/,function(B,C){return C.toLowerCase()})};var Options=new Class({setOptions:function(){this.options=$merge.run([this.options].extend(arguments));if(!this.addEvent){return this}for(var A in this.options){if($type(this.options[A])!="function"||!(/^on[A-Z]/).test(A)){continue}this.addEvent(A,this.options[A]);delete this.options[A]}return this}});var Element=new Native({name:"Element",legacy:window.Element,initialize:function(A,B){var C=Element.Constructors.get(A);if(C){return C(B)}if(typeof A=="string"){return document.newElement(A,B)}return document.id(A).set(B)},afterImplement:function(A,B){Element.Prototype[A]=B;if(Array[A]){return }Elements.implement(A,function(){var C=[],G=tru
 e;for(var E=0,D=this.length;E<D;E++){var F=this[E][A].apply(this[E],arguments);C.push(F);if(G){G=($type(F)=="element")}}return(G)?new Elements(C):C})}});Element.Prototype={$family:{name:"element"}};Element.Constructors=new Hash;var IFrame=new Native({name:"IFrame",generics:false,initialize:function(){var F=Array.link(arguments,{properties:Object.type,iframe:$defined});var D=F.properties||{};var C=document.id(F.iframe);var E=D.onload||$empty;delete D.onload;D.id=D.name=$pick(D.id,D.name,C?(C.id||C.name):"IFrame_"+$time());C=new Element(C||"iframe",D);var B=function(){var G=$try(function(){return C.contentWindow.location.host});if(!G||G==window.location.host){var H=new Window(C.contentWindow);new Document(C.contentWindow.document);$extend(H.Element.prototype,Element.Prototype)}E.call(C.contentWindow,C.contentWindow.document)};var A=$try(function(){return C.contentWindow});((A&&A.document.body)||window.frames[D.id])?B():C.addListener("load",B);return C}});var Elements=new Nativ
 e({initialize:function(F,B){B=$extend({ddup:true,cash:true},B);F=F||[];if(B.ddup||B.cash){var G={},E=[];for(var C=0,A=F.length;C<A;C++){var D=document.id(F[C],!B.cash);if(B.ddup){if(G[D.uid]){continue}G[D.uid]=true}if(D){E.push(D)}}F=E}return(B.cash)?$extend(F,this):F}});Elements.implement({filter:function(A,B){if(!A){return this}return new Elements(Array.filter(this,(typeof A=="string")?function(C){return C.match(A)}:A,B))}});Document.implement({newElement:function(A,B){if(Browser.Engine.trident&&B){["name","type","checked"].each(function(C){if(!B[C]){return }A+=" "+C+'="'+B[C]+'"';if(C!="checked"){delete B[C]}});A="<"+A+">"}return document.id(this.createElement(A)).set(B)},newTextNode:function(A){return this.createTextNode(A)},getDocument:function(){return this},getWindow:function(){return this.window},id:(function(){var A={string:function(D,C,B){D=B.getElementById(D);return(D)?A.element(D,C):null},element:function(B,E){$uid(B);if(!E&&!B.$family&&!(/^object|embed$/i).test(
 B.tagName)){var C=Element.Prototype;for(var D in C){B[D]=C[D]}}return B},object:function(C,D,B){if(C.toElement){return A.element(C.toElement(B),D)}return null}};A.textnode=A.whitespace=A.window=A.document=$arguments(0);return function(C,E,D){if(C&&C.$family&&C.uid){return C}var B=$type(C);return(A[B])?A[B](C,E,D||document):null}})()});if(window.$==null){Window.implement({$:function(A,B){return document.id(A,B,this.document)}})}Window.implement({$$:function(A){if(arguments.length==1&&typeof A=="string"){return this.document.getElements(A)}var F=[];var C=Array.flatten(arguments);for(var D=0,B=C.length;D<B;D++){var E=C[D];switch($type(E)){case"element":F.push(E);break;case"string":F.extend(this.document.getElements(E,true))}}return new Elements(F)},getDocument:function(){return this.document},getWindow:function(){return this}});Native.implement([Element,Document],{getElement:function(A,B){return document.id(this.getElements(A,true)[0]||null,B)},getElements:function(A,D){A=A.spl
 it(",");var C=[];var B=(A.length>1);A.each(function(E){var F=this.getElementsByTagName(E.trim());(B)?C.extend(F):C=F},this);return new Elements(C,{ddup:B,cash:!D})}});(function(){var H={},F={};var I={input:"checked",option:"selected",textarea:(Browser.Engine.webkit&&Browser.Engine.version<420)?"innerHTML":"value"};var C=function(L){return(F[L]||(F[L]={}))};var G=function(N,L){if(!N){return }var M=N.uid;if(L!==true){L=false}if(Browser.Engine.trident){if(N.clearAttributes){var P=L&&N.cloneNode(false);N.clearAttributes();if(P){N.mergeAttributes(P)}}else{if(N.removeEvents){N.removeEvents()}}if((/object/i).test(N.tagName)){for(var O in N){if(typeof N[O]=="function"){N[O]=$empty}}Element.dispose(N)}}if(!M){return }H[M]=F[M]=null};var D=function(){Hash.each(H,G);if(Browser.Engine.trident){$A(document.getElementsByTagName("object")).each(G)}if(window.CollectGarbage){CollectGarbage()}H=F=null};var J=function(N,L,S,M,P,R){var O=N[S||L];var Q=[];while(O){if(O.nodeType==1&&(!M||Element.
 match(O,M))){if(!P){return document.id(O,R)}Q.push(O)}O=O[L]}return(P)?new Elements(Q,{ddup:false,cash:!R}):null};var E={html:"innerHTML","class":"className","for":"htmlFor",defaultValue:"defaultValue",text:(Browser.Engine.trident||(Browser.Engine.webkit&&Browser.Engine.version<420))?"innerText":"textContent"};var B=["compact","nowrap","ismap","declare","noshade","checked","disabled","readonly","multiple","selected","noresize","defer"];var K=["value","type","defaultValue","accessKey","cellPadding","cellSpacing","colSpan","frameBorder","maxLength","readOnly","rowSpan","tabIndex","useMap"];B=B.associate(B);Hash.extend(E,B);Hash.extend(E,K.associate(K.map(String.toLowerCase)));var A={before:function(M,L){if(L.parentNode){L.parentNode.insertBefore(M,L)}},after:function(M,L){if(!L.parentNode){return }var N=L.nextSibling;(N)?L.parentNode.insertBefore(M,N):L.parentNode.appendChild(M)},bottom:function(M,L){L.appendChild(M)},top:function(M,L){var N=L.firstChild;(N)?L.insertBefore(M,N
 ):L.appendChild(M)}};A.inside=A.bottom;Hash.each(A,function(L,M){M=M.capitalize();Element.implement("inject"+M,function(N){L(this,document.id(N,true));return this});Element.implement("grab"+M,function(N){L(document.id(N,true),this);return this})});Element.implement({set:function(O,M){switch($type(O)){case"object":for(var N in O){this.set(N,O[N])}break;case"string":var L=Element.Properties.get(O);(L&&L.set)?L.set.apply(this,Array.slice(arguments,1)):this.setProperty(O,M)}return this},get:function(M){var L=Element.Properties.get(M);return(L&&L.get)?L.get.apply(this,Array.slice(arguments,1)):this.getProperty(M)},erase:function(M){var L=Element.Properties.get(M);(L&&L.erase)?L.erase.apply(this):this.removeProperty(M);return this},setProperty:function(M,N){var L=E[M];if(N==undefined){return this.removeProperty(M)}if(L&&B[M]){N=!!N}(L)?this[L]=N:this.setAttribute(M,""+N);return this},setProperties:function(L){for(var M in L){this.setProperty(M,L[M])}return this},getProperty:functi
 on(M){var L=E[M];var N=(L)?this[L]:this.getAttribute(M,2);return(B[M])?!!N:(L)?N:N||null},getProperties:function(){var L=$A(arguments);return L.map(this.getProperty,this).associate(L)},removeProperty:function(M){var L=E[M];(L)?this[L]=(L&&B[M])?false:"":this.removeAttribute(M);return this},removeProperties:function(){Array.each(arguments,this.removeProperty,this);return this},hasClass:function(L){return this.className.contains(L," ")},addClass:function(L){if(!this.hasClass(L)){this.className=(this.className+" "+L).clean()}return this},removeClass:function(L){this.className=this.className.replace(new RegExp("(^|\\s)"+L+"(?:\\s|$)"),"$1");return this},toggleClass:function(L){return this.hasClass(L)?this.removeClass(L):this.addClass(L)},adopt:function(){Array.flatten(arguments).each(function(L){L=document.id(L,true);if(L){this.appendChild(L)}},this);return this},appendText:function(M,L){return this.grab(this.getDocument().newTextNode(M),L)},grab:function(M,L){A[L||"bottom"](doc
 ument.id(M,true),this);return this},inject:function(M,L){A[L||"bottom"](this,document.id(M,true));return this},replaces:function(L){L=document.id(L,true);L.parentNode.replaceChild(this,L);return this},wraps:function(M,L){M=document.id(M,true);return this.replaces(M).grab(M,L)},getPrevious:function(L,M){return J(this,"previousSibling",null,L,false,M)},getAllPrevious:function(L,M){return J(this,"previousSibling",null,L,true,M)},getNext:function(L,M){return J(this,"nextSibling",null,L,false,M)},getAllNext:function(L,M){return J(this,"nextSibling",null,L,true,M)},getFirst:function(L,M){return J(this,"nextSibling","firstChild",L,false,M)},getLast:function(L,M){return J(this,"previousSibling","lastChild",L,false,M)},getParent:function(L,M){return J(this,"parentNode",null,L,false,M)},getParents:function(L,M){return J(this,"parentNode",null,L,true,M)},getSiblings:function(L,M){return this.getParent().getChildren(L,M).erase(this)},getChildren:function(L,M){return J(this,"nextSibling"
 ,"firstChild",L,true,M)},getWindow:function(){return this.ownerDocument.window},getDocument:function(){return this.ownerDocument},getElementById:function(O,N){var M=this.ownerDocument.getElementById(O);if(!M){return null}for(var L=M.parentNode;L!=this;L=L.parentNode){if(!L){return null}}return document.id(M,N)},getSelected:function(){return new Elements($A(this.options).filter(function(L){return L.selected}))},getComputedStyle:function(M){if(this.currentStyle){return this.currentStyle[M.camelCase()]}var L=this.getDocument().defaultView.getComputedStyle(this,null);return(L)?L.getPropertyValue([M.hyphenate()]):null},toQueryString:function(){var L=[];this.getElements("input, select, textarea",true).each(function(M){if(!M.name||M.disabled||M.type=="submit"||M.type=="reset"||M.type=="file"){return }var N=(M.tagName.toLowerCase()=="select")?Element.getSelected(M).map(function(O){return O.value}):((M.type=="radio"||M.type=="checkbox")&&!M.checked)?null:M.value;$splat(N).each(functi
 on(O){if(typeof O!="undefined"){L.push(M.name+"="+encodeURIComponent(O))}})});return L.join("&")},clone:function(O,L){O=O!==false;var R=this.cloneNode(O);var N=function(V,U){if(!L){V.removeAttribute("id")}if(Browser.Engine.trident){V.clearAttributes();V.mergeAttributes(U);V.removeAttribute("uid");if(V.options){var W=V.options,S=U.options;for(var T=W.length;T--;){W[T].selected=S[T].selected}}}var X=I[U.tagName.toLowerCase()];if(X&&U[X]){V[X]=U[X]}};if(O){var P=R.getElementsByTagName("*"),Q=this.getElementsByTagName("*");for(var M=P.length;M--;){N(P[M],Q[M])}}N(R,this);return document.id(R)},destroy:function(){Element.empty(this);Element.dispose(this);G(this,true);return null},empty:function(){$A(this.childNodes).each(function(L){Element.destroy(L)});return this},dispose:function(){return(this.parentNode)?this.parentNode.removeChild(this):this},hasChild:function(L){L=document.id(L,true);if(!L){return false}if(Browser.Engine.webkit&&Browser.Engine.version<420){return $A(this.ge
 tElementsByTagName(L.tagName)).contains(L)}return(this.contains)?(this!=L&&this.contains(L)):!!(this.compareDocumentPosition(L)&16)},match:function(L){return(!L||(L==this)||(Element.get(this,"tag")==L))}});Native.implement([Element,Window,Document],{addListener:function(O,N){if(O=="unload"){var L=N,M=this;N=function(){M.removeListener("unload",N);L()}}else{H[this.uid]=this}if(this.addEventListener){this.addEventListener(O,N,false)}else{this.attachEvent("on"+O,N)}return this},removeListener:function(M,L){if(this.removeEventListener){this.removeEventListener(M,L,false)}else{this.detachEvent("on"+M,L)}return this},retrieve:function(M,L){var O=C(this.uid),N=O[M];if(L!=undefined&&N==undefined){N=O[M]=L}return $pick(N)},store:function(M,L){var N=C(this.uid);N[M]=L;return this},eliminate:function(L){var M=C(this.uid);delete M[L];return this}});window.addListener("unload",D)})();Element.Properties=new Hash;Element.Properties.style={set:function(A){this.style.cssText=A},get:function(
 ){return this.style.cssText},erase:function(){this.style.cssText=""}};Element.Properties.tag={get:function(){return this.tagName.toLowerCase()}};Element.Properties.html=(function(){var C=document.createElement("div");var A={table:[1,"<table>","</table>"],select:[1,"<select>","</select>"],tbody:[2,"<table><tbody>","</tbody></table>"],tr:[3,"<table><tbody><tr>","</tr></tbody></table>"]};A.thead=A.tfoot=A.tbody;var B={set:function(){var E=Array.flatten(arguments).join("");var F=Browser.Engine.trident&&A[this.get("tag")];if(F){var G=C;G.innerHTML=F[1]+E+F[2];for(var D=F[0];D--;){G=G.firstChild}this.empty().adopt(G.childNodes)}else{this.innerHTML=E}}};B.erase=B.set;return B})();if(Browser.Engine.webkit&&Browser.Engine.version<420){Element.Properties.text={get:function(){if(this.innerText){return this.innerText}var A=this.ownerDocument.newElement("div",{html:this.innerHTML}).inject(this.ownerDocument.body);var B=A.innerText;A.destroy();return B}}}Element.Properties.events={set:fun
 ction(A){this.addEvents(A)}};Native.implement([Element,Window,Document],{addEvent:function(E,G){var H=this.retrieve("events",{});H[E]=H[E]||{keys:[],values:[]};if(H[E].keys.contains(G)){return this}H[E].keys.push(G);var F=E,A=Element.Events.get(E),C=G,I=this;if(A){if(A.onAdd){A.onAdd.call(this,G)}if(A.condition){C=function(J){if(A.condition.call(this,J)){return G.call(this,J)}return true}}F=A.base||F}var D=function(){return G.call(I)};var B=Element.NativeEvents[F];if(B){if(B==2){D=function(J){J=new Event(J,I.getWindow());if(C.call(I,J)===false){J.stop()}}}this.addListener(F,D)}H[E].values.push(D);return this},removeEvent:function(C,B){var A=this.retrieve("events");if(!A||!A[C]){return this}var F=A[C].keys.indexOf(B);if(F==-1){return this}A[C].keys.splice(F,1);var E=A[C].values.splice(F,1)[0];var D=Element.Events.get(C);if(D){if(D.onRemove){D.onRemove.call(this,B)}C=D.base||C}return(Element.NativeEvents[C])?this.removeListener(C,E):this},addEvents:function(A){for(var B in A){
 this.addEvent(B,A[B])}return this},removeEvents:function(A){var C;if($type(A)=="object"){for(C in A){this.removeEvent(C,A[C])}return this}var B=this.retrieve("events");if(!B){return this}if(!A){for(C in B){this.removeEvents(C)}this.eliminate("events")}else{if(B[A]){while(B[A].keys[0]){this.removeEvent(A,B[A].keys[0])}B[A]=null}}return this},fireEvent:function(D,B,A){var C=this.retrieve("events");if(!C||!C[D]){return this}C[D].keys.each(function(E){E.create({bind:this,delay:A,"arguments":B})()},this);return this},cloneEvents:function(D,A){D=document.id(D);var C=D.retrieve("events");if(!C){return this}if(!A){for(var B in C){this.cloneEvents(D,B)}}else{if(C[A]){C[A].keys.each(function(E){this.addEvent(A,E)},this)}}return this}});Element.NativeEvents={click:2,dblclick:2,mouseup:2,mousedown:2,contextmenu:2,mousewheel:2,DOMMouseScroll:2,mouseover:2,mouseout:2,mousemove:2,selectstart:2,selectend:2,keydown:2,keypress:2,keyup:2,focus:2,blur:2,change:2,reset:2,select:2,submit:2,load:1
 ,unload:1,beforeunload:2,resize:1,move:1,DOMContentLoaded:1,readystatechange:1,error:1,abort:1,scroll:1};(function(){var A=function(B){var C=B.relatedTarget;if(C==undefined){return true}if(C===false){return false}return($type(this)!="document"&&C!=this&&C.prefix!="xul"&&!this.hasChild(C))};Element.Events=new Hash({mouseenter:{base:"mouseover",condition:A},mouseleave:{base:"mouseout",condition:A},mousewheel:{base:(Browser.Engine.gecko)?"DOMMouseScroll":"mousewheel"}})})();Element.Properties.styles={set:function(A){this.setStyles(A)}};Element.Properties.opacity={set:function(A,B){if(!B){if(A==0){if(this.style.visibility!="hidden"){this.style.visibility="hidden"}}else{if(this.style.visibility!="visible"){this.style.visibility="visible"}}}if(!this.currentStyle||!this.currentStyle.hasLayout){this.style.zoom=1}if(Browser.Engine.trident){this.style.filter=(A==1)?"":"alpha(opacity="+A*100+")"}this.style.opacity=A;this.store("opacity",A)},get:function(){return this.retrieve("opacity"
 ,1)}};Element.implement({setOpacity:function(A){return this.set("opacity",A,true)},getOpacity:function(){return this.get("opacity")},setStyle:function(B,A){switch(B){case"opacity":return this.set("opacity",parseFloat(A));case"float":B=(Browser.Engine.trident)?"styleFloat":"cssFloat"}B=B.camelCase();if($type(A)!="string"){var C=(Element.Styles.get(B)||"@").split(" ");A=$splat(A).map(function(E,D){if(!C[D]){return""}return($type(E)=="number")?C[D].replace("@",Math.round(E)):E}).join(" ")}else{if(A==String(Number(A))){A=Math.round(A)}}this.style[B]=A;return this},getStyle:function(G){switch(G){case"opacity":return this.get("opacity");case"float":G=(Browser.Engine.trident)?"styleFloat":"cssFloat"}G=G.camelCase();var A=this.style[G];if(!$chk(A)){A=[];for(var F in Element.ShortStyles){if(G!=F){continue}for(var E in Element.ShortStyles[F]){A.push(this.getStyle(E))}return A.join(" ")}A=this.getComputedStyle(G)}if(A){A=String(A);var C=A.match(/rgba?\([\d\s,]+\)/);if(C){A=A.replace(C[
 0],C[0].rgbToHex())}}if(Browser.Engine.presto||(Browser.Engine.trident&&!$chk(parseInt(A,10)))){if(G.test(/^(height|width)$/)){var B=(G=="width")?["left","right"]:["top","bottom"],D=0;B.each(function(H){D+=this.getStyle("border-"+H+"-width").toInt()+this.getStyle("padding-"+H).toInt()},this);return this["offset"+G.capitalize()]-D+"px"}if((Browser.Engine.presto)&&String(A).test("px")){return A}if(G.test(/(border(.+)Width|margin|padding)/)){return"0px"}}return A},setStyles:function(B){for(var A in B){this.setStyle(A,B[A])}return this},getStyles:function(){var A={};Array.flatten(arguments).each(function(B){A[B]=this.getStyle(B)},this);return A}});Element.Styles=new Hash({left:"@px",top:"@px",bottom:"@px",right:"@px",width:"@px",height:"@px",maxWidth:"@px",maxHeight:"@px",minWidth:"@px",minHeight:"@px",backgroundColor:"rgb(@, @, @)",backgroundPosition:"@px @px",color:"rgb(@, @, @)",fontSize:"@px",letterSpacing:"@px",lineHeight:"@px",clip:"rect(@px @px @px @px)",margin:"@px @px @
 px @px",padding:"@px @px @px @px",border:"@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)",borderWidth:"@px @px @px @px",borderStyle:"@ @ @ @",borderColor:"rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)",zIndex:"@",zoom:"@",fontWeight:"@",textIndent:"@px",opacity:"@"});Element.ShortStyles={margin:{},padding:{},border:{},borderWidth:{},borderStyle:{},borderColor:{}};["Top","Right","Bottom","Left"].each(function(G){var F=Element.ShortStyles;var B=Element.Styles;["margin","padding"].each(function(H){var I=H+G;F[H][I]=B[I]="@px"});var E="border"+G;F.border[E]=B[E]="@px @ rgb(@, @, @)";var D=E+"Width",A=E+"Style",C=E+"Color";F[E]={};F.borderWidth[D]=F[E][D]=B[D]="@px";F.borderStyle[A]=F[E][A]=B[A]="@";F.borderColor[C]=F[E][C]=B[C]="rgb(@, @, @)"});(function(){Element.implement({scrollTo:function(H,I){if(B(this)){this.getWindow().scrollTo(H,I)}else{this.scrollLeft=H;this.scrollTop=I}return this},getSize:function(){if(B(this)){return this.getWindow().getSize()}return{
 x:this.offsetWidth,y:this.offsetHeight}},getScrollSize:function(){if(B(this)){return this.getWindow().getScrollSize()}return{x:this.scrollWidth,y:this.scrollHeight}},getScroll:function(){if(B(this)){return this.getWindow().getScroll()}return{x:this.scrollLeft,y:this.scrollTop}},getScrolls:function(){var I=this,H={x:0,y:0};while(I&&!B(I)){H.x+=I.scrollLeft;H.y+=I.scrollTop;I=I.parentNode}return H},getOffsetParent:function(){var H=this;if(B(H)){return null}if(!Browser.Engine.trident){return H.offsetParent}while((H=H.parentNode)&&!B(H)){if(D(H,"position")!="static"){return H}}return null},getOffsets:function(){if(this.getBoundingClientRect){var J=this.getBoundingClientRect(),M=document.id(this.getDocument().documentElement),P=M.getScroll(),K=this.getScrolls(),I=this.getScroll(),H=(D(this,"position")=="fixed");return{x:J.left.toInt()+K.x-I.x+((H)?0:P.x)-M.clientLeft,y:J.top.toInt()+K.y-I.y+((H)?0:P.y)-M.clientTop}}var L=this,N={x:0,y:0};if(B(this)){return N}while(L&&!B(L)){N.x+=
 L.offsetLeft;N.y+=L.offsetTop;if(Browser.Engine.gecko){if(!F(L)){N.x+=C(L);N.y+=G(L)}var O=L.parentNode;if(O&&D(O,"overflow")!="visible"){N.x+=C(O);N.y+=G(O)}}else{if(L!=this&&Browser.Engine.webkit){N.x+=C(L);N.y+=G(L)}}L=L.offsetParent}if(Browser.Engine.gecko&&!F(this)){N.x-=C(this);N.y-=G(this)}return N},getPosition:function(K){if(B(this)){return{x:0,y:0}}var L=this.getOffsets(),I=this.getScrolls();var H={x:L.x-I.x,y:L.y-I.y};var J=(K&&(K=document.id(K)))?K.getPosition():{x:0,y:0};return{x:H.x-J.x,y:H.y-J.y}},getCoordinates:function(J){if(B(this)){return this.getWindow().getCoordinates()}var H=this.getPosition(J),I=this.getSize();var K={left:H.x,top:H.y,width:I.x,height:I.y};K.right=K.left+K.width;K.bottom=K.top+K.height;return K},computePosition:function(H){return{left:H.x-E(this,"margin-left"),top:H.y-E(this,"margin-top")}},setPosition:function(H){return this.setStyles(this.computePosition(H))}});Native.implement([Document,Window],{getSize:function(){if(Browser.Engine.pr
 esto||Browser.Engine.webkit){var I=this.getWindow();return{x:I.innerWidth,y:I.innerHeight}}var H=A(this);return{x:H.clientWidth,y:H.clientHeight}},getScroll:function(){var I=this.getWindow(),H=A(this);return{x:I.pageXOffset||H.scrollLeft,y:I.pageYOffset||H.scrollTop}},getScrollSize:function(){var I=A(this),H=this.getSize();return{x:Math.max(I.scrollWidth,H.x),y:Math.max(I.scrollHeight,H.y)}},getPosition:function(){return{x:0,y:0}},getCoordinates:function(){var H=this.getSize();return{top:0,left:0,bottom:H.y,right:H.x,height:H.y,width:H.x}}});var D=Element.getComputedStyle;function E(H,I){return D(H,I).toInt()||0}function F(H){return D(H,"-moz-box-sizing")=="border-box"}function G(H){return E(H,"border-top-width")}function C(H){return E(H,"border-left-width")}function B(H){return(/^(?:body|html)$/i).test(H.tagName)}function A(H){var I=H.getDocument();return(!I.compatMode||I.compatMode=="CSS1Compat")?I.html:I.body}})();Element.alias("setPosition","position");Native.implement([
 Window,Document,Element],{getHeight:function(){return this.getSize().y},getWidth:function(){return this.getSize().x},getScrollTop:function(){return this.getScroll().y},getScrollLeft:function(){return this.getScroll().x},getScrollHeight:function(){return this.getScrollSize().y},getScrollWidth:function(){return this.getScrollSize().x},getTop:function(){return this.getPosition().y},getLeft:function(){return this.getPosition().x}});Native.implement([Document,Element],{getElements:function(H,G){H=H.split(",");var C,E={};for(var D=0,B=H.length;D<B;D++){var A=H[D],F=Selectors.Utils.search(this,A,E);if(D!=0&&F.item){F=$A(F)}C=(D==0)?F:(C.item)?$A(C).concat(F):C.concat(F)}return new Elements(C,{ddup:(H.length>1),cash:!G})}});Element.implement({match:function(B){if(!B||(B==this)){return true}var D=Selectors.Utils.parseTagAndID(B);var A=D[0],E=D[1];if(!Selectors.Filters.byID(this,E)||!Selectors.Filters.byTag(this,A)){return false}var C=Selectors.Utils.parseSelector(B);return(C)?Selecto
 rs.Utils.filter(this,C,{}):true}});var Selectors={Cache:{nth:{},parsed:{}}};Selectors.RegExps={id:(/#([\w-]+)/),tag:(/^(\w+|\*)/),quick:(/^(\w+|\*)$/),splitter:(/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),combined:(/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)};Selectors.Utils={chk:function(B,C){if(!C){return true}var A=$uid(B);if(!C[A]){return C[A]=true}return false},parseNthArgument:function(F){if(Selectors.Cache.nth[F]){return Selectors.Cache.nth[F]}var C=F.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);if(!C){return false}var E=parseInt(C[1],10);var B=(E||E===0)?E:1;var D=C[2]||false;var A=parseInt(C[3],10)||0;if(B!=0){A--;while(A<1){A+=B}while(A>=B){A-=B}}else{B=A;D="index"}switch(D){case"n":C={a:B,b:A,special:"n"};break;case"odd":C={a:2,b:0,special:"n"};break;case"even":C={a:2,b:1,special:"n"};break;case"first":C={a:0,special:"index"};break;case"last":C={special:"last-child"};break;case"only":C={special:"only-child"};break;default:
 C={a:(B-1),special:"index"}}return Selectors.Cache.nth[F]=C},parseSelector:function(E){if(Selectors.Cache.parsed[E]){return Selectors.Cache.parsed[E]}var D,H={classes:[],pseudos:[],attributes:[]};while((D=Selectors.RegExps.combined.exec(E))){var I=D[1],G=D[2],F=D[3],B=D[5],C=D[6],J=D[7];if(I){H.classes.push(I)}else{if(C){var A=Selectors.Pseudo.get(C);if(A){H.pseudos.push({parser:A,argument:J})}else{H.attributes.push({name:C,operator:"=",value:J})}}else{if(G){H.attributes.push({name:G,operator:F,value:B})}}}}if(!H.classes.length){delete H.classes}if(!H.attributes.length){delete H.attributes}if(!H.pseudos.length){delete H.pseudos}if(!H.classes&&!H.attributes&&!H.pseudos){H=null}return Selectors.Cache.parsed[E]=H},parseTagAndID:function(B){var A=B.match(Selectors.RegExps.tag);var C=B.match(Selectors.RegExps.id);return[(A)?A[1]:"*",(C)?C[1]:false]},filter:function(F,C,E){var D;if(C.classes){for(D=C.classes.length;D--;D){var G=C.classes[D];if(!Selectors.Filters.byClass(F,G)){retu
 rn false}}}if(C.attributes){for(D=C.attributes.length;D--;D){var B=C.attributes[D];if(!Selectors.Filters.byAttribute(F,B.name,B.operator,B.value)){return false}}}if(C.pseudos){for(D=C.pseudos.length;D--;D){var A=C.pseudos[D];if(!Selectors.Filters.byPseudo(F,A.parser,A.argument,E)){return false}}}return true},getByTagAndID:function(B,A,D){if(D){var C=(B.getElementById)?B.getElementById(D,true):Element.getElementById(B,D,true);return(C&&Selectors.Filters.byTag(C,A))?[C]:[]}else{return B.getElementsByTagName(A)}},search:function(I,H,N){var B=[];var C=H.trim().replace(Selectors.RegExps.splitter,function(Y,X,W){B.push(X);return":)"+W}).split(":)");var J,E,U;for(var T=0,P=C.length;T<P;T++){var S=C[T];if(T==0&&Selectors.RegExps.quick.test(S)){J=I.getElementsByTagName(S);continue}var A=B[T-1];var K=Selectors.Utils.parseTagAndID(S);var V=K[0],L=K[1];if(T==0){J=Selectors.Utils.getByTagAndID(I,V,L)}else{var D={},G=[];for(var R=0,Q=J.length;R<Q;R++){G=Selectors.Getters[A](G,J[R],V,L,D)}
 J=G}var F=Selectors.Utils.parseSelector(S);if(F){E=[];for(var O=0,M=J.length;O<M;O++){U=J[O];if(Selectors.Utils.filter(U,F,N)){E.push(U)}}J=E}}return J}};Selectors.Getters={" ":function(H,G,I,A,E){var D=Selectors.Utils.getByTagAndID(G,I,A);for(var C=0,B=D.length;C<B;C++){var F=D[C];if(Selectors.Utils.chk(F,E)){H.push(F)}}return H},">":function(H,G,I,A,F){var C=Selectors.Utils.getByTagAndID(G,I,A);for(var E=0,D=C.length;E<D;E++){var B=C[E];if(B.parentNode==G&&Selectors.Utils.chk(B,F)){H.push(B)}}return H},"+":function(C,B,A,E,D){while((B=B.nextSibling)){if(B.nodeType==1){if(Selectors.Utils.chk(B,D)&&Selectors.Filters.byTag(B,A)&&Selectors.Filters.byID(B,E)){C.push(B)}break}}return C},"~":function(C,B,A,E,D){while((B=B.nextSibling)){if(B.nodeType==1){if(!Selectors.Utils.chk(B,D)){break}if(Selectors.Filters.byTag(B,A)&&Selectors.Filters.byID(B,E)){C.push(B)}}}return C}};Selectors.Filters={byTag:function(B,A){return(A=="*"||(B.tagName&&B.tagName.toLowerCase()==A))},byID:function
 (A,B){return(!B||(A.id&&A.id==B))},byClass:function(B,A){return(B.className&&B.className.contains&&B.className.contains(A," "))},byPseudo:function(A,D,C,B){return D.call(A,C,B)},byAttribute:function(C,D,B,E){var A=Element.prototype.getProperty.call(C,D);if(!A){return(B=="!=")}if(!B||E==undefined){return true}switch(B){case"=":return(A==E);case"*=":return(A.contains(E));case"^=":return(A.substr(0,E.length)==E);case"$=":return(A.substr(A.length-E.length)==E);case"!=":return(A!=E);case"~=":return A.contains(E," ");case"|=":return A.contains(E,"-")}return false}};Selectors.Pseudo=new Hash({checked:function(){return this.checked},empty:function(){return !(this.innerText||this.textContent||"").length},not:function(A){return !Element.match(this,A)},contains:function(A){return(this.innerText||this.textContent||"").contains(A)},"first-child":function(){return Selectors.Pseudo.index.call(this,0)},"last-child":function(){var A=this;while((A=A.nextSibling)){if(A.nodeType==1){return fals
 e}}return true},"only-child":function(){var B=this;while((B=B.previousSibling)){if(B.nodeType==1){return false}}var A=this;while((A=A.nextSibling)){if(A.nodeType==1){return false}}return true},"nth-child":function(G,E){G=(G==undefined)?"n":G;var C=Selectors.Utils.parseNthArgument(G);if(C.special!="n"){return Selectors.Pseudo[C.special].call(this,C.a,E)}var F=0;E.positions=E.positions||{};var D=$uid(this);if(!E.positions[D]){var B=this;while((B=B.previousSibling)){if(B.nodeType!=1){continue}F++;var A=E.positions[$uid(B)];if(A!=undefined){F=A+F;break}}E.positions[D]=F}return(E.positions[D]%C.a==C.b)},index:function(A){var B=this,C=0;while((B=B.previousSibling)){if(B.nodeType==1&&++C>A){return false}}return(C==A)},even:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n+1",A)},odd:function(B,A){return Selectors.Pseudo["nth-child"].call(this,"2n",A)},selected:function(){return this.selected},enabled:function(){return(this.disabled===false)}});Element.Events.domready
 ={onAdd:function(A){if(Browser.loaded){A.call(this)}}};(function(){var B=function(){if(Browser.loaded){return }Browser.loaded=true;window.fireEvent("domready");document.fireEvent("domready")};window.addEvent("load",B);if(Browser.Engine.trident){var A=document.createElement("div");(function(){($try(function(){A.doScroll();return document.id(A).inject(document.body).set("html","temp").dispose()}))?B():arguments.callee.delay(50)})()}else{if(Browser.Engine.webkit&&Browser.Engine.version<525){(function(){(["loaded","complete"].contains(document.readyState))?B():arguments.callee.delay(50)})()}else{document.addEvent("DOMContentLoaded",B)}}})();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(A){return JSON.$specialChars[A]||"\\u00"+Math.floor(A.charCodeAt()/16).toString(16)+(A.charCodeAt()%16).toString(16)},encode:function(B){switch($t
 ype(B)){case"string":return'"'+B.replace(/[\x00-\x1f\\"]/g,JSON.$replaceChars)+'"';case"array":return"["+String(B.map(JSON.encode).clean())+"]";case"object":case"hash":var A=[];Hash.each(B,function(E,D){var C=JSON.encode(E);if(C){A.push(JSON.encode(D)+":"+C)}});return"{"+A+"}";case"number":case"boolean":return String(B);case false:return"null"}return null},decode:function(string,secure){if($type(string)!="string"||!string.length){return null}if(secure&&!(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g,"@").replace(/"[^"\\\n\r]*"/g,""))){return null}return eval("("+string+")")}});Native.implement([Hash,Array,String,Number],{toJSON:function(){return JSON.encode(this)}});var Cookie=new Class({Implements:Options,options:{path:false,domain:false,duration:false,secure:false,document:document},initialize:function(B,A){this.key=B;this.setOptions(A)},write:function(B){B=encodeURIComponent(B);if(this.options.domain){B+="; domain="+this.options.domain}if(this.options
 .path){B+="; path="+this.options.path}if(this.options.duration){var A=new Date();A.setTime(A.getTime()+this.options.duration*24*60*60*1000);B+="; expires="+A.toGMTString()}if(this.options.secure){B+="; secure"}this.options.document.cookie=this.key+"="+B;return this},read:function(){var A=this.options.document.cookie.match("(?:^|;)\\s*"+this.key.escapeRegExp()+"=([^;]*)");return(A)?decodeURIComponent(A[1]):null},dispose:function(){new Cookie(this.key,$merge(this.options,{duration:-1})).write("");return this}});Cookie.write=function(B,C,A){return new Cookie(B,A).write(C)};Cookie.read=function(A){return new Cookie(A).read()};Cookie.dispose=function(B,A){return new Cookie(B,A).dispose()};var Swiff=new Class({Implements:[Options],options:{id:null,height:1,width:1,container:null,properties:{},params:{quality:"high",allowScriptAccess:"always",wMode:"transparent",swLiveConnect:true},callBacks:{},vars:{}},toElement:function(){return this.object},initialize:function(L,M){this.instance
 ="Swiff_"+$time();this.setOptions(M);M=this.options;var B=this.id=M.id||this.instance;var A=document.id(M.container);Swiff.CallBacks[this.instance]={};var E=M.params,G=M.vars,F=M.callBacks;var H=$extend({height:M.height,width:M.width},M.properties);var K=this;for(var D in F){Swiff.CallBacks[this.instance][D]=(function(N){return function(){return N.apply(K.object,arguments)}})(F[D]);G[D]="Swiff.CallBacks."+this.instance+"."+D}E.flashVars=Hash.toQueryString(G);if(Browser.Engine.trident){H.classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";E.movie=L}else{H.type="application/x-shockwave-flash";H.data=L}var J='<object id="'+B+'"';for(var I in H){J+=" "+I+'="'+H[I]+'"'}J+=">";for(var C in E){if(E[C]){J+='<param name="'+C+'" value="'+E[C]+'" />'}}J+="</object>";this.object=((A)?A.empty():new Element("div")).set("html",J).firstChild},replaces:function(A){A=document.id(A,true);A.parentNode.replaceChild(this.toElement(),A);return this},inject:function(A){document.id(A,true).appendCh
 ild(this.toElement());return this},remote:function(){return Swiff.remote.apply(Swiff,[this.toElement()].extend(arguments))}});Swiff.CallBacks={};Swiff.remote=function(obj,fn){var rs=obj.CallFunction('<invoke name="'+fn+'" returntype="javascript">'+__flash__argumentsToXML(arguments,2)+"</invoke>");return eval(rs)};var Fx=new Class({Implements:[Chain,Events,Options],options:{fps:50,unit:false,duration:500,link:"ignore"},initialize:function(A){this.subject=this.subject||this;this.setOptions(A);this.options.duration=Fx.Durations[this.options.duration]||this.options.duration.toInt();var B=this.options.wait;if(B===false){this.options.link="cancel"}},getTransition:function(){return function(A){return -(Math.cos(Math.PI*A)-1)/2}},step:function(){var A=$time();if(A<this.time+this.options.duration){var B=this.transition((A-this.time)/this.options.duration);this.set(this.compute(this.from,this.to,B))}else{this.set(this.compute(this.from,this.to,1));this.complete()}},set:function(A){ret
 urn A},compute:function(C,B,A){return Fx.compute(C,B,A)},check:function(){if(!this.timer){return true}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false}return false},start:function(B,A){if(!this.check(B,A)){return this}this.from=B;this.to=A;this.time=0;this.transition=this.getTransition();this.startTimer();this.onStart();return this},complete:function(){if(this.stopTimer()){this.onComplete()}return this},cancel:function(){if(this.stopTimer()){this.onCancel()}return this},onStart:function(){this.fireEvent("start",this.subject)},onComplete:function(){this.fireEvent("complete",this.subject);if(!this.callChain()){this.fireEvent("chainComplete",this.subject)}},onCancel:function(){this.fireEvent("cancel",this.subject).clearChain()},pause:function(){this.stopTimer();return this},resume:function(){this.startTimer();return this},stopTimer:function(){if(!this.timer){return false}this.time=$time()-this
 .time;this.timer=$clear(this.timer);return true},startTimer:function(){if(this.timer){return false}this.time=$time()-this.time;this.timer=this.step.periodical(Math.round(1000/this.options.fps),this);return true}});Fx.compute=function(C,B,A){return(B-C)*A+C};Fx.Durations={"short":250,normal:500,"long":1000};Fx.CSS=new Class({Extends:Fx,prepare:function(D,E,B){B=$splat(B);var C=B[1];if(!$chk(C)){B[1]=B[0];B[0]=D.getStyle(E)}var A=B.map(this.parse);return{from:A[0],to:A[1]}},parse:function(A){A=$lambda(A)();A=(typeof A=="string")?A.split(" "):$splat(A);return A.map(function(C){C=String(C);var B=false;Fx.CSS.Parsers.each(function(F,E){if(B){return }var D=F.parse(C);if($chk(D)){B={value:D,parser:F}}});B=B||{value:C,parser:Fx.CSS.Parsers.String};return B})},compute:function(D,C,B){var A=[];(Math.min(D.length,C.length)).times(function(E){A.push({value:D[E].parser.compute(D[E].value,C[E].value,B),parser:D[E].parser})});A.$family={name:"fx:css:value"};return A},serve:function(C,B){if
 ($type(C)!="fx:css:value"){C=this.parse(C)}var A=[];C.each(function(D){A=A.concat(D.parser.serve(D.value,B))});return A},render:function(A,D,C,B){A.setStyle(D,this.serve(C,B))},search:function(A){if(Fx.CSS.Cache[A]){return Fx.CSS.Cache[A]}var B={};Array.each(document.styleSheets,function(E,D){var C=E.href;if(C&&C.contains("://")&&!C.contains(document.domain)){return }var F=E.rules||E.cssRules;Array.each(F,function(I,G){if(!I.style){return }var H=(I.selectorText)?I.selectorText.replace(/^\w+/,function(J){return J.toLowerCase()}):null;if(!H||!H.test("^"+A+"$")){return }Element.Styles.each(function(K,J){if(!I.style[J]||Element.ShortStyles[J]){return }K=String(I.style[J]);B[J]=(K.test(/^rgb/))?K.rgbToHex():K})})});return Fx.CSS.Cache[A]=B}});Fx.CSS.Cache={};Fx.CSS.Parsers=new Hash({Color:{parse:function(A){if(A.match(/^#[0-9a-f]{3,6}$/i)){return A.hexToRgb(true)}return((A=A.match(/(\d+),\s*(\d+),\s*(\d+)/)))?[A[1],A[2],A[3]]:false},compute:function(C,B,A){return C.map(function(E
 ,D){return Math.round(Fx.compute(C[D],B[D],A))})},serve:function(A){return A.map(Number)}},Number:{parse:parseFloat,compute:Fx.compute,serve:function(B,A){return(A)?B+A:B}},String:{parse:$lambda(false),compute:$arguments(1),serve:$arguments(0)}});Fx.Tween=new Class({Extends:Fx.CSS,initialize:function(B,A){this.element=this.subject=document.id(B);this.parent(A)},set:function(B,A){if(arguments.length==1){A=B;B=this.property||this.options.property}this.render(this.element,B,A,this.options.unit);return this},start:function(C,E,D){if(!this.check(C,E,D)){return this}var B=Array.flatten(arguments);this.property=this.options.property||B.shift();var A=this.prepare(this.element,this.property,B);return this.parent(A.from,A.to)}});Element.Properties.tween={set:function(A){var B=this.retrieve("tween");if(B){B.cancel()}return this.eliminate("tween").store("tween:options",$extend({link:"cancel"},A))},get:function(A){if(A||!this.retrieve("tween")){if(A||!this.retrieve("tween:options")){this
 .set("tween",A)}this.store("tween",new Fx.Tween(this,this.retrieve("tween:options")))}return this.retrieve("tween")}};Element.implement({tween:function(A,C,B){this.get("tween").start(arguments);return this},fade:function(C){var E=this.get("tween"),D="opacity",A;C=$pick(C,"toggle");switch(C){case"in":E.start(D,1);break;case"out":E.start(D,0);break;case"show":E.set(D,1);break;case"hide":E.set(D,0);break;case"toggle":var B=this.retrieve("fade:flag",this.get("opacity")==1);E.start(D,(B)?0:1);this.store("fade:flag",!B);A=true;break;default:E.start(D,arguments)}if(!A){this.eliminate("fade:flag")}return this},highlight:function(C,A){if(!A){A=this.retrieve("highlight:original",this.getStyle("background-color"));A=(A=="transparent")?"#fff":A}var B=this.get("tween");B.start("background-color",C||"#ffff88",A).chain(function(){this.setStyle("background-color",this.retrieve("highlight:original"));B.callChain()}.bind(this));return this}});Fx.Morph=new Class({Extends:Fx.CSS,initialize:func
 tion(B,A){this.element=this.subject=document.id(B);this.parent(A)},set:function(A){if(typeof A=="string"){A=this.search(A)}for(var B in A){this.render(this.element,B,A[B],this.options.unit)}return this},compute:function(E,D,C){var A={};for(var B in E){A[B]=this.parent(E[B],D[B],C)}return A},start:function(B){if(!this.check(B)){return this}if(typeof B=="string"){B=this.search(B)}var E={},D={};for(var C in B){var A=this.prepare(this.element,C,B[C]);E[C]=A.from;D[C]=A.to}return this.parent(E,D)}});Element.Properties.morph={set:function(A){var B=this.retrieve("morph");if(B){B.cancel()}return this.eliminate("morph").store("morph:options",$extend({link:"cancel"},A))},get:function(A){if(A||!this.retrieve("morph")){if(A||!this.retrieve("morph:options")){this.set("morph",A)}this.store("morph",new Fx.Morph(this,this.retrieve("morph:options")))}return this.retrieve("morph")}};Element.implement({morph:function(A){this.get("morph").start(A);return this}});Fx.implement({getTransition:func
 tion(){var A=this.options.transition||Fx.Transitions.Sine.easeInOut;if(typeof A=="string"){var B=A.split(":");A=Fx.Transitions;A=A[B[0]]||A[B[0].capitalize()];if(B[1]){A=A["ease"+B[1].capitalize()+(B[2]?B[2].capitalize():"")]}}return A}});Fx.Transition=function(B,A){A=$splat(A);return $extend(B,{easeIn:function(C){return B(C,A)},easeOut:function(C){return 1-B(1-C,A)},easeInOut:function(C){return(C<=0.5)?B(2*C,A)/2:(2-B(2*(1-C),A))/2}})};Fx.Transitions=new Hash({linear:$arguments(0)});Fx.Transitions.extend=function(A){for(var B in A){Fx.Transitions[B]=new Fx.Transition(A[B])}};Fx.Transitions.extend({Pow:function(B,A){return Math.pow(B,A[0]||6)},Expo:function(A){return Math.pow(2,8*(A-1))},Circ:function(A){return 1-Math.sin(Math.acos(A))},Sine:function(A){return 1-Math.sin((1-A)*Math.PI/2)},Back:function(B,A){A=A[0]||1.618;return Math.pow(B,2)*((A+1)*B-A)},Bounce:function(D){var C;for(var B=0,A=1;1;B+=A,A/=2){if(D>=(7-4*B)/11){C=A*A-Math.pow((11-6*B-11*D)/4,2);break}}return C}
 ,Elastic:function(B,A){return Math.pow(2,10*--B)*Math.cos(20*B*Math.PI*(A[0]||1)/3)}});["Quad","Cubic","Quart","Quint"].each(function(B,A){Fx.Transitions[B]=new Fx.Transition(function(C){return Math.pow(C,[A+2])})});var Request=new Class({Implements:[Chain,Events,Options],options:{url:"",data:"",headers:{"X-Requested-With":"XMLHttpRequest",Accept:"text/javascript, text/html, application/xml, text/xml, */*"},async:true,format:false,method:"post",link:"ignore",isSuccess:null,emulation:true,urlEncoded:true,encoding:"utf-8",evalScripts:false,evalResponse:false,noCache:false},initialize:function(A){this.xhr=new Browser.Request();this.setOptions(A);this.options.isSuccess=this.options.isSuccess||this.isSuccess;this.headers=new Hash(this.options.headers)},onStateChange:function(){if(this.xhr.readyState!=4||!this.running){return }this.running=false;this.status=0;$try(function(){this.status=this.xhr.status}.bind(this));this.xhr.onreadystatechange=$empty;if(this.options.isSuccess.call(
 this,this.status)){this.response={text:this.xhr.responseText,xml:this.xhr.responseXML};this.success(this.response.text,this.response.xml)}else{this.response={text:null,xml:null};this.failure()}},isSuccess:function(){return((this.status>=200)&&(this.status<300))},processScripts:function(A){if(this.options.evalResponse||(/(ecma|java)script/).test(this.getHeader("Content-type"))){return $exec(A)}return A.stripScripts(this.options.evalScripts)},success:function(B,A){this.onSuccess(this.processScripts(B),A)},onSuccess:function(){this.fireEvent("complete",arguments).fireEvent("success",arguments).callChain()},failure:function(){this.onFailure()},onFailure:function(){this.fireEvent("complete").fireEvent("failure",this.xhr)},setHeader:function(A,B){this.headers.set(A,B);return this},getHeader:function(A){return $try(function(){return this.xhr.getResponseHeader(A)}.bind(this))},check:function(){if(!this.running){return true}switch(this.options.link){case"cancel":this.cancel();return 
 true;case"chain":this.chain(this.caller.bind(this,arguments));return false}return false},send:function(K){if(!this.check(K)){return this}this.running=true;var I=$type(K);if(I=="string"||I=="element"){K={data:K}}var D=this.options;K=$extend({data:D.data,url:D.url,method:D.method},K);var G=K.data,B=String(K.url),A=K.method.toLowerCase();switch($type(G)){case"element":G=document.id(G).toQueryString();break;case"object":case"hash":G=Hash.toQueryString(G)}if(this.options.format){var J="format="+this.options.format;G=(G)?J+"&"+G:J}if(this.options.emulation&&!["get","post"].contains(A)){var H="_method="+A;G=(G)?H+"&"+G:H;A="post"}if(this.options.urlEncoded&&A=="post"){var C=(this.options.encoding)?"; charset="+this.options.encoding:"";this.headers.set("Content-type","application/x-www-form-urlencoded"+C)}if(this.options.noCache){var F="noCache="+new Date().getTime();G=(G)?F+"&"+G:F}var E=B.lastIndexOf("/");if(E>-1&&(E=B.indexOf("#"))>-1){B=B.substr(0,E)}if(G&&A=="get"){B=B+(B.conta
 ins("?")?"&":"?")+G;G=null}this.xhr.open(A.toUpperCase(),B,this.options.async);this.xhr.onreadystatechange=this.onStateChange.bind(this);this.headers.each(function(M,L){try{this.xhr.setRequestHeader(L,M)}catch(N){this.fireEvent("exception",[L,M])}},this);this.fireEvent("request");this.xhr.send(G);if(!this.options.async){this.onStateChange()}return this},cancel:function(){if(!this.running){return this}this.running=false;this.xhr.abort();this.xhr.onreadystatechange=$empty;this.xhr=new Browser.Request();this.fireEvent("cancel");return this}});(function(){var A={};["get","post","put","delete","GET","POST","PUT","DELETE"].each(function(B){A[B]=function(){var C=Array.link(arguments,{url:String.type,data:$defined});return this.send($extend(C,{method:B}))}});Request.implement(A)})();Element.Properties.send={set:function(A){var B=this.retrieve("send");if(B){B.cancel()}return this.eliminate("send").store("send:options",$extend({data:this,link:"cancel",method:this.get("method")||"post"
 ,url:this.get("action")},A))},get:function(A){if(A||!this.retrieve("send")){if(A||!this.retrieve("send:options")){this.set("send",A)}this.store("send",new Request(this.retrieve("send:options")))}return this.retrieve("send")}};Element.implement({send:function(A){var B=this.get("send");B.send({data:this,url:A||B.options.url});return this}});Request.HTML=new Class({Extends:Request,options:{update:false,append:false,evalScripts:true,filter:false},processHTML:function(C){var B=C.match(/<body[^>]*>([\s\S]*?)<\/body>/i);C=(B)?B[1]:C;var A=new Element("div");return $try(function(){var D="<root>"+C+"</root>",G;if(Browser.Engine.trident){G=new ActiveXObject("Microsoft.XMLDOM");G.async=false;G.loadXML(D)}else{G=new DOMParser().parseFromString(D,"text/xml")}D=G.getElementsByTagName("root")[0];if(!D){return null}for(var F=0,E=D.childNodes.length;F<E;F++){var H=Element.clone(D.childNodes[F],true,true);if(H){A.grab(H)}}return A})||A.set("html",C)},success:function(D){var C=this.options,B=t
 his.response;B.html=D.stripScripts(function(E){B.javascript=E});var A=this.processHTML(B.html);B.tree=A.childNodes;B.elements=A.getElements("*");if(C.filter){B.tree=B.elements.filter(C.filter)}if(C.update){document.id(C.update).empty().set("html",B.html)}else{if(C.append){document.id(C.append).adopt(A.getChildren())}}if(C.evalScripts){$exec(B.javascript)}this.onSuccess(B.tree,B.elements,B.html,B.javascript)}});Element.Properties.load={set:function(A){var B=this.retrieve("load");if(B){B.cancel()}return this.eliminate("load").store("load:options",$extend({data:this,link:"cancel",update:this,method:"get"},A))},get:function(A){if(A||!this.retrieve("load")){if(A||!this.retrieve("load:options")){this.set("load",A)}this.store("load",new Request.HTML(this.retrieve("load:options")))}return this.retrieve("load")}};Element.implement({load:function(){this.get("load").send(Array.link(arguments,{data:Object.type,url:String.type}));return this}});Request.JSON=new Class({Extends:Request,opt
 ions:{secure:true},initialize:function(A){this.parent(A);this.headers.extend({Accept:"application/json","X-Request":"JSON"})},success:function(A){this.response.json=JSON.decode(A,this.options.secure);this.onSuccess(this.response.json,A)}});MooTools.More={version:"1.2.4.4",build:"6f6057dc645fdb7547689183b2311063bd653ddf"};(function(){var A={language:"en-US",languages:{"en-US":{}},cascades:["en-US"]};var B;MooTools.lang=new Events();$extend(MooTools.lang,{setLanguage:function(C){if(!A.languages[C]){return this}A.language=C;this.load();this.fireEvent("langChange",C);return this},load:function(){var C=this.cascade(this.getCurrentLanguage());B={};$each(C,function(E,D){B[D]=this.lambda(E)},this)},getCurrentLanguage:function(){return A.language},addLanguage:function(C){A.languages[C]=A.languages[C]||{};return this},cascade:function(E){var C=(A.languages[E]||{}).cascades||[];C.combine(A.cascades);C.erase(E).push(E);var D=C.map(function(F){return A.languages[F]},this);return $merge.a
 pply(this,D)},lambda:function(C){(C||{}).get=function(E,D){return $lambda(C[E]).apply(this,$splat(D))};return C},get:function(E,D,C){if(B&&B[E]){return(D?B[E].get(D,C):B[E])}},set:function(D,E,C){this.addLanguage(D);langData=A.languages[D];if(!langData[E]){langData[E]={}}$extend(langData[E],C);if(D==this.getCurrentLanguage()){this.load();this.fireEvent("langChange",D)}return this},list:function(){return Hash.getKeys(A.languages)}})})();(function(){var C=this;var B=function(){if(C.console&&console.log){try{console.log.apply(console,arguments)}catch(D){console.log(Array.slice(arguments))}}else{Log.logged.push(arguments)}return this};var A=function(){this.logged.push(arguments);return this};this.Log=new Class({logged:[],log:A,resetLog:function(){this.logged.empty();return this},enableLog:function(){this.log=B;this.logged.each(function(D){this.log.apply(this,D)},this);return this.resetLog()},disableLog:function(){this.log=A;return this}});Log.extend(new Log).enableLog();Log.logg
 er=function(){return this.log.apply(this,arguments)}})();var Depender={options:{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(B){this.log("include: ",B);this.mapLoaded=false;var A=function(C){this.libs=$merge(this.libs,C);$each(this.libs,function(D,E){if(D.scripts){this.loadSource(E,D.scripts)}},this)}.bind(this);if($type(B)=="string"){this.log("fetching libs ",B);this.request(B,A)}else{A(B)}return this},required:[],require:function(B){var A=function(){var C=this.calculateDependencies(B.scripts);if(B.sources){B.sources.each(function(D){C.combine(this.libs[D].files)},this)}if(B.serial){C.combine(this.getLoadedScripts())}B.scripts=C;this.required.push(B);this.fireEvent("require",B);this.loadScripts(B.scripts)};if(this.mapLoaded){A.call(th
 is)}else{this.addEvent("mapLoaded",A.bind(this))}return this},cleanDoubleSlash:function(B){if(!B){return B}var A="";if(B.test(/^http:\/\//)){A="http://";B=B.substring(7,B.length)}B=B.replace(/\/\//g,"/");return A+B},request:function(A,B){new Request.JSON({url:A,secure:false,onSuccess:B}).send()},loadSource:function(B,A){if(this.libs[B].files){this.dataLoaded();return }this.log("loading source: ",A);this.request(this.cleanDoubleSlash(A+"/scripts.json"),function(C){this.log("loaded source: ",A);this.libs[B].files=C;this.dataLoaded()}.bind(this))},dataLoaded:function(){var A=true;$each(this.libs,function(C,B){if(!this.libs[B].files){A=false}},this);if(A){this.mapTree();this.mapLoaded=true;this.calculateLoaded();this.lastLoaded=this.getLoadedScripts().getLength();this.fireEvent("mapLoaded");this.removeEvents("mapLoaded")}},calculateLoaded:function(){var A=function(B){this.scriptsState[B]=true}.bind(this);if(this.options.loadedScripts){this.options.loadedScripts.each(A)}if(this.o
 ptions.loadedSources){this.options.loadedSources.each(function(B){$each(this.libs[B].files,function(C){$each(C,function(E,D){A(D)},this)},this)},this)}},deps:{},pathMap:{},mapTree:function(){$each(this.libs,function(B,A){$each(B.files,function(C,D){$each(C,function(F,E){var G=A+":"+D+":"+E;if(this.deps[G]){return }this.deps[G]=F.deps;this.pathMap[E]=G},this)},this)},this)},getDepsForScript:function(A){return this.deps[this.pathMap[A]]||[]},calculateDependencies:function(A){var B=[];$splat(A).each(function(C){if(C=="None"||!C){return }var D=this.getDepsForScript(C);if(!D){if(window.console&&console.warn){console.warn("dependencies not mapped: script: %o, map: %o, :deps: %o",C,this.pathMap,this.deps)}}else{D.each(function(E){if(E==C||E=="None"||!E){return }if(!B.contains(E)){B.combine(this.calculateDependencies(E))}B.include(E)},this)}B.include(C)},this);return B},getPath:function(A){try{var E=this.pathMap[A].split(":");var D=this.libs[E[0]];var B=(D.path||D.scripts)+"/";E.shi
 ft();return this.cleanDoubleSlash(B+E.join("/")+".js")}catch(C){return A}},loadScripts:function(A){A=A.filter(function(B){if(!this.scriptsState[B]&&B!="None"){this.scriptsState[B]=false;return true}},this);if(A.length){A.each(function(B){this.loadScript(B)},this)}else{this.check()}},toLoad:[],loadScript:function(B){if(this.scriptsState[B]&&this.toLoad.length){this.loadScript(this.toLoad.shift());return }else{if(this.loading){this.toLoad.push(B);return }}var E=function(){this.loading=false;this.scriptLoaded(B);if(this.toLoad.length){this.loadScript(this.toLoad.shift())}}.bind(this);var D=function(){this.log("could not load: ",A)}.bind(this);this.loading=true;var A=this.getPath(B);if(this.options.useScriptInjection){this.log("injecting script: ",A);var C=function(){this.log("loaded script: ",A);E()}.bind(this);new Element("script",{src:A+(this.options.noCache?"?noCache="+new Date().getTime():""),events:{load:C,readystatechange:function(){if(["loaded","complete"].contains(this.
 readyState)){C()}},error:D}}).inject(this.options.target||document.head)}else{this.log("requesting script: ",A);new Request({url:A,noCache:this.options.noCache,onComplete:function(F){this.log("loaded script: ",A);$exec(F);E()}.bind(this),onFailure:D,onException:D}).send()}},scriptsState:$H(),getLoadedScripts:function(){return this.scriptsState.filter(function(A){return A})},scriptLoaded:function(A){this.log("loaded script: ",A);this.scriptsState[A]=true;this.check();var B=this.getLoadedScripts();var D=B.getLength();var C=this.scriptsState.getLength();this.fireEvent("scriptLoaded",{script:A,totalLoaded:(D/C*100).round(),currentLoaded:((D-this.lastLoaded)/(C-this.lastLoaded)*100).round(),loaded:B});if(D==C){this.lastLoaded=D}},lastLoaded:0,check:function(){var A=[];this.required.each(function(C){var B=[];C.scripts.each(function(D){if(this.scriptsState[D]){B.push(D)}},this);if(C.onStep){C.onStep({percent:B.length/C.scripts.length*100,scripts:B})}if(C.scripts.length!=B.length){r
 eturn }C.callback();this.required.erase(C);this.fireEvent("requirementLoaded",[B,C])},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};Class.refactor=function(B,A){$each(A,function(E,D){var C=B.prototype[D];if(C&&(C=C._origin)&&typeof E=="function"){B.implement(D,function(){var F=this.previous;this.previous=C;var G=E.apply(this,arguments);this.previous=F;return G})}else{B.implement(D,E)}});return B};Class.Mutators.Binds=function(A){return A};Class.Mutators.initialize=function(A){return function(){$splat(this.Binds).each(function(B){var C=this[B];if(C){this[B]=C.bind(this)}},this);return A.apply(this,arguments)}};Class.Occlude=new Class({occlude:function(C,B){B=document.id(B||this.element);var A=B.retrieve(C||this.property);if(A&&!$defined(this.occluded)){return
  this.occluded=A}this.occluded=false;B.store(C||this.property,this);return this.occluded}});(function(){var A={wait:function(B){return this.chain(function(){this.callChain.delay($pick(B,500),this)}.bind(this))}};Chain.implement(A);if(window.Fx){Fx.implement(A);["Css","Tween","Elements"].each(function(B){if(Fx[B]){Fx[B].implement(A)}})}Element.implement({chains:function(B){$splat($pick(B,["tween","morph","reveal"])).each(function(C){C=this.get(C);if(!C){return }C.setOptions({link:"chain"})},this);return this},pauseFx:function(C,B){this.chains(B).get($pick(B,"tween")).wait(C);return this}})})();Array.implement({min:function(){return Math.min.apply(null,this)},max:function(){return Math.max.apply(null,this)},average:function(){return this.length?this.sum()/this.length:0},sum:function(){var A=0,B=this.length;if(B){do{A+=this[--B]}while(B)}return A},unique:function(){return[].combine(this)},shuffle:function(){for(var B=this.length;B&&--B;){var A=this[B],C=Math.floor(Math.random()
 *(B+1));this[B]=this[C];this[C]=A}return this}});MooTools.lang.set("en-US","Date",{months:["January","February","March","April","May","June","July","August","September","October","November","December"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dateOrder:["month","date","year"],shortDate:"%m/%d/%Y",shortTime:"%I:%M%p",AM:"AM",PM:"PM",ordinal:function(A){return(A>3&&A<21)?"th":["th","st","nd","rd","th"][Math.min(A%10,4)]},lessThanMinuteAgo:"less than a minute ago",minuteAgo:"about a minute ago",minutesAgo:"{delta} minutes ago",hourAgo:"about an hour ago",hoursAgo:"about {delta} hours ago",dayAgo:"1 day ago",daysAgo:"{delta} days ago",weekAgo:"1 week ago",weeksAgo:"{delta} weeks ago",monthAgo:"1 month ago",monthsAgo:"{delta} months ago",yearAgo:"1 year ago",yearsAgo:"{delta} years ago",lessThanMinuteUntil:"less than a minute from now",minuteUntil:"about a minute from now",minutesUntil:"{delta} minutes from now",hourUntil:"about an hour from n
 ow",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"});(function(){var I=this.Date;if(!I.now){I.now=$time}I.Methods={ms:"Milliseconds",year:"FullYear",min:"Minutes",mo:"Month",sec:"Seconds",hr:"Hours"};["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds","Time","TimezoneOffset","Week","Timezone","GMTOffset","DayOfYear","LastMonth","LastDayOfMonth","UTCDate","UTCDay","UTCFullYear","AMPM","Ordinal","UTCHours","UTCMilliseconds","UTCMinutes","UTCMonth","UTCSeconds"].each(function(P){I.Methods[P.toLowerCase()]=P});var D=function(Q,P){return new Array(P-String(Q).length+1).join("0")+Q};I.implement({set:function(S,Q){switch($type(S)){case"object":for(var R in S){this.set(R,S[R])}break;case"string":S=S.toLowerCase()
 ;var P=I.Methods;if(P[S]){this["set"+P[S]](Q)}}return this},get:function(Q){Q=Q.toLowerCase();var P=I.Methods;if(P[Q]){return this["get"+P[Q]]()}return null},clone:function(){return new I(this.get("time"))},increment:function(P,R){P=P||"day";R=$pick(R,1);switch(P){case"year":return this.increment("month",R*12);case"month":var Q=this.get("date");this.set("date",1).set("mo",this.get("mo")+R);return this.set("date",Q.min(this.get("lastdayofmonth")));case"week":return this.increment("day",R*7);case"day":return this.set("date",this.get("date")+R)}if(!I.units[P]){throw new Error(P+" is not a supported interval")}return this.set("time",this.get("time")+R*I.units[P]())},decrement:function(P,Q){return this.increment(P,-1*$pick(Q,1))},isLeapYear:function(){return I.isLeapYear(this.get("year"))},clearTime:function(){return this.set({hr:0,min:0,sec:0,ms:0})},diff:function(Q,P){if($type(Q)=="string"){Q=I.parse(Q)}return((Q-this)/I.units[P||"day"](3,3)).toInt()},getLastDayOfMonth:function
 (){return I.daysInMonth(this.get("mo"),this.get("year"))},getDayOfYear:function(){return(I.UTC(this.get("year"),this.get("mo"),this.get("date")+1)-I.UTC(this.get("year"),0,1))/I.units.day()},getWeek:function(){return(this.get("dayofyear")/7).ceil()},getOrdinal:function(P){return I.getMsg("ordinal",P||this.get("date"))},getTimezone:function(){return this.toString().replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/,"$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/,"$1$2$3")},getGMTOffset:function(){var P=this.get("timezoneOffset");return((P>0)?"-":"+")+D((P.abs()/60).floor(),2)+D(P%60,2)},setAMPM:function(P){P=P.toUpperCase();var Q=this.get("hr");if(Q>11&&P=="AM"){return this.decrement("hour",12)}else{if(Q<12&&P=="PM"){return this.increment("hour",12)}}return this},getAMPM:function(){return(this.get("hr")<12)?"AM":"PM"},parse:function(P){this.set("time",I.parse(P));return this},isValid:function(P){return !!(P||this).valueOf()},format:function(P){if(!this.isValid()){return
 "invalid date"}P=P||"%x %X";P=K[P.toLowerCase()]||P;var Q=this;return P.replace(/%([a-z%])/gi,function(S,R){switch(R){case"a":return I.getMsg("days")[Q.get("day")].substr(0,3);case"A":return I.getMsg("days")[Q.get("day")];case"b":return I.getMsg("months")[Q.get("month")].substr(0,3);case"B":return I.getMsg("months")[Q.get("month")];case"c":return Q.toString();case"d":return D(Q.get("date"),2);case"H":return D(Q.get("hr"),2);case"I":return((Q.get("hr")%12)||12);case"j":return D(Q.get("dayofyear"),3);case"m":return D((Q.get("mo")+1),2);case"M":return D(Q.get("min"),2);case"o":return Q.get("ordinal");case"p":return I.getMsg(Q.get("ampm"));case"S":return D(Q.get("seconds"),2);case"U":return D(Q.get("week"),2);case"w":return Q.get("day");case"x":return Q.format(I.getMsg("shortDate"));case"X":return Q.format(I.getMsg("shortTime"));case"y":return Q.get("year").toString().substr(2);case"Y":return Q.get("year");case"T":return Q.get("GMTOffset");case"Z":return Q.get("Timezone")}return
  R})},toISOString:function(){return this.format("iso8601")}});I.alias("toISOString","toJSON");I.alias("diff","compare");I.alias("format","strftime");var K={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 G=[];var E=I.parse;var N=function(S,U,R){var Q=-1;var T=I.getMsg(S+"s");switch($type(U)){case"object":Q=T[U.get(S)];break;case"number":Q=T[month-1];if(!Q){throw new Error("Invalid "+S+" index: "+index)}break;case"string":var P=T.filter(function(V){return this.test(V)},new RegExp("^"+U,"i"));if(!P.length){throw new Error("Invalid "+S+" string")}if(P.length>1){throw new Error("Ambiguous "+S)}Q=P[0]}return(R)?T.indexOf(Q):Q};I.extend({getMsg:function(Q,P){return MooTools.lang.get("Date",Q,P)},units:{ms:$lambda(1),second:$lambda(1000),minute:$lambda(60000),hour:$lambda(3600000),day:$lambda(86400000),week:$lambda(608400000),month:function(Q,P){var R=new I;return I.da
 ysInMonth($pick(Q,R.get("mo")),$pick(P,R.get("year")))*86400000},year:function(P){P=P||new I().get("year");return I.isLeapYear(P)?31622400000:31536000000}},daysInMonth:function(Q,P){return[31,I.isLeapYear(P)?29:28,31,30,31,30,31,31,30,31,30,31][Q]},isLeapYear:function(P){return((P%4===0)&&(P%100!==0))||(P%400===0)},parse:function(R){var Q=$type(R);if(Q=="number"){return new I(R)}if(Q!="string"){return R}R=R.clean();if(!R.length){return null}var P;G.some(function(T){var S=T.re.exec(R);return(S)?(P=T.handler(S)):false});return P||new I(E(R))},parseDay:function(P,Q){return N("day",P,Q)},parseMonth:function(Q,P){return N("month",Q,P)},parseUTC:function(Q){var P=new I(Q);var R=I.UTC(P.get("year"),P.get("mo"),P.get("date"),P.get("hr"),P.get("min"),P.get("sec"));return new I(R)},orderIndex:function(P){return I.getMsg("dateOrder").indexOf(P)+1},defineFormat:function(P,Q){K[P]=Q},defineFormats:function(P){for(var Q in P){I.defineFormat(Q,P[Q])}},parsePatterns:G,defineParser:function(
 P){G.push((P.re&&P.handler)?P:L(P))},defineParsers:function(){Array.flatten(arguments).each(I.defineParser)},define2DigitYearStart:function(P){H=P%100;M=P-H}});var M=1900;var H=70;var J=function(P){return new RegExp("(?:"+I.getMsg(P).map(function(Q){return Q.substr(0,3)}).join("|")+")[a-z]*")};var A=function(P){switch(P){case"x":return((I.orderIndex("month")==1)?"%m[.-/]%d":"%d[.-/]%m")+"([.-/]%y)?";case"X":return"%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?"}return null};var O={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})?/};O.m=O.I;O.S=O.M;var C;var B=function(P){C=P;O.a=O.A=J("days");O.b=O.B=J("months");G.each(function(R,Q){if(R.format){G[Q]=L(R.format)}})};var L=function(R){if(!C){return{format:R}}var P=[];var Q=(R.source||R).replace(/%([a-z])/gi,function(T,S){return A(S)||T}).replace(/\((?!\?)/g,"(?:").replace(/ (?!\?|\*)/g,",? ").replace(/%([a-z%])/gi,functi
 on(T,S){var U=O[S];if(!U){return S}P.push(S);return"("+U.source+")"}).replace(/\[a-z\]/gi,"[a-z\\u00c0-\\uffff]");return{format:R,re:new RegExp("^"+Q+"$","i"),handler:function(U){U=U.slice(1).associate(P);var S=new I().clearTime();if("d" in U){F.call(S,"d",1)}if("m" in U||"b" in U||"B" in U){F.call(S,"m",1)}for(var T in U){F.call(S,T,U[T])}return S}}};var F=function(P,Q){if(!Q){return this}switch(P){case"a":case"A":return this.set("day",I.parseDay(Q,true));case"b":case"B":return this.set("mo",I.parseMonth(Q,true));case"d":return this.set("date",Q);case"H":case"I":return this.set("hr",Q);case"m":return this.set("mo",Q-1);case"M":return this.set("min",Q);case"p":return this.set("ampm",Q.replace(/\./g,""));case"S":return this.set("sec",Q);case"s":return this.set("ms",("0."+Q)*1000);case"w":return this.set("day",Q);case"Y":return this.set("year",Q);case"y":Q=+Q;if(Q<100){Q+=M+(Q<H?100:0)}return this.set("year",Q);case"T":if(Q=="Z"){Q="+00"}var R=Q.match(/([+-])(\d{2}):?(\d{2})?/
 );R=(R[1]+"1")*(R[2]*60+(+R[3]||0))+this.getTimezoneOffset();return this.set("time",this-R*60000)}return this};I.defineParsers("%Y([-./]%m([-./]%d((T| )%X)?)?)?","%Y%m%d(T%H(%M%S?)?)?","%x( %X)?","%d%o( %b( %Y)?)?( %X)?","%b( %d%o)?( %Y)?( %X)?","%Y %b( %d%o( %X)?)?","%o %b %d %X %T %Y");MooTools.lang.addEvent("langChange",function(P){if(MooTools.lang.get("Date")){B(P)}}).fireEvent("langChange",MooTools.lang.getCurrentLanguage())})();Date.implement({timeDiffInWords:function(A){return Date.distanceOfTimeInWords(this,A||new Date)},timeDiff:function(G,B){if(G==null){G=new Date}var F=((G-this)/1000).toInt();if(!F){return"0s"}var A={s:60,m:60,h:24,d:365,y:0};var E,D=[];for(var C in A){if(!F){break}if((E=A[C])){D.unshift((F%E)+C);F=(F/E).toInt()}else{D.unshift(F+C)}}return D.join(B||":")}});Date.alias("timeDiffInWords","timeAgoInWords");Date.extend({distanceOfTimeInWords:function(B,A){return Date.getTimePhrase(((A-B)/1000).toInt())},getTimePhrase:function(F){var D=(F<0)?"Until":"A
 go";if(F<0){F*=-1}var B={minute:60,hour:60,day:24,week:7,month:52/12,year:12,eon:Infinity};var E="lessThanMinute";for(var C in B){var A=B[C];if(F<1.5*A){if(F>0.75*A){E=C}break}F/=A;E=C+"s"}return Date.getMsg(E+D).substitute({delta:F.round()})}});Date.defineParsers({re:/^(?:tod|tom|yes)/i,handler:function(A){var B=new Date().clearTime();switch(A[0]){case"tom":return B.increment();case"yes":return B.decrement();default:return B}}},{re:/^(next|last) ([a-z]+)$/i,handler:function(D){var E=new Date().clearTime();var B=E.getDay();var C=Date.parseDay(D[2],true);var A=C-B;if(C<=B){A+=7}if(D[1]=="last"){A-=7}return E.set("date",E.getDate()+A)}});Hash.implement({getFromPath:function(A){var B=this.getClean();A.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g,function(C){if(!B){return null}var D=arguments[2]||arguments[1]||arguments[0];B=(D in B)?B[D]:null;return C});return B},cleanValues:function(A){A=A||$defined;this.each(function(C,B){if(!A(C)){this.erase(B)}},this);return this},run:function
 (){var A=arguments;this.each(function(C,B){if($type(C)=="function"){C.run(A)}})}});(function(){var B=["À","à","Ã?","á","Â","â","Ã","ã","Ä","ä","Ã…","Ã¥","Ä‚","ă","Ä„","Ä…","Ć","ć","ÄŒ","Ä?","Ç","ç","ÄŽ","Ä?","Ä?","Ä‘","È","è","É","é","Ê","ê","Ë","ë","Äš","Ä›","Ę","Ä™","Äž","ÄŸ","ÃŒ","ì","Ã?","Ã","ÃŽ","î","Ã?","ï","Ĺ","ĺ","Ľ","ľ","Å?","Å‚","Ñ","ñ","Ň","ň","Ń","Å„","Ã’","ò","Ó","ó","Ô","ô","Õ","õ","Ö","ö","Ø","ø","Å‘","Ř","Å™","Å”","Å•","Å ","Å¡","Åž","ÅŸ","Åš","Å›","Ť","Å¥","Ť","Å¥","Å¢","Å£","Ù","ù","Ú","ú","Û","û","Ãœ","ü","Å®","ů","Ÿ","ÿ","ý","Ã?","Ž","ž","Ź","ź","Å»","ż","Þ","þ","Ã?","ð","ß","Å’","Å“","Æ","æ","µ"];var A=["A","a","A","a","A","a","A","a","Ae","ae","A","a","A","a","A","a","C","c","C","c","C","c","D","d","D","d","E","e","E","e","E","e","E","e","E","e","E","e","G","g","I","i","I","i","I","i","I","i","L","l","L","l","L","l","N","n","N","n","N","n","O","o","O","o","O","o","O","o","Oe","oe","
 O","o","o","R","r","R","r","S","s","S","s","S","s","T","t","T","t","T","t","U","u","U","u","U","u","Ue","ue","U","u","Y","y","Y","y","Z","z","Z","z","Z","z","TH","th","DH","dh","ss","OE","oe","AE","ae","u"];var D={"[\xa0\u2002\u2003\u2009]":" ","\xb7":"*","[\u2018\u2019]":"'","[\u201c\u201d]":'"',"\u2026":"...","\u2013":"-","\u2014":"--","\uFFFD":"&raquo;"};var C=function(E,F){E=E||"";var G=F?"<"+E+"[^>]*>([\\s\\S]*?)</"+E+">":"</?"+E+"([^>]+)?>";reg=new RegExp(G,"gi");return reg};String.implement({standardize:function(){var E=this;B.each(function(G,F){E=E.replace(new RegExp(G,"g"),A[F])});return E},repeat:function(E){return new Array(E+1).join(this)},pad:function(F,H,E){if(this.length>=F){return this}var G=(H==null?" ":""+H).repeat(F-this.length).substr(0,F-this.length);if(!E||E=="right"){return this+G}if(E=="left"){return G+this}return G.substr(0,(G.length/2).floor())+this+G.substr(0,(G.length/2).ceil())},getTags:function(E,F){return this.match(C(E,F))||[]},stripTags:funct
 ion(E,F){return this.replace(C(E,F),"")},tidy:function(){var E=this.toString();$each(D,function(G,F){E=E.replace(new RegExp(F,"g"),G)});return E}})})();String.implement({parseQueryString:function(){var B=this.split(/[&;]/),A={};if(B.length){B.each(function(G){var C=G.indexOf("="),D=C<0?[""]:G.substr(0,C).match(/[^\]\[]+/g),E=decodeURIComponent(G.substr(C+1)),F=A;D.each(function(I,H){var J=F[I];if(H<D.length-1){F=F[I]=J||{}}else{if($type(J)=="array"){J.push(E)}else{F[I]=$defined(J)?[J,E]:E}}})})}return A},cleanQueryString:function(A){return this.split("&").filter(function(E){var B=E.indexOf("="),C=B<0?"":E.substr(0,B),D=E.substr(B+1);return A?A.run([C,D]):$chk(D)}).join("&")}});var URI=new Class({Implements:Options,options:{},regex:/^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,parts:["scheme","user","password","host","port","directory","file","query","fragment"],schemes:{http:80,https:443
 ,ftp:21,rtsp:554,mms:1755,file:0},initialize:function(B,A){this.setOptions(A);var C=this.options.base||URI.base;if(!B){B=C}if(B&&B.parsed){this.parsed=$unlink(B.parsed)}else{this.set("value",B.href||B.toString(),C?new URI(C):false)}},parse:function(C,B){var A=C.match(this.regex);if(!A){return false}A.shift();return this.merge(A.associate(this.parts),B)},merge:function(B,A){if((!B||!B.scheme)&&(!A||!A.scheme)){return false}if(A){this.parts.every(function(C){if(B[C]){return false}B[C]=A[C]||"";return true})}B.port=B.port||this.schemes[B.scheme.toLowerCase()];B.directory=B.directory?this.parseDirectory(B.directory,A?A.directory:""):"/";return B},parseDirectory:function(B,C){B=(B.substr(0,1)=="/"?"":(C||"/"))+B;if(!B.test(URI.regs.directoryDot)){return B}var A=[];B.replace(URI.regs.endSlash,"").split("/").each(function(D){if(D==".."&&A.length>0){A.pop()}else{if(D!="."){A.push(D)}}});return A.join("/")+"/"},combine:function(A){return A.value||A.scheme+"://"+(A.user?A.user+(A.pass
 word?":"+A.password:"")+"@":"")+(A.host||"")+(A.port&&A.port!=this.schemes[A.scheme]?":"+A.port:"")+(A.directory||"/")+(A.file||"")+(A.query?"?"+A.query:"")+(A.fragment?"#"+A.fragment:"")},set:function(B,D,C){if(B=="value"){var A=D.match(URI.regs.scheme);if(A){A=A[1]}if(A&&!$defined(this.schemes[A.toLowerCase()])){this.parsed={scheme:A,value:D}}else{this.parsed=this.parse(D,(C||this).parsed)||(A?{scheme:A,value:D}:{value:D})}}else{if(B=="data"){this.setData(D)}else{this.parsed[B]=D}}return this},get:function(A,B){switch(A){case"value":return this.combine(this.parsed,B?B.parsed:false);case"data":return this.getData()}return this.parsed[A]||""},go:function(){document.location.href=this.toString()},toURI:function(){return this},getData:function(C,B){var A=this.get(B||"query");if(!$chk(A)){return C?null:{}}var D=A.parseQueryString();return C?D[C]:D},setData:function(A,C,B){if(typeof A=="string"){data=this.getData();data[arguments[0]]=arguments[1];A=data}else{if(C){A=$merge(this.
 getData(),A)}}return this.set(B||"query",Hash.toQueryString(A))},clearData:function(A){return this.set(A||"query","")}});URI.prototype.toString=URI.prototype.valueOf=function(){return this.get("value")};URI.regs={endSlash:/\/$/,scheme:/^(\w+):/,directoryDot:/\.\/|\.$/};URI.base=new URI(document.getElements("base[href]",true).getLast(),{base:document.location});String.implement({toURI:function(A){return new URI(this,A)}});URI=Class.refactor(URI,{combine:function(F,E){if(!E||F.scheme!=E.scheme||F.host!=E.host||F.port!=E.port){return this.previous.apply(this,arguments)}var A=F.file+(F.query?"?"+F.query:"")+(F.fragment?"#"+F.fragment:"");if(!E.directory){return(F.directory||(F.file?"":"./"))+A}var D=E.directory.split("/"),C=F.directory.split("/"),G="",H;var B=0;for(H=0;H<D.length&&H<C.length&&D[H]==C[H];H++){}for(B=0;B<D.length-H-1;B++){G+="../"}for(B=H;B<C.length-1;B++){G+=C[B]+"/"}return(G||(F.file?"":"./"))+A},toAbsolute:function(A){A=new URI(A);if(A){A.set("directory","").se
 t("file","")}return this.toRelative(A)},toRelative:function(A){return this.get("value",new URI(A))}});Element.implement({tidy:function(){this.set("value",this.get("value").tidy())},getTextInRange:function(B,A){return this.get("value").substring(B,A)},getSelectedText:function(){if(this.setSelectionRange){return this.getTextInRange(this.getSelectionStart(),this.getSelectionEnd())}return document.selection.createRange().text},getSelectedRange:function(){if($defined(this.selectionStart)){return{start:this.selectionStart,end:this.selectionEnd}}var E={start:0,end:0};var A=this.getDocument().selection.createRange();if(!A||A.parentElement()!=this){return E}var C=A.duplicate();if(this.type=="text"){E.start=0-C.moveStart("character",-100000);E.end=E.start+A.text.length}else{var B=this.get("value");var D=B.length;C.moveToElementText(this);C.setEndPoint("StartToEnd",A);if(C.text.length){D-=B.match(/[\n\r]*$/)[0].length}E.end=D-C.text.length;C.setEndPoint("StartToStart",A);E.start=D-C.te
 xt.length}return E},getSelectionStart:function(){return this.getSelectedRange().start},getSelectionEnd:function(){return this.getSelectedRange().end},setCaretPosition:function(A){if(A=="end"){A=this.get("value").length}this.selectRange(A,A);return this},getCaretPosition:function(){return this.getSelectedRange().start},selectRange:function(E,A){if(this.setSelectionRange){this.focus();this.setSelectionRange(E,A)}else{var C=this.get("value");var D=C.substr(E,A-E).replace(/\r/g,"").length;E=C.substr(0,E).replace(/\r/g,"").length;var B=this.createTextRange();B.collapse(true);B.moveEnd("character",E+D);B.moveStart("character",E);B.select()}return this},insertAtCursor:function(B,A){var D=this.getSelectedRange();var C=this.get("value");this.set("value",C.substring(0,D.start)+B+C.substring(D.end,C.length));if($pick(A,true)){this.selectRange(D.start,D.start+B.length)}else{this.setCaretPosition(D.start+B.length)}return this},insertAroundCursor:function(B,A){B=$extend({before:"",default
 Middle:"",after:""},B);var C=this.getSelectedText()||B.defaultMiddle;var G=this.getSelectedRange();var F=this.get("value");if(G.start==G.end){this.set("value",F.substring(0,G.start)+B.before+C+B.after+F.substring(G.end,F.length));this.selectRange(G.start+B.before.length,G.end+B.before.length+C.length)}else{var D=F.substring(G.start,G.end);this.set("value",F.substring(0,G.start)+B.before+D+B.after+F.substring(G.end,F.length));var E=G.start+B.before.length;if($pick(A,true)){this.selectRange(E,E+D.length)}else{this.setCaretPosition(E+F.length)}}return this}});Elements.from=function(E,D){if($pick(D,true)){E=E.stripScripts()}var B,C=E.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);if(C){B=new Element("table");var A=C[1].toLowerCase();if(["td","th","tr"].contains(A)){B=new Element("tbody").inject(B);if(A!="tr"){B=new Element("tr").inject(B)}}}return(B||new Element("div")).set("html",E).getChildren()};(function(D,E){var C=/(.*?):relay\(([^)]+)\)$/,B=/[+>~\s]/,F=function(G){var H=G.match
 (C);return !H?{event:G}:{event:H[1],selector:H[2]}},A=function(L,G){var J=L.target;if(B.test(G=G.trim())){var I=this.getElements(G);for(var H=I.length;H--;){var K=I[H];if(J==K||K.hasChild(J)){return K}}}else{for(;J&&J!=this;J=J.parentNode){if(Element.match(J,G)){return document.id(J)}}}return null};Element.implement({addEvent:function(J,I){var K=F(J);if(K.selector){var H=this.retrieve("$moo:delegateMonitors",{});if(!H[J]){var G=function(M){var L=A.call(this,M,K.selector);if(L){this.fireEvent(J,[M,L],0,L)}}.bind(this);H[J]=G;D.call(this,K.event,G)}}return D.apply(this,arguments)},removeEvent:function(J,I){var K=F(J);if(K.selector){var H=this.retrieve("events");if(!H||!H[J]||(I&&!H[J].keys.contains(I))){return this}if(I){E.apply(this,[J,I])}else{E.apply(this,J)}H=this.retrieve("events");if(H&&H[J]&&H[J].keys.length==0){var G=this.retrieve("$moo:delegateMonitors",{});E.apply(this,[K.event,G[J]]);delete G[J]}return this}return E.apply(this,arguments)},fireEvent:function(J,H,G,K)
 {var I=this.retrieve("events");if(!I||!I[J]){return this}I[J].keys.each(function(L){L.create({bind:K||this,delay:G,arguments:H})()},this);return this}})})(Element.prototype.addEvent,Element.prototype.removeEvent);Element.implement({measure:function(E){var G=function(H){return !!(!H||H.offsetHeight||H.offsetWidth)};if(G(this)){return E.apply(this)}var D=this.getParent(),F=[],B=[];while(!G(D)&&D!=document.body){B.push(D.expose());D=D.getParent()}var C=this.expose();var A=E.apply(this);C();B.each(function(H){H()});return A},expose:function(){if(this.getStyle("display")!="none"){return $empty}var A=this.style.cssText;this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=A}.bind(this)},getDimensions:function(A){A=$merge({computeSize:false},A);var E={};var D=function(G,F){return(F.computeSize)?G.getComputedSize(F):G.getSize()};var B=this.getParent("body");if(B&&this.getStyle("display")=="none"){E=this.measure(function(){retu
 rn D(this,A)})}else{if(B){try{E=D(this,A)}catch(C){}}else{E={x:0,y:0}}}return $chk(E.x)?$extend(E,{width:E.x,height:E.y}):$extend(E,{x:E.width,y:E.height})},getComputedSize:function(A){A=$merge({styles:["padding","border"],plains:{height:["top","bottom"],width:["left","right"]},mode:"both"},A);var C={width:0,height:0};switch(A.mode){case"vertical":delete C.width;delete A.plains.width;break;case"horizontal":delete C.height;delete A.plains.height;break}var B=[];$each(A.plains,function(G,F){G.each(function(H){A.styles.each(function(I){B.push((I=="border")?I+"-"+H+"-width":I+"-"+H)})})});var E={};B.each(function(F){E[F]=this.getComputedStyle(F)},this);var D=[];$each(A.plains,function(G,F){var H=F.capitalize();C["total"+H]=C["computed"+H]=0;G.each(function(I){C["computed"+I.capitalize()]=0;B.each(function(K,J){if(K.test(I)){E[K]=E[K].toInt()||0;C["total"+H]=C["total"+H]+E[K];C["computed"+I.capitalize()]=C["computed"+I.capitalize()]+E[K]}if(K.test(I)&&F!=K&&(K.test("border")||K.te
 st("padding"))&&!D.contains(K)){D.push(K);C["computed"+H]=C["computed"+H]-E[K]}})})});["Width","Height"].each(function(G){var F=G.toLowerCase();if(!$chk(C[F])){return }C[F]=C[F]+this["offset"+G]+C["computed"+G];C["total"+G]=C[F]+C["total"+G];delete C["computed"+G]},this);return $extend(E,C)}});(function(){var A=false;window.addEvent("domready",function(){var B=new Element("div").setStyles({position:"fixed",top:0,right:0}).inject(document.body);A=(B.offsetTop===0);B.dispose()});Element.implement({pin:function(D){if(this.getStyle("display")=="none"){return null}var F,B=window.getScroll();if(D!==false){F=this.getPosition();if(!this.retrieve("pinned")){var H={top:F.y-B.y,left:F.x-B.x};if(A){this.setStyle("position","fixed").setStyles(H)}else{this.store("pinnedByJS",true);this.setStyles({position:"absolute",top:F.y,left:F.x}).addClass("isPinned");this.store("scrollFixer",(function(){if(this.retrieve("pinned")){var I=window.getScroll()}this.setStyles({top:H.top.toInt()+I.y,left:H.
 left.toInt()+I.x})}).bind(this));window.addEvent("scroll",this.retrieve("scrollFixer"))}this.store("pinned",true)}}else{var G;if(!Browser.Engine.trident){var E=this.getParent();G=(E.getComputedStyle("position")!="static"?E:E.getOffsetParent())}F=this.getPosition(G);this.store("pinned",false);var C;if(A&&!this.retrieve("pinnedByJS")){C={top:F.y+B.y,left:F.x+B.x}}else{this.store("pinnedByJS",false);window.removeEvent("scroll",this.retrieve("scrollFixer"));C={top:F.y,left:F.x}}this.setStyles($merge(C,{position:"absolute"})).removeClass("isPinned")}return this},unpin:function(){return this.pin(false)},togglepin:function(){this.pin(!this.retrieve("pinned"))}})})();(function(){var A=Element.prototype.position;Element.implement({position:function(G){if(G&&($defined(G.x)||$defined(G.y))){return A?A.apply(this,arguments):this}$each(G||{},function(U,T){if(!$defined(U)){delete G[T]}});G=$merge({relativeTo:document.body,position:{x:"center",y:"center"},edge:false,offset:{x:0,y:0},return
 Pos:false,relFixedPosition:false,ignoreMargins:false,ignoreScroll:false,allowNegative:false},G);var R={x:0,y:0},E=false;var C=this.measure(function(){return document.id(this.getOffsetParent())});if(C&&C!=this.getDocument().body){R=C.measure(function(){return this.getPosition()});E=C!=document.id(G.relativeTo);G.offset.x=G.offset.x-R.x;G.offset.y=G.offset.y-R.y}var S=function(T){if($type(T)!="string"){return T}T=T.toLowerCase();var U={};if(T.test("left")){U.x="left"}else{if(T.test("right")){U.x="right"}else{U.x="center"}}if(T.test("upper")||T.test("top")){U.y="top"}else{if(T.test("bottom")){U.y="bottom"}else{U.y="center"}}return U};G.edge=S(G.edge);G.position=S(G.position);if(!G.edge){if(G.position.x=="center"&&G.position.y=="center"){G.edge={x:"center",y:"center"}}else{G.edge={x:"left",y:"top"}}}this.setStyle("position","absolute");var F=document.id(G.relativeTo)||document.body,D=F==document.body?window.getScroll():F.getPosition(),L=D.y,H=D.x;var N=this.getDimensions({comput
 eSize:true,styles:["padding","border","margin"]});var J={},O=G.offset.y,Q=G.offset.x,K=window.getSize();switch(G.position.x){case"left":J.x=H+Q;break;case"right":J.x=H+Q+F.offsetWidth;break;default:J.x=H+((F==document.body?K.x:F.offsetWidth)/2)+Q;break}switch(G.position.y){case"top":J.y=L+O;break;case"bottom":J.y=L+O+F.offsetHeight;break;default:J.y=L+((F==document.body?K.y:F.offsetHeight)/2)+O;break}if(G.edge){var B={};switch(G.edge.x){case"left":B.x=0;break;case"right":B.x=-N.x-N.computedRight-N.computedLeft;break;default:B.x=-(N.totalWidth/2);break}switch(G.edge.y){case"top":B.y=0;break;case"bottom":B.y=-N.y-N.computedTop-N.computedBottom;break;default:B.y=-(N.totalHeight/2);break}J.x+=B.x;J.y+=B.y}J={left:((J.x>=0||E||G.allowNegative)?J.x:0).toInt(),top:((J.y>=0||E||G.allowNegative)?J.y:0).toInt()};var I={left:"x",top:"y"};["minimum","maximum"].each(function(T){["left","top"].each(function(U){var V=G[T]?G[T][I[U]]:null;if(V!=null&&J[U]<V){J[U]=V}})});if(F.getStyle("posit
 ion")=="fixed"||G.relFixedPosition){var M=window.getScroll();J.top+=M.y;J.left+=M.x}if(G.ignoreScroll){var P=F.getScroll();J.top-=P.y;J.left-=P.x}if(G.ignoreMargins){J.left+=(G.edge.x=="right"?N["margin-right"]:G.edge.x=="center"?-N["margin-left"]+((N["margin-right"]+N["margin-left"])/2):-N["margin-left"]);J.top+=(G.edge.y=="bottom"?N["margin-bottom"]:G.edge.y=="center"?-N["margin-top"]+((N["margin-bottom"]+N["margin-top"])/2):-N["margin-top"])}J.left=Math.ceil(J.left);J.top=Math.ceil(J.top);if(G.returnPos){return J}else{this.setStyles(J)}return this}})})();Element.implement({isDisplayed:function(){return this.getStyle("display")!="none"},isVisible:function(){var A=this.offsetWidth,B=this.offsetHeight;return(A==0&&B==0)?false:(A>0&&B>0)?true:this.isDisplayed()},toggle:function(){return this[this.isDisplayed()?"hide":"show"]()},hide:function(){var B;try{B=this.getStyle("display")}catch(A){}return this.store("originalDisplay",B||"").setStyle("display","none")},show:function(A)
 {A=A||this.retrieve("originalDisplay")||"block";return this.setStyle("display",(A=="none")?"block":A)},swapClass:function(A,B){return this.removeClass(A).addClass(B)}});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(B,A){this.element=document.id(B);if(this.occlude()){return this.occluded}this.setOptions(A);this.makeShim();return this},makeShim:function(){if(this.options.browsers){var C=this.element.getStyle("zIndex").toInt();if(!C){C=1;var B=this.element.getStyle("position");if(B=="static"||!B){this.element.setStyle("position","relative")}this.element.setStyle("zIndex",C)}C=($chk(this.options.zIndex)&&C>this.options.zIndex)?this.options.zIndex:C-1;if(C<0){C=1}this.shim=new Element("i
 frame",{src:this.options.src,scrolling:"no",frameborder:0,styles:{zIndex:C,position:"absolute",border:"none",filter:"progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)"},"class":this.options.className}).store("IframeShim",this);var A=(function(){this.shim.inject(this.element,"after");this[this.options.display?"show":"hide"]();this.fireEvent("inject")}).bind(this);if(!IframeShim.ready){window.addEvent("load",A)}else{A()}}else{this.position=this.hide=this.show=this.dispose=$lambda(this)}},position:function(){if(!IframeShim.ready||!this.shim){return this}var A=this.element.measure(function(){return this.getSize()});if(this.options.margin!=undefined){A.x=A.x-(this.options.margin*2);A.y=A.y-(this.options.margin*2);this.options.offset.x+=this.options.margin;this.options.offset.y+=this.options.margin}this.shim.set({width:A.x,height:A.y}).position({relativeTo:this.element,offset:this.options.offset});return this},hide:function(){if(this.shim){this.shim.setStyle("display","no
 ne")}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});var Mask=new Class({Implements:[Options,Events],Binds:["position"],options:{style:{},"class":"mask",maskMargins:false,useIframeShim:true,iframeShimOptions:{}},initialize:function(B,A){this.target=document.id(B)||document.id(document.body);this.target.store("Mask",this);this.setOptions(A);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(B,A){A=A||this.optio
 ns.inject?this.options.inject.where:""||this.target==document.body?"inside":"after";B=B||this.options.inject?this.options.inject.target:""||this.target;this.element.inject(B,A);if(this.options.useIframeShim){this.shim=new IframeShim(this.element,this.options.iframeShimOptions);this.addEvents({show:this.shim.show.bind(this.shim),hide:this.shim.hide.bind(this.shim),destroy:this.shim.destroy.bind(this.shim)})}},position:function(){this.resize(this.options.width,this.options.height);this.element.position({relativeTo:this.target,position:"topLeft",ignoreMargins:!this.options.maskMargins,ignoreScroll:this.target==document.body});return this},resize:function(A,E){var B={styles:["padding","border"]};if(this.options.maskMargins){B.styles.push("margin")}var D=this.target.getComputedSize(B);if(this.target==document.body){var C=window.getSize();if(D.totalHeight<C.y){D.totalHeight=C.y}if(D.totalWidth<C.x){D.totalWidth=C.x}}this.element.setStyles({width:$pick(A,D.totalWidth,D.x),height:$p
 ick(E,D.totalHeight,D.y)});return this},show:function(){if(!this.hidden){return this}window.addEvent("resize",this.position);this.position();this.showMask.apply(this,arguments);return this},showMask:function(){this.element.setStyle("display","block");this.hidden=false;this.fireEvent("show")},hide:function(){if(this.hidden){return this}window.removeEvent("resize",this.position);this.hideMask.apply(this,arguments);if(this.options.destroyOnHide){return this.destroy()}return this},hideMask:function(){this.element.setStyle("display","none");this.hidden=true;this.fireEvent("hide")},toggle:function(){this[this.hidden?"show":"hide"]()},destroy:function(){this.hide();this.element.destroy();this.fireEvent("destroy");this.target.eliminate("mask")}});Element.Properties.mask={set:function(B){var A=this.retrieve("mask");return this.eliminate("mask").store("mask:options",B)},get:function(A){if(A||!this.retrieve("mask")){if(this.retrieve("mask")){this.retrieve("mask").destroy()}if(A||!this.
 retrieve("mask:options")){this.set("mask",A)}this.store("mask",new Mask(this,this.retrieve("mask:options")))}return this.retrieve("mask")}};Element.implement({mask:function(A){this.get("mask",A).show();return this},unmask:function(){this.get("mask").hide();return this}});var Spinner=new Class({Extends:Mask,options:{"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);var A=function(){this.active=false}.bind(this);this.addEvents({hide:A,show:A})},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).app
 endText(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(A){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(A)},showMask:function(A){var B=function(){this.content.position($merge({relativeTo:this.element},this.options.containerPosition))}.bind(this);if(A){this.parent();B()}else{this.element.setStyles({display:"block",opacity:0}).tween("opacity",this.options.style.opacity||0.9);B();this.hidden=false;this.fireEvent("show");this.callChain()}},hide:function(A){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(A)},hideMask:function(A){if(A){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(A){this._send=this.send;this.send=function(C){if(this.spinner){this.spinner.chain(this._send.bind(this,C)).show()}else{this._send(C)}return this};this.previous(A);var B=document.id(this.options.spinnerTarget)||document.id(this.options.update);if(this.options.useSpinner&&B){this.spinner=B.get("spinner",this.options.spinnerOptions);["onComplete","onException","onCancel"].each(function(C){this.addEvent(C,this.spinner.hide.bind(this.spinner))},this)}},getSpinner:function(){return this.spinner}})}Element.Properties.spinner={set:function(A){var B=this.retrieve("spinner");return this.elim
 inate("spinner").store("spinner:options",A)},get:function(A){if(A||!this.retrieve("spinner")){if(this.retrieve("spinner")){this.retrieve("spinner").destroy()}if(A||!this.retrieve("spinner:options")){this.set("spinner",A)}new Spinner(this,this.retrieve("spinner:options"))}return this.retrieve("spinner")}};Element.implement({spin:function(A){this.get("spinner",A).show();return this},unspin:function(){var A=Array.link(arguments,{options:Object.type,callback:Function.type});this.get("spinner",A.options).hide(A.callback);return this}});if(!window.Form){window.Form={}}(function(){Form.Request=new Class({Binds:["onSubmit","onFormValidate"],Implements:[Options,Events,Class.Occlude],options:{requestOptions:{evalScripts:true,useSpinner:true,emulation:false,link:"ignore"},extraData:{},resetForm:true},property:"form.request",initialize:function(B,C,A){this.element=document.id(B);if(this.occlude()){return this.occluded}this.update=document.id(C);this.setOptions(A);this.makeRequest();if(t
 his.options.resetForm){this.request.addEvent("success",function(){$try(function(){this.element.reset()}.bind(this));if(window.OverText){OverText.update()}}.bind(this))}this.attach()},toElement:function(){return this.element},makeRequest:function(){this.request=new Request.HTML($merge({update:this.update,emulation:false,spinnerTarget:this.element,method:this.element.get("method")||"post"},this.options.requestOptions)).addEvents({success:function(B,A){["complete","success"].each(function(C){this.fireEvent(C,[this.update,B,A])},this)}.bind(this),failure:function(A){this.fireEvent("complete").fireEvent("failure",A)}.bind(this),exception:function(){this.fireEvent("failure",xhr)}.bind(this)})},attach:function(A){A=$pick(A,true);method=A?"addEvent":"removeEvent";var B=this.element.retrieve("validator");if(B){B[method]("onFormValidate",this.onFormValidate)}if(!B||!A){this.element[method]("submit",this.onSubmit)}},detach:function(){this.attach(false)},enable:function(){this.attach()}
 ,disable:function(){this.detach()},onFormValidate:function(B,A,D){var C=this.element.retrieve("validator");if(B||(C&&!C.options.stopOnFailure)){if(D&&D.stop){D.stop()}this.send()}},onSubmit:function(A){if(this.element.retrieve("validator")){this.detach();return }A.stop();this.send()},send:function(){var B=this.element.toQueryString().trim();var A=$H(this.options.extraData).toQueryString();if(B){B+="&"+A}else{B=A}this.fireEvent("send",[this.element,B.parseQueryString()]);this.request.send({data:B,url:this.element.get("action")});return this}});Element.Properties.formRequest={set:function(){var A=Array.link(arguments,{options:Object.type,update:Element.type,updateId:String.type});var C=A.update||A.updateId;var B=this.retrieve("form.request");if(C){if(B){B.update=document.id(C)}this.store("form.request:update",C)}if(A.options){if(B){B.setOptions(A.options)}this.store("form.request:options",A.options)}return this},get:function(){var A=Array.link(arguments,{options:Object.type,up
 date:Element.type,updateId:String.type});var B=A.update||A.updateId;if(A.options||B||!this.retrieve("form.request")){if(A.options||!this.retrieve("form.request:options")){this.set("form.request",A.options)}if(B){this.set("form.request",B)}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(B,A){this.get("form.request",B,A).send();return this}})})();Fx.Reveal=new Class({Extends:Fx.Morph,options:{link:"cancel",styles:["padding","border","margin"],transitionOpacity:!Browser.Engine.trident4,mode:"vertical",display:"block",hideInputs:Browser.Engine.trident?"select, input, textarea, object, embed":false},dissolve:function(){try{if(!this.hiding&&!this.showing){if(this.element.getStyle("display")!="none"){this.hiding=true;this.showing=false;this.hidden=true;this.cssText=this.element.style.cssText;var D=this.element.getComputedSize({st
 yles:this.options.styles,mode:this.options.mode});this.element.setStyle("display",this.options.display);if(this.options.transitionOpacity){D.opacity=1}var B={};$each(D,function(F,E){B[E]=[F,0]},this);this.element.setStyle("overflow","hidden");var A=this.options.hideInputs?this.element.getElements(this.options.hideInputs):null;this.$chain.unshift(function(){if(this.hidden){this.hiding=false;$each(D,function(F,E){D[E]=F},this);this.element.style.cssText=this.cssText;this.element.setStyle("display","none");if(A){A.setStyle("visibility","visible")}}this.fireEvent("hide",this.element);this.callChain()}.bind(this));if(A){A.setStyle("visibility","hidden")}this.start(B)}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(C){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 D;this.cssText=this.element.style.cssText;this.element.measure(function(){D=this.element.getComputedSize({styles:this.options.styles,mode:this.options.mode})}.bind(this));$each(D,function(F,E){D[E]=F});if($chk(this.options.heightOverride)){D.height=this.options.heightOverride.toInt()}if($chk(this.options.widthOverride)){D.width=this.options.widthOverride.toInt()}if(this.options.transitionOpacity){this.element.setStyle("opacity",0);D.opacity=1}var B={height:0,display:this.options.display};$each(D,function(F,E){B[E]=0});this.element.setStyles($merge(B,{overflow:"hidden"}));var A=this.options.hideInputs?this.eleme
 nt.getElements(this.options.hideInputs):null;if(A){A.setStyle("visibility","hidden")}this.start(D);this.$chain.unshift(function(){this.element.style.cssText=this.cssText;this.element.setStyle("display",this.options.display);if(!this.hidden){this.showing=false}if(A){A.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(C){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("opaci
 ty")==0){this.reveal()}else{this.dissolve()}return this},cancel:function(){this.parent.apply(this,arguments);this.element.style.cssText=this.cssText;this.hidding=false;this.showing=false}});Element.Properties.reveal={set:function(A){var B=this.retrieve("reveal");if(B){B.cancel()}return this.eliminate("reveal").store("reveal:options",A)},get:function(A){if(A||!this.retrieve("reveal")){if(A||!this.retrieve("reveal:options")){this.set("reveal",A)}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(A){this.get("reveal",A).reveal();return this},dissolve:function(A){this.get("reveal",A).dissolve();return this},nix:function(){var A=Array.link(arguments,{destroy:Boolean.type,options:Object.type});this.get("reveal",A.options).dissolve().chain(function(){this[A.destroy?"destroy":"dispose"]()}.bind(this));return this},wink:function(){var B=Array
 .link(arguments,{duration:Number.type,options:Object.type});var A=this.get("reveal",B.options);A.reveal().chain(function(){(function(){A.dissolve()}).delay(B.duration||2000)})}});Form.Request.Append=new Class({Extends:Form.Request,options:{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(B,G,F,A){var C;var D=Elements.from(F);if(D.length==1){C=D[0]}else{C=new Element("div",{styles:{display:"none"}}).adopt(D)}C.inject(this.update,this.options.inject);if(this.options.requestOptions.evalScripts){$exec(A)}this.fireEvent("beforeEffect",C);var E=function(){this.fireEvent("success",[C,this.update,B,G,F,A])}.bind(this);if(this.options.useReveal){C.get("reveal",this.options.revealOptions).chain(E);C.reveal()}else{E()}}.bind(this),failure:function(A)
 {this.fireEvent("failure",A)}.bind(this)})}});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: ",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} di
 gits entered."});if(!window.Form){window.Form={}}var InputValidator=new Class({Implements:[Options],options:{errorMsg:"Validation failed.",test:function(A){return true}},initialize:function(B,A){this.setOptions(A);this.className=B},test:function(B,A){if(document.id(B)){return this.options.test(document.id(B),A||this.getProps(B))}else{return false}},getError:function(C,A){var B=this.options.errorMsg;if($type(B)=="function"){B=B(document.id(C),A||this.getProps(C))}return B},getProps:function(A){if(!document.id(A)){return{}}return A.get("validatorProps")}});Element.Properties.validatorProps={set:function(A){return this.eliminate("validatorProps").store("validatorProps",A)},get:function(A){if(A){this.set(A)}if(this.retrieve("validatorProps")){return this.retrieve("validatorProps")}if(this.getProperty("validatorProps")){try{this.store("validatorProps",JSON.decode(this.getProperty("validatorProps")))}catch(C){return{}}}else{var B=this.get("class").split(" ").filter(function(D){ret
 urn D.test(":")});if(!B.length){this.store("validatorProps",{})}else{A={};B.each(function(D){var E=D.split(":");if(E[1]){try{A[E[0]]=JSON.decode(E[1])}catch(F){}}});this.store("validatorProps",A)}}return this.retrieve("validatorProps")}};Form.Validator=new Class({Implements:[Options,Events],Binds:["onSubmit"],options:{fieldSelectors:"input, select, textarea",ignoreHidden:true,ignoreDisabled:true,useTitles:false,evaluateOnSubmit:true,evaluateFieldsOnBlur:true,evaluateFieldsOnChange:true,serial:true,stopOnFailure:true,warningPrefix:function(){return Form.Validator.getMsg("warningPrefix")||"Warning: "},errorPrefix:function(){return Form.Validator.getMsg("errorPrefix")||"Error: "}},initialize:function(B,A){this.setOptions(A);this.element=document.id(B);this.element.store("validator",this);this.warningPrefix=$lambda(this.options.warningPrefix)();this.errorPrefix=$lambda(this.options.errorPrefix)();if(this.options.evaluateOnSubmit){this.element.addEvent("submit",this.onSubmit)}if(
 this.options.evaluateFieldsOnBlur||this.options.evaluateFieldsOnChange){this.watchFields(this.getFields())}},toElement:function(){return this.element},getFields:function(){return(this.fields=this.element.getElements(this.options.fieldSelectors))},watchFields:function(A){A.each(function(B){if(this.options.evaluateFieldsOnBlur){B.addEvent("blur",this.validationMonitor.pass([B,false],this))}if(this.options.evaluateFieldsOnChange){B.addEvent("change",this.validationMonitor.pass([B,true],this))}},this)},validationMonitor:function(){$clear(this.timer);this.timer=this.validateField.delay(50,this,arguments)},onSubmit:function(A){if(!this.validate(A)&&A){A.preventDefault()}else{this.reset()}},reset:function(){this.getFields().each(this.resetField,this);return this},validate:function(B){var A=this.getFields().map(function(C){return this.validateField(C,true)},this).every(function(C){return C});this.fireEvent("formValidate",[A,this.element,B]);if(this.options.stopOnFailure&&!A&&B){B.pr
 eventDefault()}return A},validateField:function(I,A){if(this.paused){return true}I=document.id(I);var D=!I.hasClass("validation-failed");var F,H;if(this.options.serial&&!A){F=this.element.getElement(".validation-failed");H=this.element.getElement(".warning")}if(I&&(!F||A||I.hasClass("validation-failed")||(F&&!this.options.serial))){var C=I.className.split(" ").some(function(J){return this.getValidator(J)},this);var G=[];I.className.split(" ").each(function(J){if(J&&!this.test(J,I)){G.include(J)}},this);D=G.length===0;if(C&&!I.hasClass("warnOnly")){if(D){I.addClass("validation-passed").removeClass("validation-failed");this.fireEvent("elementPass",I)}else{I.addClass("validation-failed").removeClass("validation-passed");this.fireEvent("elementFail",[I,G])}}if(!H){var E=I.className.split(" ").some(function(J){if(J.test("^warn-")||I.hasClass("warnOnly")){return this.getValidator(J.replace(/^warn-/,""))}else{return null}},this);I.removeClass("warning");var B=I.className.split(" ")
 .map(function(J){if(J.test("^warn-")||I.hasClass("warnOnly")){return this.test(J.replace(/^warn-/,""),I,true)}else{return null}},this)}}return D},test:function(B,D,E){D=document.id(D);if((this.options.ignoreHidden&&!D.isVisible())||(this.options.ignoreDisabled&&D.get("disabled"))){return true}var A=this.getValidator(B);if(D.hasClass("ignoreValidation")){return true}E=$pick(E,false);if(D.hasClass("warnOnly")){E=true}var C=A?A.test(D):true;if(A&&D.isVisible()){this.fireEvent("elementValidate",[C,D,B,E])}if(E){return true}return C},resetField:function(A){A=document.id(A);if(A){A.className.split(" ").each(function(B){if(B.test("^warn-")){B=B.replace(/^warn-/,"")}A.removeClass("validation-failed");A.removeClass("warning");A.removeClass("validation-passed")},this)}return this},stop:function(){this.paused=true;return this},start:function(){this.paused=false;return this},ignoreField:function(A,B){A=document.id(A);if(A){this.enforceField(A);if(B){A.addClass("warnOnly")}else{A.addClas
 s("ignoreValidation")}}return this},enforceField:function(A){A=document.id(A);if(A){A.removeClass("warnOnly").removeClass("ignoreValidation")}return this}});Form.Validator.getMsg=function(A){return MooTools.lang.get("Form.Validator",A)};Form.Validator.adders={validators:{},add:function(B,A){this.validators[B]=new InputValidator(B,A);if(!this.initialize){this.implement({validators:this.validators})}},addAllThese:function(A){$A(A).each(function(B){this.add(B[0],B[1])},this)},getValidator:function(A){return this.validators[A.split(":")[0]]}};$extend(Form.Validator,Form.Validator.adders);Form.Validator.implement(Form.Validator.adders);Form.Validator.add("IsEmpty",{errorMsg:false,test:function(A){if(A.type=="select-one"||A.type=="select"){return !(A.selectedIndex>=0&&A.options[A.selectedIndex].value!="")}else{return((A.get("value")==null)||(A.get("value").length==0))}}});Form.Validator.addAllThese([["required",{errorMsg:function(){return Form.Validator.getMsg("required")},test:fu
 nction(A){return !Form.Validator.getValidator("IsEmpty").test(A)}}],["minLength",{errorMsg:function(A,B){if($type(B.minLength)){return Form.Validator.getMsg("minLength").substitute({minLength:B.minLength,length:A.get("value").length})}else{return""}},test:function(A,B){if($type(B.minLength)){return(A.get("value").length>=$pick(B.minLength,0))}else{return true}}}],["maxLength",{errorMsg:function(A,B){if($type(B.maxLength)){return Form.Validator.getMsg("maxLength").substitute({maxLength:B.maxLength,length:A.get("value").length})}else{return""}},test:function(A,B){return(A.get("value").length<=$pick(B.maxLength,10000))}}],["validate-integer",{errorMsg:Form.Validator.getMsg.pass("integer"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^(-?[1-9]\d*|0)$/).test(A.get("value"))}}],["validate-numeric",{errorMsg:Form.Validator.getMsg.pass("numeric"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d
 +)?$/).test(A.get("value"))}}],["validate-digits",{errorMsg:Form.Validator.getMsg.pass("digits"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^[\d() .:\-\+#]+$/.test(A.get("value")))}}],["validate-alpha",{errorMsg:Form.Validator.getMsg.pass("alpha"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^[a-zA-Z]+$/).test(A.get("value"))}}],["validate-alphanum",{errorMsg:Form.Validator.getMsg.pass("alphanum"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||!(/\W/).test(A.get("value"))}}],["validate-date",{errorMsg:function(A,B){if(Date.parse){var C=B.dateFormat||"%x";return Form.Validator.getMsg("dateSuchAs").substitute({date:new Date().format(C)})}else{return Form.Validator.getMsg("dateInFormatMDY")}},test:function(A,B){if(Form.Validator.getValidator("IsEmpty").test(A)){return true}var F;if(Date.parse){var E=B.dateFormat||"%x";F=Date.parse(A.get("value"));var D=F.format(E);if(D!="invalid date"){A.set("v
 alue",D)}return !isNaN(F)}else{var C=/^(\d{2})\/(\d{2})\/(\d{4})$/;if(!C.test(A.get("value"))){return false}F=new Date(A.get("value").replace(C,"$1/$2/$3"));return(parseInt(RegExp.$1,10)==(1+F.getMonth()))&&(parseInt(RegExp.$2,10)==F.getDate())&&(parseInt(RegExp.$3,10)==F.getFullYear())}}}],["validate-email",{errorMsg:Form.Validator.getMsg.pass("email"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(A.get("value"))}}],["validate-url",{errorMsg:Form.Validator.getMsg.pass("url"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(A.get("value"))}}],["validate-currency-dollar",{errorMsg:Form.Validator.getMsg.pass("currencyDollar"),test:function(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^\$?\-?([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(A.get("value"))}}],["validate-one-required",{errorMsg:Form.Validator.getMsg.pass("oneRequired"),test:function(A,B){var C=document.id(B["validate-one-required"])||A.getParent();return C.getElements("input").some(function(D){if(["checkbox","radio"].contains(D.get("type"))){return D.get("checked")}return D.get("value")})}}]]);Element.Properties.validator={set:function(A){var B=this.retrieve("validator");if(B){B.setOptions(A)}return this.store("validator:options")},get:function(A){if(A||!this.retrieve("validator")){if(A||!this.retrieve("validator:options")){this.set("validator",A)}this.store("validator",new Form.Validator(this,this.retrieve("validator:options")))}return this.retrieve("validator")}};Element.implement({validate:function(A){this.set("validator",A);return this.get("validator",A).validate()}});var FormValidator=Form.Validator;Form.Validator.Inline=new Class({Extends:Form.Validator,options:{scrollToErrorsOnSubmit:true,scrollFxOpti
 ons:{transition:"quad:out",offset:{y:-20}}},initialize:function(B,A){this.parent(B,A);this.addEvent("onElementValidate",function(G,F,E,H){var D=this.getValidator(E);if(!G&&D.getError(F)){if(H){F.addClass("warning")}var C=this.makeAdvice(E,F,D.getError(F),H);this.insertAdvice(C,F);this.showAdvice(E,F)}else{this.hideAdvice(E,F)}})},makeAdvice:function(D,F,C,G){var E=(G)?this.warningPrefix:this.errorPrefix;E+=(this.options.useTitles)?F.title||C:C;var A=(G)?"warning-advice":"validation-advice";var B=this.getAdvice(D,F);if(B){B=B.set("html",E)}else{B=new Element("div",{html:E,styles:{display:"none"},id:"advice-"+D+"-"+this.getFieldId(F)}).addClass(A)}F.store("advice-"+D,B);return B},getFieldId:function(A){return A.id?A.id:A.id="input_"+A.name},showAdvice:function(B,C){var A=this.getAdvice(B,C);if(A&&!C.retrieve(this.getPropName(B))&&(A.getStyle("display")=="none"||A.getStyle("visiblity")=="hidden"||A.getStyle("opacity")==0)){C.store(this.getPropName(B),true);if(A.reveal){A.reveal
 ()}else{A.setStyle("display","block")}}},hideAdvice:function(B,C){var A=this.getAdvice(B,C);if(A&&C.retrieve(this.getPropName(B))){C.store(this.getPropName(B),false);if(A.dissolve){A.dissolve()}else{A.setStyle("display","none")}}},getPropName:function(A){return"advice"+A},resetField:function(A){A=document.id(A);if(!A){return this}this.parent(A);A.className.split(" ").each(function(B){this.hideAdvice(B,A)},this);return this},getAllAdviceMessages:function(D,C){var B=[];if(D.hasClass("ignoreValidation")&&!C){return B}var A=D.className.split(" ").some(function(G){var E=G.test("^warn-")||D.hasClass("warnOnly");if(E){G=G.replace(/^warn-/,"")}var F=this.getValidator(G);if(!F){return }B.push({message:F.getError(D),warnOnly:E,passed:F.test(),validator:F})},this);return B},getAdvice:function(A,B){return B.retrieve("advice-"+A)},insertAdvice:function(A,C){var B=C.get("validatorProps");if(!B.msgPos||!document.id(B.msgPos)){if(C.type.toLowerCase()=="radio"){C.getParent().adopt(A)}else{A.
 inject(document.id(C),"after")}}else{document.id(B.msgPos).grab(A)}},validateField:function(F,E){var A=this.parent(F,E);if(this.options.scrollToErrorsOnSubmit&&!A){var B=document.id(this).getElement(".validation-failed");var C=document.id(this).getParent();while(C!=document.body&&C.getScrollSize().y==C.getSize().y){C=C.getParent()}var D=C.retrieve("fvScroller");if(!D&&window.Fx&&Fx.Scroll){D=new Fx.Scroll(C,this.options.scrollFxOptions);C.store("fvScroller",D)}if(B){if(D){D.toElement(B)}else{C.scrollTo(C.getScroll().x,B.getPosition(C).y-20)}}}return A}});Form.Validator.addAllThese([["validate-enforce-oncheck",{test:function(A,B){if(A.checked){var C=A.getParent("form").retrieve("validator");if(!C){return true}(B.toEnforce||document.id(B.enforceChildrenOf).getElements("input, select, textarea")).map(function(D){C.enforceField(D)})}return true}}],["validate-ignore-oncheck",{test:function(A,B){if(A.checked){var C=A.getParent("form").retrieve("validator");if(!C){return true}(B.to
 Ignore||document.id(B.ignoreChildrenOf).getElements("input, select, textarea")).each(function(D){C.ignoreField(D);C.resetField(D)})}return true}}],["validate-nospace",{errorMsg:function(){return Form.Validator.getMsg("noSpace")},test:function(A,B){return !A.get("value").test(/\s/)}}],["validate-toggle-oncheck",{test:function(B,C){var D=B.getParent("form").retrieve("validator");if(!D){return true}var A=C.toToggle||document.id(C.toToggleChildrenOf).getElements("input, select, textarea");if(!B.checked){A.each(function(E){D.ignoreField(E);D.resetField(E)})}else{A.each(function(E){D.enforceField(E)})}return true}}],["validate-reqchk-bynode",{errorMsg:function(){return Form.Validator.getMsg("reqChkByNode")},test:function(A,B){return(document.id(B.nodeId).getElements(B.selector||"input[type=checkbox], input[type=radio]")).some(function(C){return C.checked})}}],["validate-required-check",{errorMsg:function(A,B){return B.useTitle?A.get("title"):Form.Validator.getMsg("requiredChk")},t
 est:function(A,B){return !!A.checked}}],["validate-reqchk-byname",{errorMsg:function(A,B){return Form.Validator.getMsg("reqChkByName").substitute({label:B.label||A.get("type")})},test:function(B,D){var C=D.groupName||B.get("name");var A=$$(document.getElementsByName(C)).some(function(G,F){return G.checked});var E=B.getParent("form").retrieve("validator");if(A&&E){E.resetField(B)}return A}}],["validate-match",{errorMsg:function(A,B){return Form.Validator.getMsg("match").substitute({matchName:B.matchName||document.id(B.matchInput).get("name")})},test:function(B,C){var D=B.get("value");var A=document.id(C.matchInput)&&document.id(C.matchInput).get("value");return D&&A?D==A:true}}],["validate-after-date",{errorMsg:function(A,B){return Form.Validator.getMsg("afterDate").substitute({label:B.afterLabel||(B.afterElement?Form.Validator.getMsg("startDate"):Form.Validator.getMsg("currentDate"))})},test:function(B,C){var D=document.id(C.afterElement)?Date.parse(document.id(C.afterElemen
 t).get("value")):new Date();var A=Date.parse(B.get("value"));return A&&D?A>=D:true}}],["validate-before-date",{errorMsg:function(A,B){return Form.Validator.getMsg("beforeDate").substitute({label:B.beforeLabel||(B.beforeElement?Form.Validator.getMsg("endDate"):Form.Validator.getMsg("currentDate"))})},test:function(B,C){var D=Date.parse(B.get("value"));var A=document.id(C.beforeElement)?Date.parse(document.id(C.beforeElement).get("value")):new Date();return A&&D?A>=D:true}}],["validate-custom-required",{errorMsg:function(){return Form.Validator.getMsg("required")},test:function(A,B){return A.get("value")!=B.emptyValue}}],["validate-same-month",{errorMsg:function(A,B){var C=document.id(B.sameMonthAs)&&document.id(B.sameMonthAs).get("value");var D=A.get("value");if(D!=""){return Form.Validator.getMsg(C?"sameMonth":"startMonth")}},test:function(A,B){var D=Date.parse(A.get("value"));var C=Date.parse(document.id(B.sameMonthAs)&&document.id(B.sameMonthAs).get("value"));return D&&C?D
 .format("%B")==C.format("%B"):true}}],["validate-cc-num",{errorMsg:function(A){var B=A.get("value").replace(/[^0-9]/g,"");return Form.Validator.getMsg("creditcard").substitute({length:B.length})},test:function(C){if(Form.Validator.getValidator("IsEmpty").test(C)){return true}var G=C.get("value");G=G.replace(/[^0-9]/g,"");var A=false;if(G.test(/^4[0-9]{12}([0-9]{3})?$/)){A="Visa"}else{if(G.test(/^5[1-5]([0-9]{14})$/)){A="Master Card"}else{if(G.test(/^3[47][0-9]{13}$/)){A="American Express"}else{if(G.test(/^6011[0-9]{12}$/)){A="Discover"}}}}if(A){var D=0;var E=0;for(var B=G.length-1;B>=0;--B){E=G.charAt(B).toInt();if(E==0){continue}if((G.length-B)%2==0){E+=E}if(E>9){E=E.toString().charAt(0).toInt()+E.toString().charAt(1).toInt()}D+=E}if((D%10)==0){return true}}var F="";while(G!=""){F+=" "+G.substr(0,4);G=G.substr(4)}C.getParent("form").retrieve("validator").ignoreField(C);C.set("value",F.clean());C.getParent("form").retrieve("validator").enforceField(C);return false}}]]);var O
 verText=new Class({Implements:[Options,Events,Class.Occlude],Binds:["reposition","assert","focus","hide"],options:{element:"label",positionOptions:{position:"upperLeft",edge:"upperLeft",offset:{x:4,y:2}},poll:false,pollInterval:250,wrap:false},property:"OverText",initialize:function(B,A){this.element=document.id(B);if(this.occlude()){return this.occluded}this.setOptions(A);this.attach(this.element);OverText.instances.push(this);if(this.options.poll){this.poll()}return this},toElement:function(){return this.element},attach:function(){var A=this.options.textOverride||this.element.get("alt")||this.element.get("title");if(!A){return }this.text=new Element(this.options.element,{"class":"overTxtLabel",styles:{lineHeight:"normal",position:"absolute",cursor:"text"},html:A,events:{click:this.hide.pass(this.options.element=="label",this)}}).inject(this.element,"after");if(this.options.element=="label"){if(!this.element.get("id")){this.element.set("id","input_"+new Date().getTime())}th
 is.text.set("for",this.element.get("id"))}if(this.options.wrap){this.textHolder=new Element("div",{styles:{lineHeight:"normal",position:"relative"},"class":"overTxtWrapper"}).adopt(this.text).inject(this.element,"before")}this.element.addEvents({focus:this.focus,blur:this.assert,change:this.assert}).store("OverTextDiv",this.text);window.addEvent("resize",this.reposition.bind(this));this.assert(true);this.reposition()},wrap:function(){if(this.options.element=="label"){if(!this.element.get("id")){this.element.set("id","input_"+new Date().getTime())}this.text.set("for",this.element.get("id"))}},startPolling:function(){this.pollingPaused=false;return this.poll()},poll:function(A){if(this.poller&&!A){return this}var B=function(){if(!this.pollingPaused){this.assert(true)}}.bind(this);if(A){$clear(this.poller)}else{this.poller=B.periodical(this.options.pollInterval,this)}return this},stopPolling:function(){this.pollingPaused=true;return this.poll(true)},focus:function(){if(this.tex
 t&&(!this.text.isDisplayed()||this.element.get("disabled"))){return }this.hide()},hide:function(C,A){if(this.text&&(this.text.isDisplayed()&&(!this.element.get("disabled")||A))){this.text.hide();this.fireEvent("textHide",[this.text,this.element]);this.pollingPaused=true;if(!C){try{this.element.fireEvent("focus");this.element.focus()}catch(B){}}}return this},show:function(){if(this.text&&!this.text.isDisplayed()){this.text.show();this.reposition();this.fireEvent("textShow",[this.text,this.element]);this.pollingPaused=false}return this},assert:function(A){this[this.test()?"show":"hide"](A)},test:function(){var A=this.element.get("value");return !A},reposition:function(){this.assert(true);if(!this.element.isVisible()){return this.stopPolling().hide()}if(this.text&&this.test()){this.text.position($merge(this.options.positionOptions,{relativeTo:this.element}))}return this}});OverText.instances=[];$extend(OverText,{each:function(A){return OverText.instances.map(function(C,B){if(C.
 element&&C.text){return A.apply(OverText,[C,B])}return null})},update:function(){return OverText.each(function(A){return A.reposition()})},hideAll:function(){return OverText.each(function(A){return A.hide(true,true)})},showAll:function(){return OverText.each(function(A){return A.show()})}});if(window.Fx&&Fx.Reveal){Fx.Reveal.implement({hideInputs:Browser.Engine.trident?"select, input, textarea, object, embed, .overTxtLabel":false})}Fx.Elements=new Class({Extends:Fx.CSS,initialize:function(B,A){this.elements=this.subject=$$(B);this.parent(A)},compute:function(G,H,I){var C={};for(var D in G){var A=G[D],E=H[D],F=C[D]={};for(var B in A){F[B]=this.parent(A[B],E[B],I)}}return C},set:function(B){for(var C in B){var A=B[C];for(var D in A){this.render(this.elements[C],D,A[D],this.options.unit)}}return this},start:function(C){if(!this.check(C)){return this}var H={},I={};for(var D in C){var F=C[D],A=H[D]={},G=I[D]={};for(var B in F){var E=this.prepare(this.elements[D],B,F[B]);A[B]=E.fr
 om;G[B]=E.to}}return this.parent(H,I)}});Fx.Accordion=new Class({Extends:Fx.Elements,options:{display:0,show:false,height:true,width:false,opacity:true,alwaysHide:false,trigger:"click",initialDisplayFx:true,returnHeightToAuto:true},initialize:function(){var C=Array.link(arguments,{container:Element.type,options:Object.type,togglers:$defined,elements:$defined});this.parent(C.elements,C.options);this.togglers=$$(C.togglers);this.previous=-1;this.internalChain=new Chain();if(this.options.alwaysHide){this.options.wait=true}if($chk(this.options.show)){this.options.display=false;this.previous=this.options.show}if(this.options.start){this.options.display=false;this.options.show=false}this.effects={};if(this.options.opacity){this.effects.opacity="fullOpacity"}if(this.options.width){this.effects.width=this.options.fixedWidth?"fullWidth":"offsetWidth"}if(this.options.height){this.effects.height=this.options.fixedHeight?"fullHeight":"scrollHeight"}for(var B=0,A=this.togglers.length;B<A
 ;B++){this.addSection(this.togglers[B],this.elements[B])}this.elements.each(function(E,D){if(this.options.show===D){this.fireEvent("active",[this.togglers[D],E])}else{for(var F in this.effects){E.setStyle(F,0)}}},this);if($chk(this.options.display)||this.options.initialDisplayFx===false){this.display(this.options.display,this.options.initialDisplayFx)}if(this.options.fixedHeight!==false){this.options.returnHeightToAuto=false}this.addEvent("complete",this.internalChain.callChain.bind(this.internalChain))},addSection:function(E,C){E=document.id(E);C=document.id(C);var F=this.togglers.contains(E);this.togglers.include(E);this.elements.include(C);var A=this.togglers.indexOf(E);var B=this.display.bind(this,A);E.store("accordion:display",B);E.addEvent(this.options.trigger,B);if(this.options.height){C.setStyles({"padding-top":0,"border-top":"none","padding-bottom":0,"border-bottom":"none"})}if(this.options.width){C.setStyles({"padding-left":0,"border-left":"none","padding-right":0,
 "border-right":"none"})}C.fullOpacity=1;if(this.options.fixedWidth){C.fullWidth=this.options.fixedWidth}if(this.options.fixedHeight){C.fullHeight=this.options.fixedHeight}C.setStyle("overflow","hidden");if(!F){for(var D in this.effects){C.setStyle(D,0)}}return this},detach:function(){this.togglers.each(function(A){A.removeEvent(this.options.trigger,A.retrieve("accordion:display"))},this)},display:function(A,B){if(!this.check(A,B)){return this}B=$pick(B,true);if(this.options.returnHeightToAuto){var D=this.elements[this.previous];if(D&&!this.selfHidden){for(var C in this.effects){D.setStyle(C,D[this.effects[C]])}}}A=($type(A)=="element")?this.elements.indexOf(A):A;if((this.timer&&this.options.wait)||(A===this.previous&&!this.options.alwaysHide)){return this}this.previous=A;var E={};this.elements.each(function(H,G){E[G]={};var F;if(G!=A){F=true}else{if(this.options.alwaysHide&&((H.offsetHeight>0&&this.options.height)||H.offsetWidth>0&&this.options.width)){F=true;this.selfHidden
 =true}}this.fireEvent(F?"background":"active",[this.togglers[G],H]);for(var I in this.effects){E[G][I]=F?0:H[this.effects[I]]}},this);this.internalChain.chain(function(){if(this.options.returnHeightToAuto&&!this.selfHidden){var F=this.elements[A];if(F){F.setStyle("height","auto")}}}.bind(this));return B?this.start(E):this.set(E)}});var Accordion=new Class({Extends:Fx.Accordion,initialize:function(){this.parent.apply(this,arguments);var A=Array.link(arguments,{container:Element.type});this.container=A.container},addSection:function(C,B,E){C=document.id(C);B=document.id(B);var D=this.togglers.contains(C);var A=this.togglers.length;if(A&&(!D||E)){E=$pick(E,A-1);C.inject(this.togglers[E],"before");B.inject(C,"after")}else{if(this.container&&!D){C.inject(this.container);B.inject(this.container)}}return this.parent.apply(this,arguments)}});Fx.Move=new Class({Extends:Fx.Morph,options:{relativeTo:document.body,position:"center",edge:false,offset:{x:0,y:0}},start:function(A){return t
 his.parent(this.element.position($merge(this.options,A,{returnPos:true})))}});Element.Properties.move={set:function(A){var B=this.retrieve("move");if(B){B.cancel()}return this.eliminate("move").store("move:options",$extend({link:"cancel"},A))},get:function(A){if(A||!this.retrieve("move")){if(A||!this.retrieve("move:options")){this.set("move",A)}this.store("move",new Fx.Move(this,this.retrieve("move:options")))}return this.retrieve("move")}};Element.implement({move:function(A){this.get("move").start(A);return this}});Fx.Scroll=new Class({Extends:Fx,options:{offset:{x:0,y:0},wheelStops:true},initialize:function(B,A){this.element=this.subject=document.id(B);this.parent(A);var D=this.cancel.bind(this,false);if($type(this.element)!="element"){this.element=document.id(this.element.getDocument().body)}var C=this.element;if(this.options.wheelStops){this.addEvent("start",function(){C.addEvent("mousewheel",D)},true);this.addEvent("complete",function(){C.removeEvent("mousewheel",D)},tr
 ue)}},set:function(){var A=Array.flatten(arguments);if(Browser.Engine.gecko){A=[Math.round(A[0]),Math.round(A[1])]}this.element.scrollTo(A[0],A[1])},compute:function(C,B,A){return[0,1].map(function(D){return Fx.compute(C[D],B[D],A)})},start:function(C,G){if(!this.check(C,G)){return this}var E=this.element.getScrollSize(),B=this.element.getScroll(),D={x:C,y:G};for(var F in D){var A=E[F];if($chk(D[F])){D[F]=($type(D[F])=="number")?D[F]:A}else{D[F]=B[F]}D[F]+=this.options.offset[F]}return this.parent([B.x,B.y],[D.x,D.y])},toTop:function(){return this.start(false,0)},toLeft:function(){return this.start(0,false)},toRight:function(){return this.start("right",false)},toBottom:function(){return this.start(false,"bottom")},toElement:function(B){var A=document.id(B).getPosition(this.element);return this.start(A.x,A.y)},scrollIntoView:function(C,E,D){E=E?$splat(E):["x","y"];var H={};C=document.id(C);var F=C.getPosition(this.element);var I=C.getSize();var G=this.element.getScroll();var 
 A=this.element.getSize();var B={x:F.x+I.x,y:F.y+I.y};["x","y"].each(function(J){if(E.contains(J)){if(B[J]>G[J]+A[J]){H[J]=B[J]-A[J]}if(F[J]<G[J]){H[J]=F[J]}}if(H[J]==null){H[J]=G[J]}if(D&&D[J]){H[J]=H[J]+D[J]}},this);if(H.x!=G.x||H.y!=G.y){this.start(H.x,H.y)}return this},scrollToCenter:function(C,E,D){E=E?$splat(E):["x","y"];C=$(C);var H={},F=C.getPosition(this.element),I=C.getSize(),G=this.element.getScroll(),A=this.element.getSize(),B={x:F.x+I.x,y:F.y+I.y};["x","y"].each(function(J){if(E.contains(J)){H[J]=F[J]-(A[J]-I[J])/2}if(H[J]==null){H[J]=G[J]}if(D&&D[J]){H[J]=H[J]+D[J]}},this);if(H.x!=G.x||H.y!=G.y){this.start(H.x,H.y)}return this}});Fx.Slide=new Class({Extends:Fx,options:{mode:"vertical",wrapper:false,hideOverflow:true},initialize:function(B,A){this.addEvent("complete",function(){this.open=(this.wrapper["offset"+this.layout.capitalize()]!=0);if(this.open){this.wrapper.setStyle("height","")}if(this.open&&Browser.Engine.webkit419){this.element.dispose().inject(this.w
 rapper)}},true);this.element=this.subject=document.id(B);this.parent(A);var D=this.element.retrieve("wrapper");var C=this.element.getStyles("margin","position","overflow");if(this.options.hideOverflow){C=$extend(C,{overflow:"hidden"})}if(this.options.wrapper){D=document.id(this.options.wrapper).setStyles(C)}this.wrapper=D||new Element("div",{styles:C}).wraps(this.element);this.element.store("wrapper",this.wrapper).setStyle("margin",0);this.now=[];this.open=true},vertical:function(){this.margin="margin-top";this.layout="height";this.offset=this.element.offsetHeight},horizontal:function(){this.margin="margin-left";this.layout="width";this.offset=this.element.offsetWidth},set:function(A){this.element.setStyle(this.margin,A[0]);this.wrapper.setStyle(this.layout,A[1]);return this},compute:function(C,B,A){return[0,1].map(function(D){return Fx.compute(C[D],B[D],A)})},start:function(B,E){if(!this.check(B,E)){return this}this[E||this.options.mode]();var D=this.element.getStyle(this.m
 argin).toInt();var C=this.wrapper.getStyle(this.layout).toInt();var A=[[D,C],[0,this.offset]];var G=[[D,C],[-this.offset,0]];var F;switch(B){case"in":F=A;break;case"out":F=G;break;case"toggle":F=(C==0)?A:G}return this.parent(F[0],F[1])},slideIn:function(A){return this.start("in",A)},slideOut:function(A){return this.start("out",A)},hide:function(A){this[A||this.options.mode]();this.open=false;return this.set([-this.offset,0])},show:function(A){this[A||this.options.mode]();this.open=true;return this.set([0,this.offset])},toggle:function(A){return this.start("toggle",A)}});Element.Properties.slide={set:function(B){var A=this.retrieve("slide");if(A){A.cancel()}return this.eliminate("slide").store("slide:options",$extend({link:"cancel"},B))},get:function(A){if(A||!this.retrieve("slide")){if(A||!this.retrieve("slide:options")){this.set("slide",A)}this.store("slide",new Fx.Slide(this,this.retrieve("slide:options")))}return this.retrieve("slide")}};Element.implement({slide:function(
 D,E){D=D||"toggle";var B=this.get("slide"),A;switch(D){case"hide":B.hide(E);break;case"show":B.show(E);break;case"toggle":var C=this.retrieve("slide:flag",B.open);B[C?"slideOut":"slideIn"](E);this.store("slide:flag",!C);A=true;break;default:B.start(D,E)}if(!A){this.eliminate("slide:flag")}return this}});var SmoothScroll=Fx.SmoothScroll=new Class({Extends:Fx.Scroll,initialize:function(B,C){C=C||document;this.doc=C.getDocument();var D=C.getWindow();this.parent(this.doc,B);this.links=$$(this.options.links||this.doc.links);var A=D.location.href.match(/^[^#]*/)[0]+"#";this.links.each(function(F){if(F.href.indexOf(A)!=0){return }var E=F.href.substr(A.length);if(E){this.useLink(F,E)}},this);if(!Browser.Engine.webkit419){this.addEvent("complete",function(){D.location.hash=this.anchor},true)}},useLink:function(C,A){var B;C.addEvent("click",function(D){if(B!==false&&!B){B=document.id(A)||this.doc.getElement("a[name="+A+"]")}if(B){D.preventDefault();this.anchor=A;this.toElement(B).chai
 n(function(){this.fireEvent("scrolledTo",[C,B])}.bind(this));C.blur()}}.bind(this))}});Fx.Sort=new Class({Extends:Fx.Elements,options:{mode:"vertical"},initialize:function(B,A){this.parent(B,A);this.elements.each(function(C){if(C.getStyle("position")=="static"){C.setStyle("position","relative")}});this.setDefaultOrder()},setDefaultOrder:function(){this.currentOrder=this.elements.map(function(B,A){return A})},sort:function(E){if($type(E)!="array"){return false}var I=0,A=0,C={},H={},D=this.options.mode=="vertical";var F=this.elements.map(function(M,J){var L=M.getComputedSize({styles:["border","padding","margin"]});var N;if(D){N={top:I,margin:L["margin-top"],height:L.totalHeight};I+=N.height-L["margin-top"]}else{N={left:A,margin:L["margin-left"],width:L.totalWidth};A+=N.width}var K=D?"top":"left";H[J]={};var O=M.getStyle(K).toInt();H[J][K]=O||0;return N},this);this.set(H);E=E.map(function(J){return J.toInt()});if(E.length!=this.elements.length){this.currentOrder.each(function(J
 ){if(!E.contains(J)){E.push(J)}});if(E.length>this.elements.length){E.splice(this.elements.length-1,E.length-this.elements.length)}}var B=I=A=0;E.each(function(L,J){var K={};if(D){K.top=I-F[L].top-B;I+=F[L].height}else{K.left=A-F[L].left;A+=F[L].width}B=B+F[L].margin;C[L]=K},this);var G={};$A(E).sort().each(function(J){G[J]=C[J]});this.start(G);this.currentOrder=E;return this},rearrangeDOM:function(A){A=A||this.currentOrder;var B=this.elements[0].getParent();var C=[];this.elements.setStyle("opacity",0);A.each(function(D){C.push(this.elements[D].inject(B).setStyles({top:0,left:0}))},this);this.elements.setStyle("opacity",1);this.elements=$$(C);this.setDefaultOrder();return this},getDefaultOrder:function(){return this.elements.map(function(B,A){return A})},forward:function(){return this.sort(this.getDefaultOrder())},backward:function(){return this.sort(this.getDefaultOrder().reverse())},reverse:function(){return this.sort(this.currentOrder.reverse())},sortByElements:function(A
 ){return this.sort(A.map(function(B){return this.elements.indexOf(B)},this))},swap:function(C,B){if($type(C)=="element"){C=this.elements.indexOf(C)}if($type(B)=="element"){B=this.elements.indexOf(B)}var A=$A(this.currentOrder);A[this.currentOrder.indexOf(C)]=B;A[this.currentOrder.indexOf(B)]=C;return this.sort(A)}});var Drag=new Class({Implements:[Events,Options],options:{snap:6,unit:"px",grid:false,style:true,limit:false,handle:false,invert:false,preventDefault:false,stopPropagation:false,modifiers:{x:"left",y:"top"}},initialize:function(){var B=Array.link(arguments,{options:Object.type,element:$defined});this.element=document.id(B.element);this.document=this.element.getDocument();this.setOptions(B.options||{});var A=$type(this.options.handle);this.handles=((A=="array"||A=="collection")?$$(this.options.handle):document.id(this.options.handle))||this.element;this.mouse={now:{},pos:{}};this.value={start:{},now:{}};this.selection=(Browser.Engine.trident)?"selectstart":"mousedo
 wn";this.bound={start:this.start.bind(this),check:this.check.bind(this),drag:this.drag.bind(this),stop:this.stop.bind(this),cancel:this.cancel.bind(this),eventStop:$lambda(false)};this.attach()},attach:function(){this.handles.addEvent("mousedown",this.bound.start);return this},detach:function(){this.handles.removeEvent("mousedown",this.bound.start);return this},start:function(C){if(C.rightClick){return }if(this.options.preventDefault){C.preventDefault()}if(this.options.stopPropagation){C.stopPropagation()}this.mouse.start=C.page;this.fireEvent("beforeStart",this.element);var A=this.options.limit;this.limit={x:[],y:[]};for(var D in this.options.modifiers){if(!this.options.modifiers[D]){continue}if(this.options.style){this.value.now[D]=this.element.getStyle(this.options.modifiers[D]).toInt()}else{this.value.now[D]=this.element[this.options.modifiers[D]]}if(this.options.invert){this.value.now[D]*=-1}this.mouse.pos[D]=C.page[D]-this.value.now[D];if(A&&A[D]){for(var B=2;B--;B){if
 ($chk(A[D][B])){this.limit[D][B]=$lambda(A[D][B])()}}}}if($type(this.options.grid)=="number"){this.options.grid={x:this.options.grid,y:this.options.grid}}this.document.addEvents({mousemove:this.bound.check,mouseup:this.bound.cancel});this.document.addEvent(this.selection,this.bound.eventStop)},check:function(A){if(this.options.preventDefault){A.preventDefault()}var B=Math.round(Math.sqrt(Math.pow(A.page.x-this.mouse.start.x,2)+Math.pow(A.page.y-this.mouse.start.y,2)));if(B>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});this.fireEvent("start",[this.element,A]).fireEvent("snap",this.element)}},drag:function(A){if(this.options.preventDefault){A.preventDefault()}this.mouse.now=A.page;for(var B in this.options.modifiers){if(!this.options.modifiers[B]){continue}this.value.now[B]=this.mouse.now[B]-this.mouse.pos[B];if(this.options.invert){this.value.now[B]*=-1}if(this.options.limit&&this.limit[B]){if($chk(this.limit[B][
 1])&&(this.value.now[B]>this.limit[B][1])){this.value.now[B]=this.limit[B][1]}else{if($chk(this.limit[B][0])&&(this.value.now[B]<this.limit[B][0])){this.value.now[B]=this.limit[B][0]}}}if(this.options.grid[B]){this.value.now[B]-=((this.value.now[B]-(this.limit[B][0]||0))%this.options.grid[B])}if(this.options.style){this.element.setStyle(this.options.modifiers[B],this.value.now[B]+this.options.unit)}else{this.element[this.options.modifiers[B]]=this.value.now[B]}}this.fireEvent("drag",[this.element,A])},cancel:function(A){this.document.removeEvent("mousemove",this.bound.check);this.document.removeEvent("mouseup",this.bound.cancel);if(A){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element)}},stop:function(A){this.document.removeEvent(this.selection,this.bound.eventStop);this.document.removeEvent("mousemove",this.bound.drag);this.document.removeEvent("mouseup",this.bound.stop);if(A){this.fireEvent("complete",[this.element,A])}}});E
 lement.implement({makeResizable:function(A){var B=new Drag(this,$merge({modifiers:{x:"width",y:"height"}},A));this.store("resizer",B);return B.addEvent("drag",function(){this.fireEvent("resize",B)}.bind(this))}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(B,A){this.parent(B,A);B=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&$type(this.container)!="element"){this.container=document.id(this.container.getDocument().body)}var C=B.getStyles("left","top","position");if(C.left=="auto"||C.top=="auto"){B.setPosition(B.getPosition(B.getOffsetParent()))}if(C.position=="static"){B.setStyle("position","absolute")}this.addEvent("start",this.checkDroppables,true);this.overed=null},start:function(A){if(this.container){this.options.limit=this.calculateLimit()}if(this.options.precalculate){this.positions
 =this.droppables.map(function(B){return B.getCoordinates()})}this.parent(A)},calculateLimit:function(){var D=this.element.getOffsetParent(),G=this.container.getCoordinates(D),F={},C={},B={},I={},K={};["top","right","bottom","left"].each(function(O){F[O]=this.container.getStyle("border-"+O).toInt();B[O]=this.element.getStyle("border-"+O).toInt();C[O]=this.element.getStyle("margin-"+O).toInt();I[O]=this.container.getStyle("margin-"+O).toInt();K[O]=D.getStyle("padding-"+O).toInt()},this);var E=this.element.offsetWidth+C.left+C.right,N=this.element.offsetHeight+C.top+C.bottom,H=0,J=0,M=G.right-F.right-E,A=G.bottom-F.bottom-N;if(this.options.includeMargins){H+=C.left;J+=C.top}else{M+=C.right;A+=C.bottom}if(this.element.getStyle("position")=="relative"){var L=this.element.getCoordinates(D);L.left-=this.element.getStyle("left").toInt();L.top-=this.element.getStyle("top").toInt();H+=F.left-L.left;J+=F.top-L.top;M+=C.left-L.left;A+=C.top-L.top;if(this.container!=D){H+=I.left+K.left;J
 +=(Browser.Engine.trident4?0:I.top)+K.top}}else{H-=C.left;J-=C.top;if(this.container==D){M-=F.left;A-=F.top}else{H+=G.left+F.left;J+=G.top+F.top}}return{x:[H,M],y:[J,A]}},checkAgainst:function(C,B){C=(this.positions)?this.positions[B]:C.getCoordinates();var A=this.mouse.now;return(A.x>C.left&&A.x<C.right&&A.y<C.bottom&&A.y>C.top)},checkDroppables:function(){var A=this.droppables.filter(this.checkAgainst,this).getLast();if(this.overed!=A){if(this.overed){this.fireEvent("leave",[this.element,this.overed])}if(A){this.fireEvent("enter",[this.element,A])}this.overed=A}},drag:function(A){this.parent(A);if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables()}},stop:function(A){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,A]);this.overed=null;return this.parent(A)}});Element.implement({makeDraggable:function(A){var B=new Drag.Move(this,A);this.store("dragger",B);return B}});var Slider=new Class({Implements:[Events,Options],Binds:["cl
 ickedElement","draggedKnob","scrolledElement"],options:{onTick:function(A){if(this.options.snap){A=this.toPosition(this.step)}this.knob.setStyle(this.property,A)},initialStep:0,snap:false,offset:0,range:false,wheel:false,steps:100,mode:"horizontal"},initialize:function(F,A,E){this.setOptions(E);this.element=document.id(F);this.knob=document.id(A);this.previousChange=this.previousEnd=this.step=-1;var G,B={},D={x:false,y:false};switch(this.options.mode){case"vertical":this.axis="y";this.property="top";G="offsetHeight";break;case"horizontal":this.axis="x";this.property="left";G="offsetWidth"}this.full=this.element.measure(function(){this.half=this.knob[G]/2;return this.element[G]-this.knob[G]+(this.options.offset*2)}.bind(this));this.min=$chk(this.options.range[0])?this.options.range[0]:0;this.max=$chk(this.options.range[1])?this.options.range[1]:this.options.steps;this.range=this.max-this.min;this.steps=this.options.steps||this.full;this.stepSize=Math.abs(this.range)/this.step
 s;this.stepWidth=this.stepSize*this.full/Math.abs(this.range);this.knob.setStyle("position","relative").setStyle(this.property,this.options.initialStep?this.toPosition(this.options.initialStep):-this.options.offset);D[this.axis]=this.property;B[this.axis]=[-this.options.offset,this.full-this.options.offset];var C={snap:0,limit:B,modifiers:D,onDrag:this.draggedKnob,onStart:this.draggedKnob,onBeforeStart:(function(){this.isDragging=true}).bind(this),onCancel:function(){this.isDragging=false}.bind(this),onComplete:function(){this.isDragging=false;this.draggedKnob();this.end()}.bind(this)};if(this.options.snap){C.grid=Math.ceil(this.stepWidth);C.limit[this.axis][1]=this.full}this.drag=new Drag(this.knob,C);this.attach()},attach:function(){this.element.addEvent("mousedown",this.clickedElement);if(this.options.wheel){this.element.addEvent("mousewheel",this.scrolledElement)}this.drag.attach();return this},detach:function(){this.element.removeEvent("mousedown",this.clickedElement);t
 his.element.removeEvent("mousewheel",this.scrolledElement);this.drag.detach();return this},set:function(A){if(!((this.range>0)^(A<this.min))){A=this.min}if(!((this.range>0)^(A>this.max))){A=this.max}this.step=Math.round(A);this.checkStep();this.fireEvent("tick",this.toPosition(this.step));this.end();return this},clickedElement:function(C){if(this.isDragging||C.target==this.knob){return }var B=this.range<0?-1:1;var A=C.page[this.axis]-this.element.getPosition()[this.axis]-this.half;A=A.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+B*this.toStep(A));this.checkStep();this.fireEvent("tick",A);this.end()},scrolledElement:function(A){var B=(this.options.mode=="horizontal")?(A.wheel<0):(A.wheel>0);this.set(B?this.step-this.stepSize:this.step+this.stepSize);A.stop()},draggedKnob:function(){var B=this.range<0?-1:1;var A=this.drag.value.now[this.axis];A=A.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+B
 *this.toStep(A));this.checkStep()},checkStep:function(){if(this.previousChange!=this.step){this.previousChange=this.step;this.fireEvent("change",this.step)}},end:function(){if(this.previousEnd!==this.step){this.previousEnd=this.step;this.fireEvent("complete",this.step+"")}},toStep:function(A){var B=(A+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(B-=B%this.stepSize):B},toPosition:function(A){return(this.full*Math.abs(this.min-A))/(this.steps*this.stepSize)-this.options.offset}});var Sortables=new Class({Implements:[Events,Options],options:{snap:4,opacity:1,clone:false,revert:false,handle:false,constrain:false},initialize:function(A,B){this.setOptions(B);this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(A)||A));if(!this.options.clone){this.options.revert=false}if(this.options.revert){this.effect=new Fx.Morph(null,$merge({duration:250,link:"cancel"},this.options.revert))}},attach:function(){this.addLists(t
 his.lists);return this},detach:function(){this.lists=this.removeLists(this.lists);return this},addItems:function(){Array.flatten(arguments).each(function(A){this.elements.push(A);var B=A.retrieve("sortables:start",this.start.bindWithEvent(this,A));(this.options.handle?A.getElement(this.options.handle)||A:A).addEvent("mousedown",B)},this);return this},addLists:function(){Array.flatten(arguments).each(function(A){this.lists.push(A);this.addItems(A.getChildren())},this);return this},removeItems:function(){return $$(Array.flatten(arguments).map(function(A){this.elements.erase(A);var B=A.retrieve("sortables:start");(this.options.handle?A.getElement(this.options.handle)||A:A).removeEvent("mousedown",B);return A},this))},removeLists:function(){return $$(Array.flatten(arguments).map(function(A){this.lists.erase(A);this.removeItems(A.getChildren());return A},this))},getClone:function(B,A){if(!this.options.clone){return new Element("div").inject(document.body)}if($type(this.options.cl
 one)=="function"){return this.options.clone.call(this,B,A,this.list)}var C=A.clone(true).setStyles({margin:"0px",position:"absolute",visibility:"hidden",width:A.getStyle("width")});if(C.get("html").test("radio")){C.getElements("input[type=radio]").each(function(D,E){D.set("name","clone_"+E)})}return C.inject(this.list).setPosition(A.getPosition(A.getOffsetParent()))},getDroppables:function(){var A=this.list.getChildren();if(!this.options.constrain){A=this.lists.concat(A).erase(this.list)}return A.erase(this.clone).erase(this.element)},insert:function(C,B){var A="inside";if(this.lists.contains(B)){this.list=B;this.drag.droppables=this.getDroppables()}else{A=this.element.getAllPrevious().contains(B)?"before":"after"}this.element.inject(B,A);this.fireEvent("sort",[this.element,this.clone])},start:function(B,A){if(!this.idle){return }this.idle=false;this.element=A;this.opacity=A.get("opacity");this.list=A.getParent();this.clone=this.getClone(B,A);this.drag=new Drag.Move(this.clo
 ne,{snap:this.options.snap,container:this.options.constrain&&this.element.getParent(),droppables:this.getDroppables(),onSnap:function(){B.stop();this.clone.setStyle("visibility","visible");this.element.set("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone])}.bind(this),onEnter:this.insert.bind(this),onCancel:this.reset.bind(this),onComplete:this.end.bind(this)});this.clone.inject(this.element,"before");this.drag.start(B)},end:function(){this.drag.detach();this.element.set("opacity",this.opacity);if(this.effect){var A=this.element.getStyles("width","height");var B=this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));this.effect.element=this.clone;this.effect.start({top:B.top,left:B.left,width:A.width,height:A.height,opacity:0.25}).chain(this.reset.bind(this))}else{this.reset()}},reset:function(){this.idle=true;this.clone.destroy();this.fireEvent("complete",this.element)},serialize:function(){var C=Array.link(arguments,
 {modifier:Function.type,index:$defined});var B=this.lists.map(function(D){return D.getChildren().map(C.modifier||function(E){return E.get("id")},this)},this);var A=C.index;if(this.lists.length==1){A=0}return $chk(A)&&A>=0&&A<this.lists.length?B[A]:B}});Request.JSONP=new Class({Implements:[Chain,Events,Options,Log],options:{url:"",data:{},retries:0,timeout:0,link:"ignore",callbackKey:"callback",injectScript:document.head},initialize:function(A){this.setOptions(A);if(this.options.log){this.enableLog()}this.running=false;this.requests=0;this.triesRemaining=[]},check:function(){if(!this.running){return true}switch(this.options.link){case"cancel":this.cancel();return true;case"chain":this.chain(this.caller.bind(this,arguments));return false}return false},send:function(C){if(!$chk(arguments[1])&&!this.check(C)){return this}var E=$type(C),A=this.options,B=$chk(arguments[1])?arguments[1]:this.requests++;if(E=="string"||E=="element"){C={data:C}}C=$extend({data:A.data,url:A.url},C);if
 (!$chk(this.triesRemaining[B])){this.triesRemaining[B]=this.options.retries}var D=this.triesRemaining[B];(function(){var F=this.getScript(C);this.log("JSONP retrieving script with url: "+F.get("src"));this.fireEvent("request",F);this.running=true;(function(){if(D){this.triesRemaining[B]=D-1;if(F){F.destroy();this.send(C,B).fireEvent("retry",this.triesRemaining[B])}}else{if(F&&this.options.timeout){F.destroy();this.cancel().fireEvent("failure")}}}).delay(this.options.timeout,this)}).delay(Browser.Engine.trident?50:0,this);return this},cancel:function(){if(!this.running){return this}this.running=false;this.fireEvent("cancel");return this},getScript:function(C){var B=Request.JSONP.counter,D;Request.JSONP.counter++;switch($type(C.data)){case"element":D=document.id(C.data).toQueryString();break;case"object":case"hash":D=Hash.toQueryString(C.data)}var E=C.url+(C.url.test("\\?")?"&":"?")+(C.callbackKey||this.options.callbackKey)+"=Request.JSONP.request_map.request_"+B+(D?"&"+D:"");
 if(E.length>2083){this.log("JSONP "+E+" will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs")}var A=new Element("script",{type:"text/javascript",src:E});Request.JSONP.request_map["request_"+B]=function(){this.success(arguments,A)}.bind(this);return A.inject(this.options.injectScript)},success:function(B,A){if(A){A.destroy()}this.running=false;this.log("JSONP successfully retrieved: ",B);this.fireEvent("complete",B).fireEvent("success",B).callChain()}});Request.JSONP.counter=0;Request.JSONP.request_map={};Request.Queue=new Class({Implements:[Options,Events],Binds:["attach","request","complete","cancel","success","failure","exception"],options:{stopOnFailure:true,autoAdvance:true,concurrent:1,requests:{}},initialize:function(A){if(A){var B=A.requests;delete A.requests}this.setOptions(A);this.requests=new Hash;this.queue=[];this.reqBinders={};if(B){this.addRequests(B)}},addRequest:function(A,B){this.requests.set(A,B);this.attach(A,B);return this},ad
 dRequests:function(A){$each(A,function(C,B){this.addRequest(B,C)},this);return this},getName:function(A){return this.requests.keyOf(A)},attach:function(A,B){if(B._groupSend){return this}["request","complete","cancel","success","failure","exception"].each(function(C){if(!this.reqBinders[A]){this.reqBinders[A]={}}this.reqBinders[A][C]=function(){this["on"+C.capitalize()].apply(this,[A,B].extend(arguments))}.bind(this);B.addEvent(C,this.reqBinders[A][C])},this);B._groupSend=B.send;B.send=function(C){this.send(A,C);return B}.bind(this);return this},removeRequest:function(B){var A=$type(B)=="object"?this.getName(B):B;if(!A&&$type(A)!="string"){return this}B=this.requests.get(A);if(!B){return this}["request","complete","cancel","success","failure","exception"].each(function(C){B.removeEvent(C,this.reqBinders[A][C])},this);B.send=B._groupSend;delete B._groupSend;return this},getRunning:function(){return this.requests.filter(function(A){return A.running})},isRunning:function(){retur
 n !!(this.getRunning().getKeys().length)},send:function(B,A){var C=function(){this.requests.get(B)._groupSend(A);this.queue.erase(C)}.bind(this);C.name=B;if(this.getRunning().getKeys().length>=this.options.concurrent||(this.error&&this.options.stopOnFailure)){this.queue.push(C)}else{C()}return this},hasNext:function(A){return(!A)?!!this.queue.length:!!this.queue.filter(function(B){return B.name==A}).length},resume:function(){this.error=false;(this.options.concurrent-this.getRunning().getKeys().length).times(this.runNext,this);return this},runNext:function(A){if(!this.queue.length){return this}if(!A){this.queue[0]()}else{var B;this.queue.each(function(C){if(!B&&C.name==A){B=true;C()}})}return this},runAll:function(){this.queue.each(function(A){A()});return this},clear:function(A){if(!A){this.queue.empty()}else{this.queue=this.queue.map(function(B){if(B.name!=A){return B}else{return false}}).filter(function(B){return B})}return this},cancel:function(A){this.requests.get(A).can
 cel();return this},onRequest:function(){this.fireEvent("request",arguments)},onComplete:function(){this.fireEvent("complete",arguments);if(!this.queue.length){this.fireEvent("end")}},onCancel:function(){if(this.options.autoAdvance&&!this.error){this.runNext()}this.fireEvent("cancel",arguments)},onSuccess:function(){if(this.options.autoAdvance&&!this.error){this.runNext()}this.fireEvent("success",arguments)},onFailure:function(){this.error=true;if(!this.options.stopOnFailure&&this.options.autoAdvance){this.runNext()}this.fireEvent("failure",arguments)},onException:function(){this.error=true;if(!this.options.stopOnFailure&&this.options.autoAdvance){this.runNext()}this.fireEvent("exception",arguments)}});Request.implement({options:{initialDelay:5000,delay:5000,limit:60000},startTimer:function(B){var A=function(){if(!this.running){this.send({data:B})}};this.timer=A.delay(this.options.initialDelay,this);this.lastDelay=this.options.initialDelay;this.completeCheck=function(C){$clea
 r(this.timer);this.lastDelay=(C)?this.options.delay:(this.lastDelay+this.options.delay).min(this.options.limit);this.timer=A.delay(this.lastDelay,this)};return this.addEvent("complete",this.completeCheck)},stopTimer:function(){$clear(this.timer);return this.removeEvent("complete",this.completeCheck)}});var Asset={javascript:function(F,D){D=$extend({onload:$empty,document:document,check:$lambda(true)},D);if(D.onLoad){D.onload=D.onLoad}var B=new Element("script",{src:F,type:"text/javascript"});var E=D.onload.bind(B),A=D.check,G=D.document;delete D.onload;delete D.check;delete D.document;B.addEvents({load:E,readystatechange:function(){if(["loaded","complete"].contains(this.readyState)){E()}}}).set(D);if(Browser.Engine.webkit419){var C=(function(){if(!$try(A)){return }$clear(C);E()}).periodical(50)}return B.inject(G.head)},css:function(B,A){return new Element("link",$merge({rel:"stylesheet",media:"screen",type:"text/css",href:B},A)).inject(document.head)},image:function(C,B){B=$
 merge({onload:$empty,onabort:$empty,onerror:$empty},B);var D=new Image();var A=document.id(D)||new Element("img");["load","abort","error"].each(function(E){var G="on"+E;var F=E.capitalize();if(B["on"+F]){B[G]=B["on"+F]}var H=B[G];delete B[G];D[G]=function(){if(!D){return }if(!A.parentNode){A.width=D.width;A.height=D.height}D=D.onload=D.onabort=D.onerror=null;H.delay(1,A,A);A.fireEvent(E,A,1)}});D.src=A.src=C;if(D&&D.complete){D.onload.delay(1)}return A.set(B)},images:function(D,C){C=$merge({onComplete:$empty,onProgress:$empty,onError:$empty,properties:{}},C);D=$splat(D);var A=[];var B=0;return new Elements(D.map(function(E){return Asset.image(E,$extend(C.properties,{onload:function(){C.onProgress.call(this,B,D.indexOf(E));B++;if(B==D.length){C.onComplete()}},onerror:function(){C.onError.call(this,B,D.indexOf(E));B++;if(B==D.length){C.onComplete()}}}))}))}};var Color=new Native({initialize:function(B,C){if(arguments.length>=3){C="rgb";B=Array.slice(arguments,0,3)}else{if(type
 of B=="string"){if(B.match(/rgb/)){B=B.rgbToHex().hexToRgb(true)}else{if(B.match(/hsb/)){B=B.hsbToRgb()}else{B=B.hexToRgb(true)}}}}C=C||"rgb";switch(C){case"hsb":var A=B;B=B.hsbToRgb();B.hsb=A;break;case"hex":B=B.hexToRgb(true);break}B.rgb=B.slice(0,3);B.hsb=B.hsb||B.rgbToHsb();B.hex=B.rgbToHex();return $extend(B,this)}});Color.implement({mix:function(){var A=Array.slice(arguments);var C=($type(A.getLast())=="number")?A.pop():50;var B=this.slice();A.each(function(D){D=new Color(D);for(var E=0;E<3;E++){B[E]=Math.round((B[E]/100*(100-C))+(D[E]/100*C))}});return new Color(B,"rgb")},invert:function(){return new Color(this.map(function(A){return 255-A}))},setHue:function(A){return new Color([A,this.hsb[1],this.hsb[2]],"hsb")},setSaturation:function(A){return new Color([this.hsb[0],A,this.hsb[2]],"hsb")},setBrightness:function(A){return new Color([this.hsb[0],this.hsb[1],A],"hsb")}});var $RGB=function(C,B,A){return new Color([C,B,A],"rgb")};var $HSB=function(C,B,A){return new Colo
 r([C,B,A],"hsb")};var $HEX=function(A){return new Color(A,"hex")};Array.implement({rgbToHsb:function(){var B=this[0],C=this[1],J=this[2],G=0;var I=Math.max(B,C,J),E=Math.min(B,C,J);var K=I-E;var H=I/255,F=(I!=0)?K/I:0;if(F!=0){var D=(I-B)/K;var A=(I-C)/K;var L=(I-J)/K;if(B==I){G=L-A}else{if(C==I){G=2+D-L}else{G=4+A-D}}G/=6;if(G<0){G++}}return[Math.round(G*360),Math.round(F*100),Math.round(H*100)]},hsbToRgb:function(){var C=Math.round(this[2]/100*255);if(this[1]==0){return[C,C,C]}else{var A=this[0]%360;var E=A%60;var F=Math.round((this[2]*(100-this[1]))/10000*255);var D=Math.round((this[2]*(6000-this[1]*E))/600000*255);var B=Math.round((this[2]*(6000-this[1]*(60-E)))/600000*255);switch(Math.floor(A/60)){case 0:return[C,B,F];case 1:return[D,C,F];case 2:return[F,C,B];case 3:return[F,D,C];case 4:return[B,F,C];case 5:return[C,F,D]}}return false}});String.implement({rgbToHsb:function(){var A=this.match(/\d{1,3}/g);return(A)?A.rgbToHsb():null},hsbToRgb:function(){var A=this.match(/
 \d{1,3}/g);return(A)?A.hsbToRgb():null}});var Group=new Class({initialize:function(){this.instances=Array.flatten(arguments);this.events={};this.checker={}},addEvent:function(B,A){this.checker[B]=this.checker[B]||{};this.events[B]=this.events[B]||[];if(this.events[B].contains(A)){return false}else{this.events[B].push(A)}this.instances.each(function(C,D){C.addEvent(B,this.check.bind(this,[B,C,D]))},this);return this},check:function(C,A,B){this.checker[C][B]=true;var D=this.instances.every(function(F,E){return this.checker[C][E]||false},this);if(!D){return }this.checker[C]={};this.events[C].each(function(E){E.call(this,this.instances,A)},this)}});Hash.Cookie=new Class({Extends:Cookie,options:{autoSave:true},initialize:function(B,A){this.parent(B,A);this.load()},save:function(){var A=JSON.encode(this.hash);if(!A||A.length>4096){return false}if(A=="{}"){this.dispose()}else{this.write(A)}return true},load:function(){this.hash=new Hash(JSON.decode(this.read(),true));return this}})
 ;Hash.each(Hash.prototype,function(B,A){if(typeof B=="function"){Hash.Cookie.implement(A,function(){var C=B.apply(this.hash,arguments);if(this.options.autoSave){this.save()}return C})}});var HtmlTable=new Class({Implements:[Options,Events,Class.Occlude],options:{properties:{cellpadding:0,cellspacing:0,border:0},rows:[],headers:[],footers:[]},property:"HtmlTable",initialize:function(){var A=Array.link(arguments,{options:Object.type,table:Element.type});this.setOptions(A.options);this.element=A.table||new Element("table",this.options.properties);if(this.occlude()){return this.occluded}this.build()},build:function(){this.element.store("HtmlTable",this);this.body=document.id(this.element.tBodies[0])||new Element("tbody").inject(this.element);$$(this.body.rows);if(this.options.headers.length){this.setHeaders(this.options.headers)}else{this.thead=document.id(this.element.tHead)}if(this.thead){this.head=document.id(this.thead.rows[0])}if(this.options.footers.length){this.setFooters
 (this.options.footers)}this.tfoot=document.id(this.element.tFoot);if(this.tfoot){this.foot=document.id(this.thead.rows[0])}this.options.rows.each(function(A){this.push(A)},this);["adopt","inject","wraps","grab","replaces","dispose"].each(function(A){this[A]=this.element[A].bind(this.element)},this)},toElement:function(){return this.element},empty:function(){this.body.empty();return this},set:function(D,A){var C=(D=="headers")?"tHead":"tFoot";this[C.toLowerCase()]=(document.id(this.element[C])||new Element(C.toLowerCase()).inject(this.element,"top")).empty();var B=this.push(A,{},this[C.toLowerCase()],D=="headers"?"th":"td");if(D=="headers"){this.head=document.id(this.thead.rows[0])}else{this.foot=document.id(this.thead.rows[0])}return B},setHeaders:function(A){this.set("headers",A);return this},setFooters:function(A){this.set("footers",A);return this},push:function(E,B,D,A){var C=E.map(function(H){var I=new Element(A||"td",H.properties),G=H.content||H||"",F=document.id(G);if(
 $type(G)!="string"&&F){I.adopt(F)}else{I.set("html",G)}return I});return{tr:new Element("tr",B).inject(D||this.body).adopt(C),tds:C}}});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(B,A){return B[((A%2)?"remove":"add")+"Class"](this.options.classZebra)},push:function(){var A=this.previous.apply(this,arguments);if(this.options.zebra){this.updateZebras()}return A}});HtmlTable=Class.refactor(HtmlTable,{options:{sortIndex:0,sortReverse:false,parsers:[],defaultParser:"string",classSortable:"table-sortable",classHeadSort:"table-th-sort",classHeadSortRev:"table-th-sort-rev",classNoSort:"table-th-nosort",classGroupHead:"table-tr-group-head",classGroup:"table-tr-group",classCellSort:"table-td-sort",classSortSpan:"table-th-sort
 -span",sortable:false},initialize:function(){this.previous.apply(this,arguments);if(this.occluded){return this.occluded}this.sorted={index:null,dir:1};this.bound={headClick:this.headClick.bind(this)};this.sortSpans=new Elements();if(this.options.sortable){this.enableSort();if(this.options.sortIndex!=null){this.sort(this.options.sortIndex,this.options.sortReverse)}}},attachSorts:function(A){this.element.removeEvents("click:relay(th)");this.element[$pick(A,true)?"addEvent":"removeEvent"]("click:relay(th)",this.bound.headClick)},setHeaders:function(){this.previous.apply(this,arguments);if(this.sortEnabled){this.detectParsers()}},detectParsers:function(C){if(!this.head){return }var A=this.options.parsers,B=this.body.rows;this.parsers=$$(this.head.cells).map(function(D,E){if(!C&&(D.hasClass(this.options.classNoSort)||D.retrieve("htmltable-parser"))){return D.retrieve("htmltable-parser")}var F=new Element("div");$each(D.childNodes,function(J){F.adopt(J)});F.inject(D);var H=new Ele
 ment("span",{html:"&#160;","class":this.options.classSortSpan}).inject(F,"top");this.sortSpans.push(H);var I=A[E],G;switch($type(I)){case"function":I={convert:I};G=true;break;case"string":I=I;G=true;break}if(!G){HtmlTable.Parsers.some(function(M){var K=M.match;if(!K){return false}for(var L=0,J=B.length;L<J;L++){var N=$(B[L].cells[E]).get("html").clean();if(N&&K.test(N)){I=M;return true}}})}if(!I){I=this.options.defaultParser}D.store("htmltable-parser",I);return I},this)},headClick:function(C,B){console.log(B);if(!this.head||B.hasClass(this.options.classNoSort)){return }var A=Array.indexOf(this.head.cells,B);this.sort(A);return false},sort:function(F,H,K){if(!this.head){return }K=!!(K);var J=this.options.classCellSort;var M=this.options.classGroup,R=this.options.classGroupHead;if(!K){if(F!=null){if(this.sorted.index==F){this.sorted.reverse=!(this.sorted.reverse)}else{if(this.sorted.index!=null){this.sorted.reverse=false;this.head.cells[this.sorted.index].removeClass(this.opti
 ons.classHeadSort).removeClass(this.options.classHeadSortRev)}else{this.sorted.reverse=true}this.sorted.index=F}}else{F=this.sorted.index}if(H!=null){this.sorted.reverse=H}var D=document.id(this.head.cells[F]);if(D){D.addClass(this.options.classHeadSort);if(this.sorted.reverse){D.addClass(this.options.classHeadSortRev)}else{D.removeClass(this.options.classHeadSortRev)}}this.body.getElements("td").removeClass(this.options.classCellSort)}var C=this.parsers[F];if($type(C)=="string"){C=HtmlTable.Parsers.get(C)}if(!C){return }if(!Browser.Engine.trident){var B=this.body.getParent();this.body.dispose()}var Q=Array.map(this.body.rows,function(U,S){var T=C.convert.call(document.id(U.cells[F]));return{position:S,value:T,toString:function(){return T.toString()}}},this);Q.reverse(true);Q.sort(function(T,S){if(T.value===S.value){return 0}return T.value>S.value?1:-1});if(!this.sorted.reverse){Q.reverse(true)}var N=Q.length,I=this.body;var L,P,A,G;while(N){var O=Q[--N];P=O.position;var E=I
 .rows[P];if(E.disabled){continue}if(!K){if(G===O.value){E.removeClass(R).addClass(M)}else{G=O.value;E.removeClass(M).addClass(R)}if(this.zebra){this.zebra(E,N)}E.cells[F].addClass(J)}I.appendChild(E);for(L=0;L<N;L++){if(Q[L].position>P){Q[L].position--}}}Q=null;if(B){B.grab(I)}return this.fireEvent("sort",[I,F])},reSort:function(){if(this.sortEnabled){this.sort.call(this,this.sorted.index,this.sorted.reverse)}return this},enableSort:function(){this.element.addClass(this.options.classSortable);this.attachSorts(true);this.detectParsers();this.sortEnabled=true;return this},disableSort:function(){this.element.removeClass(this.options.classSortable);this.attachSorts(false);this.sortSpans.each(function(A){A.destroy()});this.sortSpans.empty();this.sortEnabled=false;return this}});HtmlTable.Parsers=new Hash({date:{match:/^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,convert:function(){return Date.parse(this.get("text")).format("db")},type:"date"},"input-checked":{match:/ type="(radio|checkbox)" 
 /,convert:function(){return this.getElement("input").checked}},"input-value":{match:/<input/,convert:function(){return this.getElement("input").value}},number:{match:/^\d+[^\d.,]*$/,convert:function(){return this.get("text").toInt()},number:true},numberLax:{match:/^[^\d]+\d+$/,convert:function(){return this.get("text").replace(/[^-?^0-9]/,"").toInt()},number:true},"float":{match:/^[\d]+\.[\d]+/,convert:function(){return this.get("text").replace(/[^-?^\d.]/,"").toFloat()},number:true},floatLax:{match:/^[^\d]+[\d]+\.[\d]+$/,convert:function(){return this.get("text").replace(/[^-?^\d.]/,"")},number:true},string:{match:null,convert:function(){return this.get("text")}},title:{match:null,convert:function(){return this.title}}});(function(){var A=this.Keyboard=new Class({Extends:Events,Implements:[Options,Log],options:{defaultEventType:"keydown",active:false,events:{},nonParsedEvents:["activate","deactivate","onactivate","ondeactivate","changed","onchanged"]},initialize:function(F)
 {this.setOptions(F);this.setup()},setup:function(){this.addEvents(this.options.events);if(A.manager&&!this.manager){A.manager.manage(this)}if(this.options.active){this.activate()}},handle:function(H,G){if(H.preventKeyboardPropagation){return }var F=!!this.manager;if(F&&this.activeKB){this.activeKB.handle(H,G);if(H.preventKeyboardPropagation){return }}this.fireEvent(G,H);if(!F&&this.activeKB){this.activeKB.handle(H,G)}},addEvent:function(H,G,F){return this.parent(A.parse(H,this.options.defaultEventType,this.options.nonParsedEvents),G,F)},removeEvent:function(G,F){return this.parent(A.parse(G,this.options.defaultEventType,this.options.nonParsedEvents),F)},toggleActive:function(){return this[this.active?"deactivate":"activate"]()},activate:function(F){if(F){if(F!=this.activeKB){this.previous=this.activeKB}this.activeKB=F.fireEvent("activate");A.manager.fireEvent("changed")}else{if(this.manager){this.manager.activate(this)}}return this},deactivate:function(F){if(F){if(F===this.a
 ctiveKB){this.activeKB=null;F.fireEvent("deactivate");A.manager.fireEvent("changed")}}else{if(this.manager){this.manager.deactivate(this)}}return this},relenquish:function(){if(this.previous){this.activate(this.previous)}},manage:function(F){if(F.manager){F.manager.drop(F)}this.instances.push(F);F.manager=this;if(!this.activeKB){this.activate(F)}else{this._disable(F)}},_disable:function(F){if(this.activeKB==F){this.activeKB=null}},drop:function(F){this._disable(F);this.instances.erase(F)},instances:[],trace:function(){A.trace(this)},each:function(F){A.each(this,F)}});var B={};var C=["shift","control","alt","meta"];var E=/^(?:shift|control|ctrl|alt|meta)$/;A.parse=function(H,G,K){if(K&&K.contains(H.toLowerCase())){return H}H=H.toLowerCase().replace(/^(keyup|keydown):/,function(M,L){G=L;return""});if(!B[H]){var F,J={};H.split("+").each(function(L){if(E.test(L)){J[L]=true}else{F=L}});J.control=J.control||J.ctrl;var I=[];C.each(function(L){if(J[L]){I.push(L)}});if(F){I.push(F)}B
 [H]=I.join("+")}return G+":"+B[H]};A.each=function(F,G){var H=F||A.manager;while(H){G.run(H);H=H.activeKB}};A.stop=function(F){F.preventKeyboardPropagation=true};A.manager=new A({active:true});A.trace=function(F){F=F||A.manager;F.enableLog();F.log("the following items have focus: ");A.each(F,function(G){F.log(document.id(G.widget)||G.wiget||G)})};var D=function(G){var F=[];C.each(function(H){if(G[H]){F.push(H)}});if(!E.test(G.key)){F.push(G.key)}A.manager.handle(G,G.type+":"+F.join("+"))};document.addEvents({keyup:D,keydown:D});Event.Keys.extend({shift:16,control:17,alt:18,capslock:20,pageup:33,pagedown:34,end:35,home:36,numlock:144,scrolllock:145,";":186,"=":187,",":188,"-":Browser.Engine.Gecko?109:189,".":190,"/":191,"`":192,"[":219,"\\":220,"]":221,"'":222})})();HtmlTable=Class.refactor(HtmlTable,{options:{useKeyboard:true,classRowSelected:"table-tr-selected",classRowHovered:"table-tr-hovered",classSelectable:"table-selectable",allowMultiSelect:true,selectable:false},init
 ialize: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(A){A=$pick(A,true);var B=A?"addEvents":"removeEvents";this.element[B]({mouseleave:this.bound.mouseleave});this.body[B]({"click:relay(tr)":this.bound.focusRow});if(this.options.useKeyboard||this.keyboard){if(!this.keyboard){this.keyboard=new Keyboard({events:{down:function(C){C.preventDefault();this.shiftFocus(1)}.bind(this),up:function(C){C.preventDefault();this.shiftFocus(-1)}.bind(this),enter:function(C){C.preventDefault();if(this.hover){this.focusRow(
 this.hover)}}.bind(this)},active:true})}this.keyboard[A?"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 A=this.previous.apply(this,arguments);this.updateSelects();return A},updateSelects:function(){Array.each(this.body.rows,function(A){var B=A.retrieve("binders");if((B&&this.selectEnabled)||(!B&&!this.selectEnabled)){return }if(!B){B={mouseenter:this.enterRow.bind(this,[A]),mouseleave:this.leaveRow.bind(this,[A])};A.store("binders",B).addEvents(B)}else{A.removeEvents(B)}},this)},enterRow:function(A){if(this.hover){this.hover=this.leaveRow(this.hover)}this.hover=A.addClass(this.options.classRowHovered)},shiftFocus:function(A){if(!this.hover){return this.enterRow(this.body.rows[0])}var B=Array.indexOf(this.body.rows,this.hover)+A;if(B<0){B=0}if(B>=this.body.rows.length)
 {B=this.body.rows.length-1}if(this.hover==this.body.rows[B]){return this}this.enterRow(this.body.rows[B])},leaveRow:function(A){A.removeClass(this.options.classRowHovered)},focusRow:function(){var B=arguments[1]||arguments[0];if(!this.body.getChildren().contains(B)){return }var A=function(C){this.selectedRows.erase(C);C.removeClass(this.options.classRowSelected);this.fireEvent("rowUnfocus",[C,this.selectedRows])}.bind(this);if(!this.options.allowMultiSelect){this.selectedRows.each(A)}if(!this.selectedRows.contains(B)){this.selectedRows.push(B);B.addClass(this.options.classRowSelected);this.fireEvent("rowFocus",[B,this.selectedRows])}else{A(B)}return false},selectAll:function(A){A=$pick(A,true);if(!this.options.allowMultiSelect&&A){return }if(!A){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)}});Keyboard.p
 rototype.options.nonParsedEvents.combine(["rebound","onrebound"]);Keyboard.implement({addShortcut:function(B,A){this.shortcuts=this.shortcuts||[];this.shortcutIndex=this.shortcutIndex||{};A.getKeyboard=$lambda(this);A.name=B;this.shortcutIndex[B]=A;this.shortcuts.push(A);if(A.keys){this.addEvent(A.keys,A.handler)}return this},addShortcuts:function(B){for(var A in B){this.addShortcut(A,B[A])}return this},getShortcuts:function(){return this.shortcuts||[]},getShortcut:function(A){return(this.shortcutIndex||{})[A]}});Keyboard.rebind=function(B,A){$splat(A).each(function(C){C.getKeyboard().removeEvent(C.keys,C.handler);C.getKeyboard().addEvent(B,C.handler);C.keys=B;C.getKeyboard().fireEvent("rebound")})};Keyboard.getActiveShortcuts=function(B){var A=[],C=[];Keyboard.each(B,[].push.bind(A));A.each(function(D){C.extend(D.getShortcuts())});return C};Keyboard.getShortcut=function(C,B,D){D=D||{};var A=D.many?[]:null,E=D.many?function(G){var F=G.getShortcut(C);if(F){A.push(F)}}:functio
 n(F){if(!A){A=F.getShortcut(C)}};Keyboard.each(B,E);return A};Keyboard.getShortcuts=function(B,A){return Keyboard.getShortcut(B,A,{many:true})};var Scroller=new Class({Implements:[Events,Options],options:{area:20,velocity:1,onChange:function(A,B){this.element.scrollTo(A,B)},fps:50},initialize:function(B,A){this.setOptions(A);this.element=document.id(B);this.docBody=document.id(this.element.getDocument().body);this.listener=($type(this.element)!="element")?this.docBody:this.element;this.timer=null;this.bound={attach:this.attach.bind(this),detach:this.detach.bind(this),getCoords:this.getCoords.bind(this)}},start:function(){this.listener.addEvents({mouseover:this.bound.attach,mouseout:this.bound.detach})},stop:function(){this.listener.removeEvents({mouseover:this.bound.attach,mouseout:this.bound.detach});this.detach();this.timer=$clear(this.timer)},attach:function(){this.listener.addEvent("mousemove",this.bound.getCoords)},detach:function(){this.listener.removeEvent("mousemove"
 ,this.bound.getCoords);this.timer=$clear(this.timer)},getCoords:function(A){this.page=(this.listener.get("tag")=="body")?A.client:A.page;if(!this.timer){this.timer=this.scroll.periodical(Math.round(1000/this.options.fps),this)}},scroll:function(){var B=this.element.getSize(),A=this.element.getScroll(),F=this.element!=this.docBody?this.element.getOffsets():{x:0,y:0},C=this.element.getScrollSize(),E={x:0,y:0};for(var D in this.page){if(this.page[D]<(this.options.area+F[D])&&A[D]!=0){E[D]=(this.page[D]-this.options.area-F[D])*this.options.velocity}else{if(this.page[D]+this.options.area>(B[D]+F[D])&&A[D]+B[D]!=C[D]){E[D]=(this.page[D]-B[D]+this.options.area-F[D])*this.options.velocity}}}if(E.y||E.x){this.fireEvent("change",[A.x+E.x,A.y+E.y])}}});(function(){var A=function(C,B){return(C)?($type(C)=="function"?C(B):B.get(C)):""};this.Tips=new Class({Implements:[Events,Options],options:{onShow:function(){this.tip.setStyle("display","block")},onHide:function(){this.tip.setStyle("dis
 play","none")},title:"title",text:function(B){return B.get("rel")||B.get("href")},showDelay:100,hideDelay:100,className:"tip-wrap",offset:{x:16,y:16},windowPadding:{x:0,y:0},fixed:false},initialize:function(){var B=Array.link(arguments,{options:Object.type,elements:$defined});this.setOptions(B.options);if(B.elements){this.attach(B.elements)}this.container=new Element("div",{"class":"tip"})},toElement:function(){if(this.tip){return this.tip}return this.tip=new Element("div",{"class":this.options.className,styles:{position:"absolute",top:0,left:0}}).adopt(new Element("div",{"class":"tip-top"}),this.container,new Element("div",{"class":"tip-bottom"})).inject(document.body)},attach:function(B){$$(B).each(function(D){var F=A(this.options.title,D),E=A(this.options.text,D);D.erase("title").store("tip:native",F).retrieve("tip:title",F);D.retrieve("tip:text",E);this.fireEvent("attach",[D]);var C=["enter","leave"];if(!this.options.fixed){C.push("move")}C.each(function(H){var G=D.retri
 eve("tip:"+H);if(!G){G=this["element"+H.capitalize()].bindWithEvent(this,D)}D.store("tip:"+H,G).addEvent("mouse"+H,G)},this)},this);return this},detach:function(B){$$(B).each(function(D){["enter","leave","move"].each(function(E){D.removeEvent("mouse"+E,D.retrieve("tip:"+E)).eliminate("tip:"+E)});this.fireEvent("detach",[D]);if(this.options.title=="title"){var C=D.retrieve("tip:native");if(C){D.set("title",C)}}},this);return this},elementEnter:function(C,B){this.container.empty();["title","text"].each(function(E){var D=B.retrieve("tip:"+E);if(D){this.fill(new Element("div",{"class":"tip-"+E}).inject(this.container),D)}},this);$clear(this.timer);this.timer=(function(){this.show(this,B);this.position((this.options.fixed)?{page:B.getPosition()}:C)}).delay(this.options.showDelay,this)},elementLeave:function(C,B){$clear(this.timer);this.timer=this.hide.delay(this.options.hideDelay,this,B);this.fireForParent(C,B)},fireForParent:function(C,B){B=B.getParent();if(!B||B==document.body)
 {return }if(B.retrieve("tip:enter")){B.fireEvent("mouseenter",C)}else{this.fireForParent(C,B)}},elementMove:function(C,B){this.position(C)},position:function(E){if(!this.tip){document.id(this)}var C=window.getSize(),B=window.getScroll(),F={x:this.tip.offsetWidth,y:this.tip.offsetHeight},D={x:"left",y:"top"},G={};for(var H in D){G[D[H]]=E.page[H]+this.options.offset[H];if((G[D[H]]+F[H]-B[H])>C[H]-this.options.windowPadding[H]){G[D[H]]=E.page[H]-this.options.offset[H]-F[H]}}this.tip.setStyles(G)},fill:function(B,C){if(typeof C=="string"){B.set("html",C)}else{B.adopt(C)}},show:function(B){if(!this.tip){document.id(this)}this.fireEvent("show",[this.tip,B])},hide:function(B){if(!this.tip){document.id(this)}this.fireEvent("hide",[this.tip,B])}})})();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"],dateOr
 der:["date","month","year"],shortDate:"%d/%m/%Y",shortTime:"%H:%M",AM:"AM",PM:"PM",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"});MooTools.lang.set("cs-CZ","Date",{months:["Leden","Únor","BÅ™ezen","Duben","KvÄ›ten","ÄŒerven","ÄŒervenec","Srpen","ZářÃ","ŘÃjen","Listopad","Prosinec"],days:["NedÄ›le","PondÄ›lÃ","Úterý","StÅ™eda","ÄŒtvrtek","Pátek","Sobota"],dateOrder:["date","month","year"],shortDate:"%d/%m/%Y",shortTime:"%H:%M",AM:"dop.",PM:"odp.",ordinal:function(A){return"."},lessThanMinuteAgo:"ménÄ› než minutou",minuteAgo:"pÅ™ibližnÄ› pÅ™ed minutou",minute
 sAgo:"pÅ™ed {delta} minutami",hourAgo:"pÅ™ibližnÄ› pÅ™ed hodinou",hoursAgo:"pÅ™ed {delta} hodinami",dayAgo:"pÅ™ed dnem",daysAgo:"pÅ™ed {delta} dni",lessThanMinuteUntil:"pÅ™ed ménÄ› než minutou",minuteUntil:"asi pÅ™ed minutou",minutesUntil:" asi pÅ™ed {delta} minutami",hourUntil:"asi pÅ™ed hodinou",hoursUntil:"pÅ™ed {delta} hodinami",dayUntil:"pÅ™ed dnem",daysUntil:"pÅ™ed {delta} dni",weekUntil:"pÅ™ed týdnem",weeksUntil:"pÅ™ed {delta} týdny",monthUntil:"pÅ™ed mÄ›sÃcem",monthsUntil:"pÅ™ed {delta} mÄ›sÃci",yearUntil:"pÅ™ed rokem",yearsUntil:"pÅ™ed {delta} lety"});MooTools.lang.set("da-DK","Date",{months:["Januar","Februa","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],days:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dateOrder:["date","month","year"],AM:"AM",PM:"PM",shortDate:"%d-%m-%Y",shortTime:"%H:%M",ordinal:function(A){return(A>3&&A<21)?"th":["th","st","nd","rd","th"][Math.min(A%10,4)]},lessThanMinuteA
 go:"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"});MooTools.lang.set("nl-NL","Date",{months:["Januari","Februari","Maart","April","Mei","Juni","Juli","Augustus","September","Oktober","November","December"],da
 ys:["Zondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrijdag","Zaterdag"],dateOrder:["date","month","year"],AM:"AM",PM:"PM",shortDate:"%d/%m/%Y",shortTime:"%H:%M",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"});MooTools.lang.set("en-GB","Date",{dateOrder:["date","month","year"],shortDate:"%d/%m/%Y",shortTime:"%H:%M"}).set("cascade",["en-US"]);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"],dateOrder:["month","date","year"],AM:"AM",PM:"PM",shortDate:"%m.%d.%Y",shortTime:"%H:%M",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"});MooTools.lang.set("de-DE","Date",{months:["Januar","Februar","M&auml;rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],dateOrder:["date","month","year","."],AM:"vormittags",PM:"nachmittags",shortDate:"%d.%m.%Y",shortTime:"%H:%M
 ",ordinal:".",lessThanMinuteAgo:"Vor weniger als einer Minute",minuteAgo:"Vor einer Minute",minutesAgo:"Vor {delta} Minuten",hourAgo:"Vor einer Stunde",hoursAgo:"Vor {delta} Stunden",dayAgo:"Vor einem Tag",daysAgo:"Vor {delta} Tagen",weekAgo:"Vor einer Woche",weeksAgo:"Vor {delta} Wochen",monthAgo:"Vor einem Monat",monthsAgo:"Vor {delta} Monaten",yearAgo:"Vor einem Jahr",yearsAgo:"Vor {delta} Jahren",lessThanMinuteUntil:"In weniger als einer Minute",minuteUntil:"In einer Minute",minutesUntil:"In {delta} Minuten",hourUntil:"In ca. einer Stunde",hoursUntil:"In ca. {delta} Stunden",dayUntil:"In einem Tag",daysUntil:"In {delta} Tagen",weekUntil:"In einer Woche",weeksUntil:"In {delta} Wochen",monthUntil:"In einem Monat",monthsUntil:"In {delta} Monaten",yearUntil:"In einem Jahr",yearsUntil:"In {delta} Jahren"});MooTools.lang.set("de-CH","cascade",["de-DE"]);MooTools.lang.set("fr-FR","Date",{months:["janvier","f&eacute;vrier","mars","avril","mai","juin","juillet","ao&ucirc;t","sept
 embre","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",getOrdinal:function(A){return(A>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 {d
 elta} mois",yearUntil:"dans 1 an",yearsUntil:"dans {delta} ans"});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"],dateOrder:["date","month","year"],AM:"AM",PM:"PM",shortDate:"%d/%m/%Y",shortTime:"%H.%M",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"});MooTools.lang.set("no-NO","Date",{dateOrder:["date","month","ye
 ar"],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"});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",ordinal:function(A){return(A>3&&A<21)?"ty":["ty","szy","gi","ci","ty"][Math.min(A%10,4)]},lessThanMinuteAgo:"mniej niż minute temu",minuteAgo:"około minutę temu",minutesAgo:"{delta} minut temu",hourAgo:"około godzinę temu",hoursAgo:"około {delta} godzin temu",dayAgo:"Wczoraj",daysAgo:"{delta} dni temu",lessThanMinuteUntil:"za ni
 ecałą 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"});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"],dateOrder:["date","month","year"],shortDate:"%d/%m/%Y",shortTime:"%H:%M",ordinal:function(A){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"});MooTools.lang.set("ru-RU-unicode","Date",{months:["Январь","Февраль","Март","Ð?прель","Май","Июнь","Июль","Ð?вгуÑ?Ñ‚","СентÑ?брь","ОктÑ?брь","Ð?оÑ?брь","Декабрь"],days:["ВоÑ?креÑ?енье","Понедельник","Вторник","Среда","Четверг","ПÑ?тница","Суббота"],dateOrder:["date","month","year"],AM:"AM",PM:"PM",shortDate:"%d/%m/%Y",shortTime:"%H:%M",pluralize:function(G,D,C,F,A){var B=G%10;var E=G%100;if(B==1&&E!=11){return D}else{if((B==2||B==3||B==4)&&!(E==12||E==13||E==14)){return C}else{if(B==0||(B==5||B==6||B==7||B==8||B==9)||(E==11||E==12
 ||E==13||E==14)){return F}else{return A}}}},ordinal:"",lessThanMinuteAgo:"меньше минуты назад",minuteAgo:"минута назад",minutesAgo:function(A){return"{delta} "+this.pluralize(A,"минута","минуты","минут")+" назад"},hourAgo:"чаÑ? назад",hoursAgo:function(A){return"{delta} "+this.pluralize(A,"чаÑ?","чаÑ?а","чаÑ?ов")+" назад"},dayAgo:"вчера",daysAgo:function(A){return"{delta} "+this.pluralize(A,"день","днÑ?","дней")+" назад"},lessThanMinuteUntil:"меньше минуты назад",minuteUntil:"через минуту",minutesUntil:function(A){return"через {delta} "+this.pluralize(A,"чаÑ?","чаÑ?а","чаÑ?ов")+""},hourUntil:"через чаÑ?",hoursUntil:function(A){return"через {delta} "+this.pluralize(A,"чаÑ?","чаÑ?а","чаÑ?ов")+""},dayUntil:"завтра",daysUntil:function(A){return"через {delta} "+this.pluralize(A,"день","днÑ?","дней")+""}});Mo
 oTools.lang.set("es-ES","Date",{months:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],days:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],dateOrder:["date","month","year"],AM:"AM",PM:"PM",shortDate:"%d/%m/%Y",shortTime:"%H:%M",ordinal:"",lessThanMinuteAgo:"hace menos de un minuto",minuteAgo:"hace un minuto",minutesAgo:"hace {delta} minutos",hourAgo:"hace una hora",hoursAgo:"hace unas {delta} horas",dayAgo:"hace un dÃa",daysAgo:"hace {delta} dÃas",weekAgo:"hace una semana",weeksAgo:"hace unas {delta} semanas",monthAgo:"hace un mes",monthsAgo:"hace {delta} meses",yearAgo:"hace un año",yearsAgo:"hace {delta} años",lessThanMinuteUntil:"menos de un minuto desde ahora",minuteUntil:"un minuto desde ahora",minutesUntil:"{delta} minutos desde ahora",hourUntil:"una hora desde ahora",hoursUntil:"unas {delta} horas desde ahora",dayUntil:"un dÃa desde ahora",daysUntil:"{delta} dÃas desde ahora",
 weekUntil:"una semana desde ahora",weeksUntil:"unas {delta} semanas desde ahora",monthUntil:"un mes desde ahora",monthsUntil:"{delta} meses desde ahora",yearUntil:"un año desde ahora",yearsUntil:"{delta} años desde ahora"});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"],dateOrder:["year","month","date"],AM:"",PM:"",shortDate:"%Y-%m-%d",shortTime:"%H:%M",ordinal:function(A){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"});(function(){var A=function(I,D,C,H,B){var G=(I/10).toInt();var F=I%10;var E=(I/100).toInt();if(G==1&&I>10){return H}if(F==1){return D}if(F>0&&F<5){return C}return H};MooTools.lang.set("uk-UA","Date",{months:["Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","ВереÑ?ень","Жовтень","ЛиÑ?топад","Грудень"],days:["Ð?еділÑ?","Понеділок","Вівторок","Середа","Четвер","П'Ñ?тницÑ?","Субота"],dateOrder:["date","month","year"],AM:"до полуднÑ?",PM:"по полудню",shortDate:"%d/%m/%Y",shortTime:"%H:%M",ordinal:"",lessThanMinuteAgo:"меньше хвилини тому",minuteAgo:"хвилину тому",minutesAgo:function(B){return"{delta} "+A(B,"хвилину","хвилини","хвилин")+" тому"},hourAgo:"годину тому",hoursA
 go:function(B){return"{delta} "+A(B,"годину","години","годин")+" тому"},dayAgo:"вчора",daysAgo:function(B){return"{delta} "+A(B,"день","днÑ?","днів")+" тому"},weekAgo:"тиждень тому",weeksAgo:function(B){return"{delta} "+A(B,"тиждень","тижні","тижнів")+" тому"},monthAgo:"міÑ?Ñ?ць тому",monthsAgo:function(B){return"{delta} "+A(B,"міÑ?Ñ?ць","міÑ?Ñ?ці","міÑ?Ñ?ців")+" тому"},yearAgo:"рік тому",yearsAgo:function(B){return"{delta} "+A(B,"рік","роки","років")+" тому"},lessThanMinuteUntil:"за мить",minuteUntil:"через хвилину",minutesUntil:function(B){return"через {delta} "+A(B,"хвилину","хвилини","хвилин")},hourUntil:"через годину",hoursUntil:function(B){return"через {delta} "+A(B,"годину","години","годин")},dayUntil:"завтра",daysUntil:function(B){return"через {delta} "+A(B,"де
 нь","днÑ?","днів")},weekUntil:"через тиждень",weeksUntil:function(B){return"через {delta} "+A(B,"тиждень","тижні","тижнів")},monthUntil:"через міÑ?Ñ?ць",monthesUntil:function(B){return"через {delta} "+A(B,"міÑ?Ñ?ць","міÑ?Ñ?ці","міÑ?Ñ?ців")},yearUntil:"через рік",yearsUntil:function(B){return"через {delta} "+A(B,"рік","роки","років")}})})();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","/"]});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 caracter
 s.",dateSuchAs:"Per favor introdueix una data valida com {date}",dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',url:"Per favor introdueix una URL valida com http://www.google.com.",currencyDollar:"Per favor introdueix una quantitat valida de €. Per exemple €100,00 .",oneRequired:"Per favor introdueix alguna cosa per al menys una d´aquestes entrades.",errorPrefix:"Error: ",warningPrefix:"Avis: ",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."});MooTools.lang.set("cs-CZ","Form.Validator",{required:"Tato položka je povinná.",minLength:"Zadejte prosÃm alespoň {minLength} znaků (napsáno {length} znaků).",maxLength:"Zadejte prosÃm ménÄ› než {maxLength} znaků (nápsáno {length} znaků).",integer:"Zadejte prosÃm celé Ä?Ãslo. Desetinná Ä?Ãsla (napÅ™. 1.25) nejsou povolena.",numeric:'Zadejte jen Ä?Ãselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',digits:"Zadejte prosÃm pouze Ä?Ãsla a interpunkÄ?nà znaménka(napÅ™Ãklad telefonnà Ä?Ãslo s pomlÄ?kami nebo teÄ?kami je povoleno).",alpha:"Zadejte prosÃm pouze pÃsmena (a-z). Mezery nebo jiné znaky nejsou povoleny.",alphanum:"Zadejte prosÃm pouze pÃsmena (a-z) nebo Ä?Ãslice (0-9). Mezery nebo jiné znaky nejsou povoleny.",dateSuchAs:"Zadejte prosÃm platné datum jako {date}",dateInFormatMDY:'Zadejte prosÃm
  platné datum jako MM / DD / RRRR (tj. "12/31/1999")',email:'Zadejte prosÃm platnou e-mailovou adresu. NapÅ™Ãklad "fred at domain.com".',url:"Zadejte prosÃm platnou URL adresu jako http://www.google.com.",currencyDollar:"Zadejte prosÃm platnou Ä?ástku. NapÅ™Ãklad $100.00.",oneRequired:"Zadejte prosÃm alespoň jednu hodnotu pro tyto položky.",errorPrefix:"Chyba: ",warningPrefix:"UpozornÄ›nÃ: ",noSpace:"V této položce nejsou povoleny mezery",reqChkByNode:"Nejsou vybrány žádné položky.",requiredChk:"Tato položka je vyžadována.",reqChkByName:"ProsÃm vyberte {label}.",match:"Tato položka se musà shodovat s položkou {matchName}",startDate:"datum zahájenÃ",endDate:"datum ukonÄ?enÃ",currendDate:"aktuálnà datum",afterDate:"Datum by mÄ›lo být stejné nebo vÄ›tÅ¡Ã než {label}.",beforeDate:"Datum by mÄ›lo být stejné nebo menÅ¡Ã než {label}.",startMonth:"Vyberte poÄ?áteÄ?nà mÄ›sÃc.",sameMonth:"Tyto dva datumy musà být ve stejném mÄ›sÃci - změňte jeden z nich.",cre
 ditcard:"Zadané Ä?Ãslo kreditnà karty je neplatné. ProsÃm opravte ho. Bylo zadáno {length} Ä?Ãsel."});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:"è¦å‘Šï¼š"});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(A){return Form.Validator.getValidator("IsEmpty").test(A)||(/^ï¿¥?\-?([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(A.get("value"))}});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 ing
 evoerd).",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: "});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 kehti
 v 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: ",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."});MooTools.lang.set("de-DE","Form.Validator",{required:"Dieses Eingabefeld muss ausgef&uuml;llt werden.",minLength:"Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingeg
 eben).",maxLength:"Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).",integer:"Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.",numeric:"Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.",digits:"Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).",alpha:"Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.",alphanum:"Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.",dateSuchAs:"Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).",dateInFormatMDY:"Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.199
 9&quot;).",email:"Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max at mustermann.de&quot;).",url:"Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).",currencyDollar:"Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).",oneRequired:"Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.",errorPrefix:"Fehler: ",warningPrefix:"Warnung: ",noSpace:"Es darf kein Leerzeichen in diesem Eingabefeld sein.",reqChkByNode:"Es wurden keine Elemente gew&auml;hlt.",requiredChk:"Dieses Feld muss ausgef&uuml;llt werden.",reqChkByName:"Bitte w&auml;hlen Sie ein {label}.",match:"Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.",startDate:"Das Anfangsdatum",endDate:"Das Enddatum",currendDate:"Das aktuelle Datum",afterDate:"Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.",beforeDate:"Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.",startMonth:"W&auml;hle
 n Sie bitte einen Anfangsmonat",sameMonth:"Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.",creditcard:"Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben."});MooTools.lang.set("de-CH","Form.Validator",{required:"Dieses Feld ist obligatorisch.",minLength:"Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).",maxLength:"Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).",integer:"Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.",numeric:"Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).",digits:"Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Pu
 nkten).",alpha:"Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.",alphanum:"Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.",dateSuchAs:"Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}",dateInFormatMDY:"Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)",email:"Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria at bernasconi.ch&quot;.",url:"Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.",currencyDollar:"Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .",oneRequired:"Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.",errorPrefix:"Fehler: ",warningPrefix:"Warnung: ",noSpace:"In diesem Eingabefeld darf kein Leerzeichen sein.",reqChkByNod
 e:"Es wurden keine Elemente gew&auml;hlt.",requiredChk:"Dieses Feld ist obligatorisch.",reqChkByName:"Bitte w&auml;hlen Sie ein {label}.",match:"Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.",startDate:"Das Anfangsdatum",endDate:"Das Enddatum",currendDate:"Das aktuelle Datum",afterDate:"Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.",beforeDate:"Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.",startMonth:"W&auml;hlen Sie bitte einen Anfangsmonat",sameMonth:"Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern."});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 no
 mbre 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 : ",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."});MooTools.lang.set("it-IT","Form.Validator",{requi
 red:"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: ",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."});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} t
 egn).",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 felten
 e.",errorPrefix:"Feil: ",warningPrefix:"Advarsel: "});MooTools.lang.set("pl-PL","Form.Validator",{required:"To pole jest wymagane.",minLength:"Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).",maxLength:"Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})",integer:"To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.",numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',digits:"Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).",alpha:"Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.",alphanum:"Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.",dateSuchAs:"Prosimy podać prawidłową datę w formacie: {date}",dateInFormatMDY:'Prosimy podać poprawną date w formaci
 e 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: ",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."});MooTools.lang.set("p
 t-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 ender
 eç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","/"]});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 us
 e 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: ",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 dat
 a 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."});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:"Внимание: "});MooTools.lang.set("ru-RU","Form.Validator",{required:"Ã?òî ïîëå îáÿçàòåëüÃî ê çàïîëÃÃ¥Ãèþ.",minLength:"Ã?îæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).",maxLength:"Ã?îæàëóéñòà, ââåäèòå ÃÃ¥ áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).",integer:"Ã?îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. ÄðîáÃûå ÷èñëà (Ãàïðèìåð 1.25) òóò ÃÃ¥ ðàçðåøåÃû.",nu
 meric:'Ã?îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (Ãàïðèìåð "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:"ÂÃèìàÃèå: "});MooTools.lang.set("es-ES","Form.Validator",{required:"Este campo es obligatorio.",minLength:"Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).",maxLength:"Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).",integer:"Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.",numeric:'Por favor introduce solo valores num&eac
 ute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',digits:"Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).",alpha:"Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.",alphanum:"Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.",dateSuchAs:"Por favor introduce una fecha v&aacute;lida como {date}",dateInFormatMDY:'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',email:'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred at domain.com".',url:"Por favor introduce una URL v&aacute;lida como http://www.google.com.",currencyDollar:"Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .",oneRequired:"Por favor introduce algo para por lo menos una de estas entradas.",errorPref
 ix:"Error: ",warningPrefix:"Aviso: ",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."});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 dett
 a 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: ",noSpace:"Det får inte finnas några mellanslag i detta fält.",reqChkByNode:"Inga objekt är valda.",requiredChk:"Detta är ett obligatoriskt fäl
 t.",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."});MooTools.lang.set("uk-UA","Form.Validator",{required:"Це поле повинне бути заповненим.",minLength:"Введіть хоча б {minLength} Ñ?имволів (Ви ввели {length}).",maxLength:"КількіÑ?Ñ‚ÑŒ Ñ?имволів не може бути більше {maxLength} (Ви ввели {length}).",integer:"Введіть в це поле чиÑ?ло. Дробові чиÑ?ла (наприклад 1.25) не дозволені.",numeric:'Введіть в це поле чиÑ?ло (наприклад "1" або "1.1", або "-1", Ð
 °Ð±Ð¾ "-1.1").',digits:"Ð’ цьому полі ви можете викориÑ?товувати лише цифри Ñ– знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).",alpha:"Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z). Пробіли Ñ– інші Ñ?имволи заборонені.",alphanum:"Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z) Ñ– цифри (0-9). Пробіли Ñ– інші Ñ?имволи заборонені.",dateSuchAs:"Введіть коректну дату {date}.",dateInFormatMDY:'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',email:'Введіть коректну адреÑ?у електронної пошти (наприклад "name at domain.com").',url:"Введіть коре
 ктне інтернет-поÑ?иланнÑ? (наприклад http://www.google.com).",currencyDollar:'Введіть Ñ?уму в доларах (наприклад "$100.00").',oneRequired:"Заповніть одне з полів.",errorPrefix:"Помилка: ",warningPrefix:"Увага: "});function $jx(B){var A=null;B=document.id(B);if(B){A=B.retrieve("jxWidget");if(!A&&B!=document.body){A=$jx(B.getParent())}}return A}window.addEvent("load",function(){if(!("console" in window)){var B=["log","debug","info","warn","error","assert","dir","dirxml","group","groupEnd","time","timeEnd","count","trace","profile","profileEnd"],A;window.console={};for(A=0;A<B.length;++A){window.console[B[A]]=function(){}}}});Class.Mutators.Family=function(A,B){if($defined(B)){A.jxFamily=B;return A}else{if(!$defined(this.prototype.jxFamily)){this.implement({jxFamily:A})}}};function $unlink(C){if(C&&C.jxFamily){return C}var B,E,D,A;switch($type(C)){case"object":B={};for(E in C){B[E]=$unlink(C[E
 ])}break;case"hash":B=new Hash(C);break;case"array":B=[];for(D=0,A=C.length;D<A;D++){B[D]=$unlink(C[D])}break;default:return C}return B}if(typeof Jx==="undefined"){var Jx={}}if(!$defined(Jx.baseURL)){(function(){var D=document.getElementsByTagName("SCRIPT"),B,C,E,A;for(B=0;B<D.length;B++){C=D[B].src;E=C.lastIndexOf("/");A=C.slice(E+1,C.length-1);if(A.contains("jxlib")){Jx.baseURL=C.slice(0,E);break}}})()}if(!$defined(Jx.aPixel)){Jx.aPixel=new Element("img",{alt:"",title:"",src:Jx.baseURL+"/a_pixel.png"})}if(!$defined(Jx.isAir)){(function(){var A=document.getElementsByTagName("SCRIPT"),B=A[0].src;if(B.contains("app:")){Jx.isAir=true}else{Jx.isAir=false}})()}Jx.setLanguage=function(A){Jx.lang=A;MooTools.lang.setLanguage(Jx.lang)};if(!$defined(Jx.lang)){Jx.lang="en-US"}Jx.setLanguage(Jx.lang);Jx.applyPNGFilter=function(C){var A=Jx.aPixel.src,B;if(C.src!=A){B=C.src;C.src=A;C.runtimeStyle.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+B+"',sizingMethod='scale')
 "}};Jx.imgQueue=[];Jx.imgLoaded={};Jx.imagesLoading=0;Jx.addToImgQueue=function(A){if(Jx.imgLoaded[A.src]){A.element.src=A.src}else{Jx.imgQueue.push(A);Jx.imgLoaded[A.src]=true}Jx.checkImgQueue()};Jx.checkImgQueue=function(){while(Jx.imagesLoading<2&&Jx.imgQueue.length>0){Jx.loadNextImg()}};Jx.loadNextImg=function(){var A=Jx.imgQueue.shift();if(A){++Jx.imagesLoading;A.element.onload=function(){--Jx.imagesLoading;Jx.checkImgQueue()};A.element.onerror=function(){--Jx.imagesLoading;Jx.checkImgQueue()};A.element.src=A.src}};Jx.getNumber=function(C,B){var A=C===null||isNaN(parseInt(C,10))?(B||0):parseInt(C,10);return A};Jx.getPageDimensions=function(){return{width:window.getWidth(),height:window.getHeight()}};Jx.type=function(A){return typeof A=="undefined"?false:A.jxFamily||$type(A)};(function(A){Element.implement({getBoxSizing:function(){var C="content-box",B,D;if(Browser.Engine.trident||Browser.Engine.presto){B=document.compatMode;if(B=="BackCompat"||B=="QuirksMode"){C="border
 -box"}else{C="content-box"}}else{if(arguments.length===0){node=document.documentElement}D=this.getStyle("-moz-box-sizing");if(!D){D=this.getStyle("box-sizing")}C=(D?D:"content-box")}return C},getContentBoxSize:function(){var B=this.getSizes(["padding","border"]);return{width:this.offsetWidth-B.padding.left-B.padding.right-B.border.left-B.border.right,height:this.offsetHeight-B.padding.bottom-B.padding.top-B.border.bottom-B.border.top}},getBorderBoxSize:function(){return{width:this.offsetWidth,height:this.offsetHeight}},getMarginBoxSize:function(){var B=this.getSizes(["margin"]);return{width:this.offsetWidth+B.margin.left+B.margin.right,height:this.offsetHeight+B.margin.top+B.margin.bottom}},getSizes:function(E,C){E=E||["padding","border","margin"];C=C||["left","top","right","bottom"];var B={},D,F;E.each(function(G){B[G]={};C.each(function(H){D=(G=="border")?H+"-width":H;F=this.getStyle(G+"-"+D);B[G][H]=F===null||isNaN(parseInt(F,10))?0:parseInt(F,10)},this)},this);return B},
 setContentBoxSize:function(D){var C,E,B;if(this.getBoxSizing()=="border-box"){C=this.measure(function(){return this.getSizes(["padding","border"])});if($defined(D.width)){E=D.width+C.padding.left+C.padding.right+C.border.left+C.border.right;if(E<0){E=0}this.setStyle("width",E)}if($defined(D.height)){B=D.height+C.padding.top+C.padding.bottom+C.border.top+C.border.bottom;if(B<0){B=0}this.setStyle("height",B)}}else{if($defined(D.width)&&D.width>=0){this.setStyle("width",E)}if($defined(D.height)&&D.height>=0){this.setStyle("height",B)}}},setBorderBoxSize:function(D){var C,E,B;if(this.getBoxSizing()=="content-box"){C=this.measure(function(){return this.getSizes()});if($defined(D.width)){E=D.width-C.padding.left-C.padding.right-C.border.left-C.border.right-C.margin.left-C.margin.right;if(E<0){E=0}this.setStyle("width",E)}if($defined(D.height)){B=D.height-C.padding.top-C.padding.bottom-C.border.top-C.border.bottom-C.margin.top-C.margin.bottom;if(B<0){B=0}this.setStyle("height",B)}}
 else{if($defined(D.width)&&D.width>=0){this.setStyle("width",E)}if($defined(D.height)&&D.height>=0){this.setStyle("height",B)}}},descendantOf:function(C){var B=document.id(this.parentNode);while(B!=C&&B&&B.parentNode&&B.parentNode!=B){B=document.id(B.parentNode)}return B==C},findElement:function(C){var D=this,B=D.tagName;while(D.tagName!=C&&D&&D.parentNode&&D.parentNode!=D){D=document.id(D.parentNode)}return D.tagName==C?D:false}});Array.implement({swap:function(C,B){var D=this[C];this[C]=this[B];this[B]=D}})})(document.id||$);Jx.Styles=new (new Class({dynamicStyleMap:new Hash(),getCssRule:function(B,A){var D=this.getDynamicStyleSheet(A),E=null,C;if(D.indicies){C=D.indicies.indexOf(B);if(C==-1){E=this.insertCssRule(B,"",A)}else{if(Browser.Engine.trident){E=D.sheet.rules[C]}else{E=D.sheet.cssRules[C]}}}return E},insertCssRule:function(B,G,A){var D=this.getDynamicStyleSheet(A),E,F=B+" {"+G+"}",C;if(Browser.Engine.trident){if(G==""){G="{}"}C=D.styleSheet.addRule(B,G);E=D.styleS
 heet.rules[C]}else{D.sheet.insertRule(F,D.indicies.length);E=D.sheet.cssRules[D.indicies.length]}D.indicies.push(B);return E},removeCssRule:function(C,B){var E=this.getDynamicStyleSheet(B),D=E.indicies.indexOf(C),A=false;E.indicies.splice(D,1);if(Browser.Engine.trident){E.removeRule(D);A=true}else{E.sheet.deleteRule(D);A=true}return A},getDynamicStyleSheet:function(A){A=(A)?A:"default";if(!this.dynamicStyleMap.has(A)){var B=new Element("style").set("type","text/css").inject(document.head);B.indicies=[];this.dynamicStyleMap.set(A,B)}return this.dynamicStyleMap.get(A)},enableStyleSheet:function(A){this.getDynamicStyleSheet(A).disabled=false},disableStyleSheet:function(A){this.getDynamicStyleSheet(A).disabled=true},removeStyleSheet:function(A){this.disableStyleSheet(A);this.getDynamicStyleSheet(A).dispose();this.dynamicStyleMap.erase(A)},isStyleSheetDefined:function(A){return this.dynamicStyleMap.has(A)}}))();Jx.Object=new Class({Family:"Jx.Object",Implements:[Options,Events],p
 lugins:null,pluginNamespace:"Other",parameters:["options"],options:{useLang:true,plugins:null},bound:null,initialize:function(){this.plugins=new Hash();this.bound={};var D=arguments.length,B={},E=this.parameters,F,A;if(D>0){if(D===1&&(Jx.type(arguments[0])==="object"||Jx.type(arguments[0])==="Hash")&&E.length===1&&E[0]==="options"){B=arguments[0]}else{F=E.length;A;if(F<=D){A=F}else{A=D}for(var C=0;C<A;C++){if(E[C]==="options"){$extend(B,arguments[C])}else{B[E[C]]=arguments[C]}}}}this.setOptions(B);this.bound.changeText=this.changeText.bind(this);if(this.options.useLang){MooTools.lang.addEvent("langChange",this.bound.changeText)}this.fireEvent("preInit");this.init();this.fireEvent("postInit");this.fireEvent("prePluginInit");this.initPlugins();this.fireEvent("postPluginInit");this.fireEvent("initializeDone")},initPlugins:function(){var A;if($defined(this.pluginNamespace)){if($defined(this.options.plugins)&&Jx.type(this.options.plugins)==="array"){this.options.plugins.each(func
 tion(B){if(B instanceof Jx.Plugin){B.attach(this);this.plugins.set(B.name,B)}else{if(Jx.type(B)==="object"){if($defined(Jx.Plugin[this.pluginNamespace][B.name.capitalize()])){A=new Jx.Plugin[this.pluginNamespace][B.name.capitalize()](B.options)}else{A=new Jx.Adaptor[this.pluginNamespace][B.name.capitalize()](B.options)}A.attach(this)}else{if(Jx.type(B)==="string"){if($defined(Jx.Plugin[this.pluginNamespace][B.capitalize()])){A=new Jx.Plugin[this.pluginNamespace][B.capitalize()]()}else{A=new Jx.Adaptor[this.pluginNamespace][B.capitalize()]()}A.attach(this)}}}},this)}}},destroy:function(){this.fireEvent("preDestroy");this.cleanup();this.fireEvent("postDestroy")},cleanup:function(){if(this.plugins.getLength>0){this.plugins.each(function(A){A.detach();A.destroy()},this)}this.plugins.empty();if(this.options.useLang&&$defined(this.bound.changeText)){MooTools.lang.removeEvent("langChange",this.bound.changeText)}this.bound=null},init:$empty,registerPlugin:function(A){if(!this.plugin
 s.has(A.name)){this.plugins.set(A.name,A)}},deregisterPlugin:function(A){if(this.plugins.has(A.name)){this.plugins.erase(A.name)}},getPlugin:function(A){if(this.plugins.has(A)){return this.plugins.get(A)}},getText:function(B){var A="";if(Jx.type(B)=="string"||Jx.type(B)=="number"){A=B}else{if(Jx.type(B)=="function"){A=B()}else{if(Jx.type(B)=="object"&&$defined(B.set)&&$defined(B.key)){this.i18n=B;if($defined(B.value)){A=MooTools.lang.get(B.set,B.key)[B.value]}else{A=MooTools.lang.get(B.set,B.key)}}}}return A},changeText:$empty,generateId:function(B){B=(B)?B:"jx-";var A=$uid(this);delete this.uid;return B+A}});MooTools.lang.set("en-US","Jx",{widget:{busyMessage:"Working ..."},colorpalette:{alphaLabel:"alpha (%)"},notice:{closeTip:"close this notice"},progressbar:{messageText:"Loading...",progressText:"{progress} of {total}"},field:{requiredText:"*"},file:{browseLabel:"Browse..."},"formatter.boolean":{"true":"Yes","false":"No"},"formatter.currency":{sign:"$"},"formatter.number
 ":{decimalSeparator:".",thousandsSeparator:","},splitter:{barToolTip:"drag this bar to resize"},panelset:{barToolTip:"drag this bar to resize"},panel:{collapseTooltip:"Collapse/Expand Panel",collapseLabel:"Collapse",expandLabel:"Expand",maximizeTooltip:"Maximize Panel",maximizeLabel:"Maximize",restoreTooltip:"Restore Panel",restoreLabel:"Restore",closeTooltip:"Close Panel",closeLabel:"Close"},confirm:{affirmativeLabel:"Yes",negativeLabel:"No"},dialog:{resizeToolTip:"Resize dialog"},message:{okButton:"Ok"},prompt:{okButton:"Ok",cancelButton:"Cancel"},upload:{buttonText:"Upload Files"},"plugin.resize":{tooltip:"Drag to resize, double click to auto-size."},"plugin.editor":{submitButton:"Save",cancelButton:"Cancel"}});Jx.Widget=new Class({Family:"Jx.Widget",Extends:Jx.Object,options:{id:null,content:null,contentURL:null,loadOnDemand:false,cacheContent:true,template:'<div class="jxWidget"></div>',busyClass:"jxBusy",busyMask:{"class":"jxSpinner jxSpinnerLarge",img:{"class":"jxSpin
 nerImage"},content:{"class":"jxSpinnerContent"},messageContainer:{"class":"jxSpinnerMessage"},useIframeShim:true,iframeShimOptions:{className:"jxIframeShim"},fx:true},deferRender:false},classes:new Hash({domObj:"jxWidget"}),busy:false,domObj:null,contentIsLoaded:false,chrome:null,init:function(){if(!this.options.deferRender){this.fireEvent("preRender");this.render();this.fireEvent("postRender")}else{this.fireEvent("deferRender")}},loadContent:function(B){var D,A=this.options,C;B=document.id(B);if(A.content){if(A.content.domObj){D=document.id(A.content.domObj)}else{D=document.id(A.content)}if(D){if(A.content.addTo){A.content.addTo(B)}else{B.appendChild(D)}this.contentIsLoaded=true}else{B.innerHTML=A.content;this.contentIsLoaded=true}}else{if(A.contentURL){this.contentIsLoaded=false;this.req=new Request({url:A.contentURL,method:"get",evalScripts:true,onRequest:(function(){if(A.loadOnDemand){this.setBusy(true)}}).bind(this),onSuccess:(function(E){B.innerHTML=E;this.contentIsLoa
 ded=true;if(Jx.isAir){$clear(this.reqTimeout)}this.setBusy(false);this.fireEvent("contentLoaded",this)}).bind(this),onFailure:(function(){this.contentIsLoaded=true;this.fireEvent("contentLoadFailed",this);this.setBusy(false)}).bind(this),headers:{"If-Modified-Since":"Sat, 1 Jan 2000 00:00:00 GMT"}});this.req.send();if(Jx.isAir){C=$defined(A.timeout)?A.timeout:10000;this.reqTimeout=this.checkRequest.delay(C,this)}}else{this.contentIsLoaded=true}}if(A.contentId){B.id=this.options.contentId}if(this.contentIsLoaded){this.fireEvent("contentLoaded",this)}},position:function(I,D,Q){I=document.id(I);D=document.id(D);var C=$splat(Q.horizontal||["center center"]),J=$splat(Q.vertical||["center center"]),G=$merge({top:0,right:0,bottom:0,left:0},Q.offsets||{}),O=D.getCoordinates(),L,N,P,F,B,M,A,E,H;if(!document.id(I.parentNode)||I.parentNode==document.body){L=Jx.getPageDimensions();N=document.id(document.body).getScroll()}else{L=document.id(I.parentNode).getContentBoxSize();N=document.id
 (I.parentNode).getScroll()}if(D==document.body){O.left+=N.x;O.top+=N.y}else{if(I.parentNode==D){O.left=0;O.top=0}}P=I.getMarginBoxSize();if(!C.some(function(R){H=R.split(" ");if(H.length!=2){return false}if(!isNaN(parseInt(H[0],10))){E=parseInt(H[0],10);if(E>=0){F=E}else{F=O.left+O.width+E}}else{switch(H[0]){case"right":F=O.left+O.width;break;case"center":F=O.left+Math.round(O.width/2);break;case"left":default:F=O.left;break}}if(!isNaN(parseInt(H[1],10))){E=parseInt(H[1],10);if(E<0){right=F+E;F=right-P.width}else{F+=E;right=F+P.width}right=O.left+O.width+parseInt(H[1],10);F=right-P.width}else{switch(H[1]){case"left":F-=G.left;right=F+P.width;break;case"right":F+=G.right;right=F;F=F-P.width;break;case"center":default:F=F-Math.round(P.width/2);right=F+P.width;break}}return(F>=N.x&&right<=N.x+L.width)})){if(right>L.width){F=N.x+L.width-P.width}if(F<0){F=0}}I.setStyle("left",F);if(!J.some(function(R){H=R.split(" ");if(H.length!=2){return false}if(!isNaN(parseInt(H[0],10))){M=par
 seInt(H[0],10)}else{switch(H[0]){case"bottom":M=O.top+O.height;break;case"center":M=O.top+Math.round(O.height/2);break;case"top":default:M=O.top;break}}if(!isNaN(parseInt(H[1],10))){var S=parseInt(H[1],10);if(S>=0){M+=S;A=M+P.height}else{A=M+S;M=A-P.height}}else{switch(H[1]){case"top":M-=G.top;A=M+P.height;break;case"bottom":M+=G.bottom;A=M;M=M-P.height;break;case"center":default:M=M-Math.round(P.height/2);A=M+P.height;break}}return(M>=N.y&&A<=N.y+L.height)})){if(A>L.height){M=N.y+L.height-P.height}if(M<0){M=0}}I.setStyle("top",M);var K=I.retrieve("jxLayout");if(K){K.options.left=F;K.options.top=M}},makeChrome:function(A){var C=new Element("div",{"class":"jxChrome",events:{contextmenu:function(D){D.stop()}}}),B;A.adopt(C);this.chromeOffsets=C.measure(function(){return this.getSizes(["padding"]).padding});C.setStyle("padding",0);B=C.getStyle("backgroundImage");if(B!=null){if(!(B.contains("http://")||B.contains("https://")||B.contains("file://")||B.contains("app:/"))){B=null}e
 lse{B=B.slice(4,-1);if(B.charAt(0)=='"'){B=B.slice(1,-1)}C.setStyle("backgroundImage","none");["TR","TL","BL","BR"].each(function(D){C.adopt(new Element("div",{"class":"jxChrome"+D}).adopt(new Element("img",{"class":"png24",src:B,alt:"",title:""})))},this)}}if($defined(window.IframeShim)){this.shim=new IframeShim(C,{className:"jxIframeShim"})}C.dispose();this.chrome=C},showChrome:function(A){A=document.id(A)||document.id(this);if(A){if(!this.chrome){this.makeChrome(A);A.addClass("jxHasChrome")}this.resizeChrome(A);if(this.shim){this.shim.show()}if(A&&this.chrome.parentNode!==A){A.adopt(this.chrome);this.chrome.setStyle("z-index",-1)}}},hideChrome:function(){if(this.chrome){if(this.shim){this.shim.hide()}this.chrome.parentNode.removeClass("jxHasChrome");this.chrome.dispose()}},resizeChrome:function(A){if(this.chrome&&Browser.Engine.trident4){this.chrome.setContentBoxSize(document.id(A).getBorderBoxSize());if(this.shim){this.shim.position()}}},addTo:function(A,B){var C=documen
 t.id(this.addable)||document.id(this.domObj);if(C){if(A instanceof Jx.Widget&&$defined(A.add)){A.add(C)}else{ref=document.id(A);C.inject(ref,B)}this.fireEvent("addTo",this)}return this},toElement:function(){return this.domObj},processTemplate:function(F,C,A){var E=new Hash(),B,D;if($defined(A)){B=A.set("html",F)}else{B=new Element("div",{html:F})}C.each(function(G){D=B.getElement("."+G);if($defined(D)){E.set(G,D)}});return E},dispose:function(){var A=document.id(this.addable)||document.id(this.domObj);if(A){A.dispose()}},cleanup:function(){if($defined(this.domObj)){this.domObj.eliminate("jxWidget");this.domObj.destroy()}if($defined(this.addable)){this.addable.destroy()}if($defined(this.domA)){this.domA.destroy()}if($defined(this.classes)){this.classes.each(function(B,A){this[A]=null},this)}this.elements.empty();this.elements=null;this.parent()},render:function(){this.elements=this.processElements(this.options.template,this.classes);if($defined(this.domObj)){if($defined(this.
 options.id)){this.domObj.set("id",this.options.id)}this.domObj.store("jxWidget",this)}},elements:null,processElements:function(B,A){var C=A.getValues();elements=this.processTemplate(B,C);A.each(function(E,D){if(D!="elements"&&elements.get(E)){this[D]=elements.get(E)}},this);return elements},isBusy:function(){return this.busy},setBusy:function(F,E,H){if(this.busy==F){return }var B=this.options,G,C,D,A=this.domObj;E=$defined(E)?E:{set:"Jx",key:"widget",value:"busyMessage"};H=$defined(H)?H:false;this.busy=F;this.fireEvent("busy",F);if(F){if(B.busyClass){A.addClass(B.busyClass)}if(B.busyMask&&A.spin){G=Jx.getNumber(A.getStyle("z-index"));D={style:{"z-index":G+1}};C=A.getBorderBoxSize();if(C.height<60||H){D["class"]="jxSpinner jxSpinnerSmall";D.img=null;D.message=new Element("p",{"class":"jxSpinnerMessage",html:'<span class="jxSpinnerImage"></span>'+this.getText(E)})}D=$merge(B.busyMask,D);A.get("spinner",D).show(!B.busyMask.fx)}}else{if(B.busyClass){A.removeClass(B.busyClass)}if
 (B.busyMask&&this.domObj.unspin){A.get("spinner").hide(!B.busyMask.fx)}}},changeText:function(A){if(this.busy){this.setBusy(false);this.setBusy(true)}},stack:function(A){Jx.Stack.stack(A||document.id(this))},unstack:function(A){Jx.Stack.unstack(A=A||document.id(this))}});if(Jx.isAir){Jx.Widget.implement({checkRequest:function(){if(this.req.xhr.readyState===1){$clear(this.reqTimeout);this.req.cancel();this.contentIsLoaded=true;this.fireEvent("contentLoadFailed",this)}}})}Jx.Selection=new Class({Family:"Jx.Selection",Extends:Jx.Object,options:{eventToFire:{select:"select",unselect:"unselect"},selectClass:"jxSelected",selectMode:"single",selectToggle:true,minimumSelection:0},selection:null,init:function(){this.selection=[];this.parent()},cleanup:function(){this.selection=null;this.parent()},defaultSelect:function(A){if(this.selection.length<this.options.minimumSelection){this.select(A)}},select:function(C){var A=this.options,B=this.selection;C=document.id(C);if(A.selectMode==="
 multiple"){if(B.contains(C)){this.unselect(C)}else{document.id(C).addClass(A.selectClass);B.push(C);this.fireEvent(A.eventToFire.select,C)}}else{if(A.selectMode=="single"){if(!this.selection.contains(C)){document.id(C).addClass(A.selectClass);B.push(C);if(B.length>1){this.unselect(B[0])}this.fireEvent(A.eventToFire.select,C)}else{if(A.selectToggle){this.unselect(C)}}}}},unselect:function(C){var B=this.selection,A=this.options;if(B.contains(C)&&B.length>A.minimumSelection){document.id(C).removeClass(A.selectClass);B.erase(C);this.fireEvent(A.eventToFire.unselect,[C,this])}},selected:function(){return this.selection},isSelected:function(A){return this.selection.contains(A)}});Jx.List=new Class({Family:"Jx.List",Extends:Jx.Object,parameters:["container","options","selection"],ownsSelection:false,container:null,selection:null,options:{items:null,hover:false,hoverClass:"jxHover",press:false,pressClass:"jxPressed",select:false},init:function(){this.container=document.id(this.optio
 ns.container);this.container.store("jxList",this);var D=this,B=this.options,C=function(E){var F=E.retrieve("jxListTargetItem")||E;return !F.hasClass("jxDisabled")},A=function(E){var F=E.retrieve("jxListTargetItem")||E;return !F.hasClass("jxUnselectable")};this.bound=$merge(this.bound,{mousedown:function(){if(C(this)){this.addClass(B.pressClass);D.fireEvent("mousedown",this,D)}},mouseup:function(){if(C(this)){this.removeClass(B.pressClass);D.fireEvent("mouseup",this,D)}},mouseenter:function(){if(C(this)){this.addClass(B.hoverClass);D.fireEvent("mouseenter",this,D)}},mouseleave:function(){if(C(this)){this.removeClass(B.hoverClass);D.fireEvent("mouseleave",this,D)}},keydown:function(E){if(E.key=="enter"&&C(this)){this.addClass("jxPressed")}},keyup:function(E){if(E.key=="enter"&&C(this)){this.removeClass("jxPressed")}},click:function(E){if(D.selection&&C(this)&&A(this)){D.selection.select(this,D)}D.fireEvent("click",this,D)},select:function(E){if(C(E)){var F=E.retrieve("jxListTa
 rgetItem")||E;D.fireEvent("select",F)}},unselect:function(E){if(C(E)){var F=E.retrieve("jxListTargetItem")||E;D.fireEvent("unselect",F)}},contextmenu:function(F){var E=this.retrieve("jxContextMenu");if(E){E.show(F);this.removeClass(B.pressClass)}F.stop()}});if(B.selection){this.setSelection(B.selection);B.select=true}else{if(B.select){this.selection=new Jx.Selection(B);this.ownsSelection=true}}if($defined(B.items)){this.add(B.items)}},cleanup:function(){this.container.getChildren().each(function(B){this.remove(B)},this);if(this.selection&&this.ownsSelection){this.selection.removeEvents();this.selection.destroy()}this.setSelection(null);this.container.eliminate("jxList");var A=this.bound;A.mousedown=null;A.mouseup=null;A.mouseenter=null;A.mouseleave=null;A.keydown=null;A.keyup=null;A.click=null;A.select=null;A.unselect=null;A.contextmenu=null;this.parent()},add:function(F,A){if(Jx.type(F)=="array"){F.each(function(H){this.add(H,A)}.bind(this));return }var E=document.id(F),G=E
 .retrieve("jxListTarget")||E,D=this.bound,C=this.options,B=this.container;if(G){G.store("jxListTargetItem",E);G.addEvents({contextmenu:this.bound.contextmenu});if(C.press&&C.pressClass){G.addEvents({mousedown:D.mousedown,mouseup:D.mouseup,keyup:D.keyup,keydown:D.keydown})}if(C.hover&&C.hoverClass){G.addEvents({mouseenter:D.mouseenter,mouseleave:D.mouseleave})}if(this.selection){G.addEvents({click:D.click})}if($defined(A)){if($type(A)=="number"){if(A<B.childNodes.length){E.inject(B.childNodes[A],"before")}else{E.inject(B,"bottom")}}else{if(B.hasChild(A)){E.inject(A,"after")}}this.fireEvent("add",F,this)}else{E.inject(B,"bottom");this.fireEvent("add",F,this)}if(this.selection){this.selection.defaultSelect(E)}}},remove:function(B){var A=document.id(B),C;if(A&&this.container.hasChild(A)){this.unselect(A,true);A.dispose();C=A.retrieve("jxListTarget")||A;C.removeEvents(this.bound);this.fireEvent("remove",B,this);return B}return null},replace:function(B,A){if(this.container.hasChil
 d(B)){this.add(A,B);this.remove(B)}},indexOf:function(A){return $A(this.container.childNodes).indexOf(A)},count:function(){return this.container.childNodes.length},items:function(){return $A(this.container.childNodes)},each:function(B,A){$A(this.container.childNodes).each(B,A)},select:function(A){if(this.selection){this.selection.select(A)}},unselect:function(A,B){if(this.selection){this.selection.unselect(A)}},selected:function(){return this.selection?this.selection.selected:[]},empty:function(){this.container.getChildren().each(function(A){this.remove(A)},this)},setSelection:function(A){var B=this.selection;if(B==A){return }if(B){B.removeEvents(this.bound);if(this.ownsSelection){B.destroy();this.ownsSelection=false}}this.selection=A;if(A){A.addEvents({select:this.bound.select,unselect:this.bound.unselect})}}});Jx.Stack=new (new Class({els:[],base:1000,increment:100,stack:function(A){this.unstack(A);this.els.push(A);this.setZIndex(A,this.els.length-1)},unstack:function(C){v
 ar D=this.els;if(D.contains(C)){C.setStyle("z-index","");var A=D.indexOf(C);D.erase(C);for(var B=A;B<D.length;B++){this.setZIndex(D[B],B)}}},setZIndex:function(B,A){A=A||this.els.indexOf(B);if(A!==false){document.id(B).setStyle("z-index",this.base+(A*this.increment))}}}))();MooTools.lang.set("de-DE","Date",{months:["Januar","Februar","März","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"]});MooTools.lang.set("de-DE","Jx",{widget:{busyMessage:"Arbeite ..."},colorpalette:{alphaLabel:"alpha (%)"},notice:{closeTip:"Notiz schließen"},progressbar:{messageText:"Lade...",progressText:"{progress} von {total}"},field:{requiredText:"*"},file:{browseLabel:"Durchsuchen..."},"formatter.boolean":{"true":"Ja","false":"Nein"},"formatter.currency":{sign:"€"},"formatter.number":{decimalSeparator:",",thousandsSeparator:"."},splitter:{barToolTip:"Ziehen Sie diese Leiste um die Größe zu verändern"},panelset:{barToolTip:"Ziehen Sie diese Leiste um die Größ
 e zu verändern"},panel:{collapseTooltip:"Panel ein-/ausklappen",collapseLabel:"Einklappen",expandLabel:"Ausklappen",maximizeTooltip:"Panel maximieren",maximizeLabel:"maximieren",restoreTooltip:"Panel wieder herstellen",restoreLabel:"wieder herstellen",closeTooltip:"Panel schließen",closeLabel:"Schließen"},confirm:{affirmativeLabel:"Ja",negativeLabel:"Nein"},dialog:{label:"Neues Fenster"},message:{okButton:"Ok"},prompt:{okButton:"Ok",cancelButton:"Abbrechen"},upload:{buttonText:"Dateien hochladen"},"plugin.resize":{tooltip:"Klicken um Größe zu verändern. Doppelklick für automatische Anpassung."},"plugin.editor":{submitButton:"Speichern",cancelButton:"Abbrechen"}});MooTools.lang.set("ru-RU-unicode","Jx",{widget:{busyMessage:"Обработка..."},colorpalette:{alphaLabel:"alpha (%)"},notice:{closeTip:"закрыть Ñ?то Ñ?ообщение"},progressbar:{messageText:"Загрузка...",progressText:"{progress} из {total}"},field:{requiredText:"*"},file:{browseLa
 bel:"Выбрать..."},"formatter.boolean":{"true":"Да","false":"Ð?ет"},"formatter.currency":{sign:"Ñ€."},"formatter.number":{decimalSeparator:",",thousandsSeparator:" "},splitter:{barToolTip:"потÑ?ни, чтобы изменить размер"},panelset:{barToolTip:"потÑ?ни, чтобы изменить размер"},panel:{collapseTooltip:"Свернуть/Развернуть Панель",collapseLabel:"Свернуть",expandLabel:"Развернуть",maximizeTooltip:"Увеличить Панель",maximizeLabel:"Увеличить",restoreTooltip:"ВоÑ?Ñ?тановить Панель",restoreLabel:"ВоÑ?Ñ?тановить",closeTooltip:"Закрыть Панель",closeLabel:"Закрыть"},confirm:{affirmativeLabel:"Да",negativeLabel:"Ð?ет"},dialog:{resizeToolTip:"Изменить размер"},message:{okButton:"Ок"},prompt:{okButton:"Ок",cancelButton:"Отмена"},upload:{buttonText:"Загрузка файла"},"plugin.resize":{
 tooltip:"ПотÑ?ни, чтобы изменить, двойной щелчок длÑ? авто размера."},"plugin.editor":{submitButton:"Сохранить",cancelButton:"Отмена"}});Jx.Record=new Class({Extends:Jx.Object,Family:"Jx.Record",options:{separator:".",primaryKey:null},data:null,state:null,columns:null,parameters:["store","columns","data","options"],init:function(){this.parent();if($defined(this.options.columns)){this.columns=this.options.columns}if($defined(this.options.data)){this.processData(this.options.data)}else{this.data=new Hash()}if($defined(this.options.store)){this.store=this.options.store}},get:function(B){var A=Jx.type(B);if(A!=="object"){if(B==="primaryKey"){B=this.resolveCol(this.options.primaryKey)}else{B=this.resolveCol(B)}}if(this.data.has(B.name)){return this.data.get(B.name)}else{return null}},set:function(C,D){var B=Jx.type(C),A;if(B!=="object"){C=this.resolveCol(C)}if(!$defined(this.data)){this.data=new Hash()}A=this.get(C);this.
 data.set(C.name,D);this.state=Jx.Record.UPDATE;return[C.name,A,D]},equals:function(B,C){if(B==="primaryKey"){B=this.resolveCol(this.options.primaryKey)}else{B=this.resolveCol(B)}if(!this.data.has(B.name)){return null}else{if(!$defined(this.comparator)){this.comparator=new Jx.Compare({separator:this.options.separator})}var A=this.comparator[B.type].bind(this.comparator);return(A(this.get(B),C)===0)}},processData:function(A){this.data=$H(A)},resolveCol:function(A){var B=Jx.type(A);if(B==="number"){A=this.columns[A]}else{if(B==="string"){this.columns.each(function(C){if(C.name===A){A=C}},this)}}return A},asHash:function(){return this.data}});Jx.Record.UPDATE=1;Jx.Record.DELETE=2;Jx.Record.INSERT=3;Jx.Store=new Class({Family:"Jx.Store",Extends:Jx.Object,options:{id:null,columns:[],protocol:null,strategies:null,record:null,recordOptions:{primaryKey:null}},data:null,index:0,id:null,loaded:false,ready:false,deleted:null,init:function(){this.parent();this.deleted=[];if($defined(this
 .options.id)){this.id=this.options.id}if(!$defined(this.options.protocol)){this.ready=false;return }else{this.protocol=this.options.protocol}this.strategies=new Hash();if($defined(this.options.strategies)){this.options.strategies.each(function(B){this.addStrategy(B)},this)}else{var A=new Jx.Store.Strategy.Full();this.addStrategy(A)}if($defined(this.options.record)){this.record=this.options.record}else{this.record=Jx.Record}},cleanup:function(){this.strategies.each(function(A){A.destroy()},this);this.strategies=null;this.protocol.destroy();this.protocol=null;this.record=null},getStrategy:function(A){if(this.strategies.has(A)){return this.strategies.get(A)}return null},addStrategy:function(A){this.strategies.set(A.name,A);A.setStore(this);A.activate()},load:function(A){this.fireEvent("storeLoad",A)},empty:function(){if($defined(this.data)){this.data.empty()}},hasNext:function(){if($defined(this.data)){return this.index<this.data.length-1}return null},hasPrevious:function(){if(
 $defined(this.data)){return this.index>0}return null},valid:function(){return($defined(this.data)&&$defined(this.data[this.index]))},next:function(){if($defined(this.data)){this.index++;if(this.index===this.data.length){this.index=this.data.length-1}this.fireEvent("storeMove",this);return true}else{return null}},previous:function(){if($defined(this.data)){this.index--;if(this.index<0){this.index=0}this.fireEvent("storeMove",this);return true}else{return null}},first:function(){if($defined(this.data)){this.index=0;this.fireEvent("storeMove",this);return true}else{return null}},last:function(){if($defined(this.data)){this.index=this.data.length-1;this.fireEvent("storeMove",this);return true}else{return null}},count:function(){if($defined(this.data)){return this.data.length}return null},getPosition:function(){if($defined(this.data)){return this.index}return null},moveTo:function(A){if($defined(this.data)&&A>=0&&A<this.data.length){this.index=A;this.fireEvent("storeMove",this);r
 eturn true}else{if(!$defined(this.data)){return null}else{return false}}},each:function(A,D,C){if($defined(this.data)){var B;if(C){B=this.data.filter(function(E){return E.state!==Jx.Record.DELETE},this)}else{B=this.data}B.each(A,D)}},get:function(B,A){if(!$defined(A)){A=this.index}return this.data[A].get(B)},set:function(C,D,B){if(!$defined(B)){B=this.index}var A=this.data[B].set(C,D);A.reverse();A.push(B);A.reverse();this.fireEvent("storeColumnChanged",A)},refresh:function(){this.fireEvent("storeRefresh",this)},addRecord:function(D,A,C){if(!$defined(this.data)){this.data=[]}A=$defined(A)?A:"bottom";var B=D;if(!(D instanceof Jx.Record)){B=new (this.record)(this,this.options.columns,D,this.options.recordOptions)}if(C){B.state=Jx.Record.INSERT}if(A==="top"){this.data.reverse();this.data.push(B);this.data.reverse()}else{this.data.push(B)}this.fireEvent("storeRecordAdded",[this,B,A])},addRecords:function(D,A){var C=$defined(D),B=Jx.type(D);if(C&&B==="array"){this.fireEvent("stor
 eBeginAddRecords",this);if(A==="top"){D.reverse()}D.each(function(E){this.addRecord(E,A)},this);this.fireEvent("storeEndAddRecords",this);return true}return false},getRecord:function(A){if(!$defined(A)){A=this.index}if(Jx.type(A)==="number"){if($defined(this.data)&&$defined(this.data[A])){return this.data[A]}}else{var B;this.data.each(function(C){if(C===A){B=C}},this);return B}return null},replace:function(D,C){if($defined(D)){if(!$defined(C)){C=this.index}var B=new this.record(this.options.columns,D),A=this.data[C];this.data[C]=B;this.fireEvent("storeRecordReplaced",[A,B]);return true}return false},deleteRecord:function(B){if(!$defined(B)){B=this.index}var A=this.data[B];A.state=Jx.Record.DELETE;this.data.splice(B,1);this.deleted.push(A);this.fireEvent("storeRecordDeleted",[this,A])},insertRecord:function(B,A){this.addRecord(B,A,true)},getColumns:function(){return this.options.columns},findByColumn:function(C,D){if(typeof StopIteration==="undefined"){StopIteration=new Error
 ("StopIteration")}var B;try{this.data.each(function(F,E){if(F.equals(C,D)){B=E;throw StopIteration}},this)}catch(A){if(A!==StopIteration){throw A}return B}return null},removeRecord:function(A){if(!$defined(A)){A=this.index}this.data.splice(A,1);this.fireEvent("storeRecordRemoved",[this,A])},removeRecords:function(C,B){for(var A=C;A<=B;A++){this.removeRecord(C)}this.fireEvent("storeMultipleRecordsRemoved",[this,C,B])},parseTemplate:function(C){var A=[],B;this.options.columns.each(function(D){B="{"+D.name+"}";if(C.contains(B)){A.push(D.name)}},this);return A},fillTemplate:function(C,D,F,E){var A=null,B;if($defined(C)){if(C instanceof Jx.Record){A=C}else{A=this.getRecord(C)}}else{A=this.getRecord(this.index)}B=$defined(E)?E:{};F.each(function(G){B[G]=A.get(G)},this);return D.substitute(B)}});Jx.Compare=new Class({Family:"Jx.Compare",Extends:Jx.Object,options:{separator:"."},alphanumeric:function(B,A){return(B===A)?0:(B<A)?-1:1},numeric:function(B,A){return this.alphanumeric(thi
 s.convert(B),this.convert(A))},convert:function(A){if(Jx.type(A)==="string"){var B=false;if(A.substr(0,1)=="-"){B=true}A=parseFloat(A.replace(/^[^\d\.]*([\d., ]+).*/g,"$1").replace(new RegExp("[^\\\d"+this.options.separator+"]","g"),"").replace(/,/,"."))||0;if(B){A=A*-1}}return A||0},ignorecase:function(B,A){return this.alphanumeric((""+B).toLowerCase(),(""+A).toLowerCase())},currency:function(B,A){return this.numeric(B,A)},date:function(C,B){var A=new Date().parse(C),D=new Date().parse(B);return(A<D)?-1:(A>D)?1:0},"boolean":function(B,A){return(B===true&&A===false)?-1:(B===A)?0:1}});Jx.Sort=new Class({Family:"Jx.Sort",Extends:Jx.Object,options:{timeIt:false,onStart:$empty,onEnd:$empty},timer:null,data:null,comparator:$empty,col:null,parameters:["data","fn","col","options"],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.opt
 ions.fn;this.col=this.options.col},sort:$empty,startTimer:function(){this.timer=new Date()},stopTimer:function(){this.end=new Date();this.dif=this.timer.diff(this.end,"ms")},setData:function(A){if($defined(A)){this.data=A}},setColumn:function(A){if($defined(A)){this.col=A}},setComparator:function(A){this.comparator=A}});Jx.Sort.Mergesort=new Class({Family:"Jx.Sort.Mergesort",Extends:Jx.Sort,name:"mergesort",sort:function(){this.fireEvent("start");var A=this.mergeSort(this.data);this.fireEvent("stop");return A},mergeSort:function(B){if(B.length<=1){return B}var C=(B.length)/2,E=B.slice(0,C),D=B.slice(C),A;E=this.mergeSort(E);D=this.mergeSort(D);A=this.merge(E,D);return A},merge:function(C,B){var A=[];while(C.length>0&&B.length>0){if(this.comparator((C[0]).get(this.col),(B[0]).get(this.col))<=0){A.push(C[0]);C=C.slice(1)}else{A.push(B[0]);B=B.slice(1)}}while(C.length>0){A.push(C[0]);C=C.slice(1)}while(B.length>0){A.push(B[0]);B=B.slice(1)}return A}});Jx.Sort.Heapsort=new Class
 ({Family:"Jx.Sort.Heapsort",Extends:Jx.Sort,name:"heapsort",sort:function(){this.fireEvent("start");var B=this.data.length,A;if(B===1){return this.data}if(B>2){this.heapify(B);A=B-1;while(A>1){this.data.swap(A,0);A=A-1;this.siftDown(0,A)}}else{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},heapify:function(A){var B=Math.round((A-2)/2);while(B>=0){this.siftDown(B,A-1);B=B-1}},siftDown:function(D,B){var A=D,C;while(A*2<=B){C=A*2;if((C+1<B)&&(this.comparator((this.data[C]).get(this.col),(this.data[C+1]).get(this.col))<0)){C=C+1}if((this.comparator((this.data[A]).get(this.col),(this.data[C]).get(this.col))<0)){this.data.swap(A,C);A=C}else{return }}}});Jx.Sort.Quicksort=new Class({Family:"Jx.Sort.Quicksort",Extends:Jx.Sort,name:"quicksort",sort:function(B,A){this.fireEvent("start");if(!$defined(B)){B=0}if(!$defined(A)){A=this.data.length-1}this.quicksort(B,A);this.fireEvent("stop");r
 eturn this.data},quicksort:function(C,B){if(C>=B){return }var A=this.partition(C,B);this.quicksort(C,A-1);this.quicksort(A+1,B)},partition:function(F,D){this.findMedianOfMedians(F,D);var E=F,A=(this.data[E]).get(this.col),B=F,C;this.data.swap(E,D);for(C=F;C<D;C++){if(this.comparator((this.data[C]).get(this.col),A)<0){this.data.swap(C,B);B=B+1}}this.data.swap(D,B);return B},findMedianOfMedians:function(F,C){if(F===C){return this.data[F]}var B,A=1,E,D;while(A<=(C-F)){for(B=F;B<=C;B+=A*5){E=(B+A*5-1<C)?B+A*5-1:C;D=this.findMedianIndex(B,E,A);this.data.swap(B,D)}A*=5}return this.data[F]},findMedianIndex:function(C,I,A){var B=Math.round((I-C)/A+1),D=Math.round(C+B/2*A),F,G,J,H,E;if(D>this.data.length-1){D=this.data.length-1}for(F=C;F<D;F+=A){G=F;J=this.data[G];H=J.get(this.col);for(E=F;E<=I;E+=A){if(this.comparator((this.data[E]).get(this.col),H)<0){G=E;H=(this.data[G]).get(this.col)}}this.data.swap(F,G)}return D}});Jx.Sort.Nativesort=new Class({Family:"Jx.Sort.Nativesort",Extend
 s:Jx.Sort,name:"nativesort",sort:function(){this.fireEvent("start");var A=function(C,B){return this.comparator((this.data[C]).get(this.col),(this.data[B]).get(this.col))};this.data.sort(A);this.fireEvent("stop");return this.data}});Jx.Store.Response=new Class({Family:"Jx.Store.Response",Extends:Jx.Object,code:null,data:null,meta:null,requestType:null,requestParams:null,request:null,error:null,success:function(){return this.code>0}});Jx.Store.Response.WAITING=2;Jx.Store.Response.SUCCESS=1;Jx.Store.Response.FAILURE=0;Jx.Store.Protocol=new Class({Extends:Jx.Object,Family:"Jx.Store.Protocol",parser:null,options:{combine:{insert:false,update:false,"delete":false}},init:function(){this.parent();if($defined(this.options.parser)){this.parser=this.options.parser}},cleanup:function(){this.parser=null;this.parent()},read:$empty,insert:$empty,update:$empty,"delete":$empty,abort:$empty,combineRequests:function(A){return $defined(this.options.combine[A])?this.options.combine[A]:false}});J
 x.Store.Protocol.Local=new Class({Extends:Jx.Store.Protocol,parameters:["data","options"],data:null,init:function(){this.parent();if($defined(this.options.data)){this.data=this.parser.parse(this.options.data)}},read:function(B){var F=new Jx.Store.Response(),E=B.data.page,C=B.data.itemsPerPage,G,A,D=this.data;F.requestType="read";F.requestParams=arguments;if($defined(D)){if(E<=1&&C===-1){F.data=D;F.meta={count:D.length}}else{G=(E-1)*C;A=G+C;F.data=D.slice(G,A);F.meta={page:E,itemsPerPage:C,totalItems:D.length,totalPages:Math.ceil(D.length/C)}}F.code=Jx.Store.Response.SUCCESS;this.fireEvent("dataLoaded",F)}else{F.code=Jx.Store.Response.SUCCESS;this.fireEvent("dataLoaded",F)}}});Jx.Store.Protocol.Ajax=new Class({Extends:Jx.Store.Protocol,options:{requestOptions:{method:"get"},rest:false,urls:{rest:null,insert:null,read:null,update:null,"delete":null},queue:{autoAdvance:true,concurrent:1}},queue:null,init:function(){if(!$defined(Jx.Store.Protocol.Ajax.UniqueId)){Jx.Store.Protoco
 l.Ajax.UniqueId=1}this.queue=new Request.Queue({autoAdvance:this.options.queue.autoAdvance,concurrent:this.options.queue.concurrent});this.parent()},read:function(B){var E=new Jx.Store.Response(),A={},D,C,F=Jx.Store.Protocol.Ajax.UniqueId();E.requestType="read";E.requestParams=arguments;if(this.options.rest){A.url=this.options.urls.rest}else{A.url=this.options.urls.read}D=$merge(this.options.requestOptions,A,B);D.onSuccess=this.handleResponse.bind(this,E);C=new Request(D);E.request=C;this.queue.addRequest(F,C);C.send();E.code=Jx.Store.Response.WAITING;return E},handleResponse:function(A){var B=A.request,D=B.xhr.responseText,C=this.parser.parse(D);if($defined(C)){if($defined(C.success)&&C.success){if($defined(C.data)){A.data=C.data}if($defined(C.meta)){A.meta=C.meta}A.code=Jx.Store.Response.SUCCESS}else{A.code=Jx.Store.Response.FAILURE;A.error=$defined(C.error)?C.error:null}}else{A.code=Jx.Store.Response.FAILURE}this.fireEvent("dataLoaded",A)},insert:function(A,B){if(this.opt
 ions.rest){B=$merge({url:this.options.urls.rest},B)}else{B=$merge({url:this.options.urls.insert},B)}this.options.requestOptions.method="POST";return this.run(A,B,"insert")},update:function(A,B){if(this.options.rest){B=$merge({url:this.options.urls.rest},B);this.options.requestOptions.method="PUT"}else{B=$merge({url:this.options.urls.update},B);this.options.requestOptions.method="POST"}return this.run(A,B,"update")},"delete":function(A,B){if(this.options.rest){B=$merge({url:this.options.urls.rest},B);this.options.requestOptions.method="DELETE"}else{B=$merge({url:this.options.urls["delete"]},B);this.options.requestOptions.method="POST"}return this.run(A,B,"delete")},abort:function(A){A.request.cancel()},run:function(A,B,H){var F=new Jx.Store.Response(),D,C,E,G=Jx.Store.Protocol.Ajax.UniqueId();if(Jx.type(A)=="array"){if(!this.combineRequests(H)){A.each(function(I){this.run(I,B,H)},this)}else{E=[];A.each(function(I){E.push(this.parser.encode(I))},this)}}else{E=this.parser.encod
 e(A)}this.options.requestOptions.data=$merge(this.options.requestOptions.data,{data:E});F.requestType=H;F.requestParams=[A,B,H];D=$merge(this.options.requestOptions,B);D.onSuccess=this.handleResponse.bind(this,F);C=new Request(D);F.request=C;this.queue.addRequest(G,C);C.send();F.code=Jx.Store.Response.WAITING;return F}});Jx.Store.Protocol.Ajax.UniqueId=(function(){var A=1;return function(){return"req-"+(A++)}})();Jx.Store.Strategy=new Class({Extends:Jx.Object,Family:"Jx.Store.Strategy",store:null,active:null,init:function(){this.parent();this.active=false},setStore:function(A){if(A instanceof Jx.Store){this.store=A;return true}return false},activate:function(){if(!this.active){this.active=true;return true}return false},deactivate:function(){if(this.active){this.active=false;return true}return false}});Jx.Store.Strategy.Full=new Class({Extends:Jx.Store.Strategy,name:"full",options:{},init:function(){this.parent();this.bound.load=this.load.bind(this);this.bound.loadStore=this.
 loadStore.bind(this)},activate:function(){this.parent();this.store.addEvent("storeLoad",this.bound.load)},deactivate:function(){this.parent();this.store.removeEvent("storeLoad",this.bound.load)},load:function(B){this.store.fireEvent("storeBeginDataLoad",this.store);this.store.protocol.addEvent("dataLoaded",this.bound.loadStore);var A={};if($defined(B)){A.data=B}else{A.data={}}A.data.page=0;A.data.itemsPerPage=-1;this.store.protocol.read(A)},loadStore:function(A){this.store.protocol.removeEvent("dataLoaded",this.bound.loadStore);if(A.success()){this.store.empty();if($defined(A.meta)){this.parseMetaData(A.meta)}this.store.addRecords(A.data);this.store.loaded=true;this.store.fireEvent("storeDataLoaded",this.store)}else{this.store.loaded=false;this.store.fireEvent("storeDataLoadFailed",[this.store,A])}},parseMetaData:function(A){if($defined(A.columns)){this.store.options.columns=A.columns}if($defined(A.primaryKey)){this.store.options.recordOptions.primaryKey=A.primaryKey}}});Jx.
 Store.Strategy.Paginate=new Class({Extends:Jx.Store.Strategy,name:"paginate",options:{getPaginationParams:function(){return{page:this.page,itemsPerPage:this.itemsPerPage}},startingItemsPerPage:25,startingPage:1,expirationInterval:(1000*60*5),ignoreExpiration:false},data:null,cacheTimer:null,page:null,itemsPerPage:null,init:function(){this.parent();this.data=new Hash();this.cacheTimer=new Hash();this.bound.load=this.load.bind(this);this.bound.loadStore=this.loadStore.bind(this);this.itemsPerPage=this.options.startingItemsPerPage;this.page=this.options.startingPage},activate:function(){this.parent();this.store.addEvent("storeLoad",this.bound.load)},deactivate:function(){this.parent();this.store.removeEvent("storeLoad",this.bound.load)},load:function(B){this.store.fireEvent("storeBeginDataLoad",this.store);this.store.protocol.addEvent("dataLoaded",this.bound.loadStore);this.params=B;var A={data:$merge(B,this.options.getPaginationParams.apply(this))};this.store.protocol.read(A)}
 ,loadStore:function(A){this.store.protocol.removeEvent("dataLoaded",this.bound.loadStore);if(A.success()){if($defined(A.meta)){this.parseMetaData(A.meta)}this.data.set(this.page,A.data);this.loadData(A.data)}else{this.store.fireEvent("storeDataLoadFailed",this.store)}},loadData:function(A){this.store.empty();this.store.loaded=false;if(!this.options.ignoreExpiration){var B=this.expirePage.delay(this.options.expirationInterval,this,this.page);this.cacheTimer.set(this.page,B)}this.store.addRecords(A);this.store.loaded=true;this.store.fireEvent("storeDataLoaded",this.store)},parseMetaData:function(A){if($defined(A.columns)){this.store.options.columns=A.columns}if($defined(A.totalItems)){this.totalItems=A.totalItems}if($defined(A.totalPages)){this.totalPages=A.totalPages}if($defined(A.primaryKey)){this.store.options.recordOptions.primaryKey=A.primaryKey}},expirePage:function(A){this.data.erase(A);this.cacheTimer.erase(A)},setPage:function(A){if(Jx.type(A)==="string"){switch(A){ca
 se"first":this.page=1;break;case"last":this.page=this.totalPages;break;case"next":this.page++;break;case"previous":this.page--;break;default:this.page=this.page+Jx.getNumber(A);break}}else{this.page=A}if(this.cacheTimer.has(this.page)){$clear(this.cacheTimer.get(this.page));this.cacheTimer.erase(this.page)}if(this.data.has(this.page)){this.loadData(this.data.get(this.page))}else{this.load(this.params)}},getPage:function(){return this.page},getNumberOfPages:function(){return this.totalPages},setPageSize:function(A){this.itemsPerPage=A;this.cacheTimer.each(function(B){$clear(B)},this);this.cacheTimer.empty();this.data.empty();this.load()},getPageSize:function(){return this.itemsPerPage},getTotalCount:function(){return this.totalItems}});Jx.Store.Strategy.Progressive=new Class({Extends:Jx.Store.Strategy.Paginate,name:"progressive",options:{maxRecords:1000,dropRecords:true},startingPage:0,maxPages:null,loadedPages:0,loadAt:"bottom",init:function(){this.parent();if(this.options.d
 ropPages){this.maxPages=Math.ceil(this.options.maxRecords/this.itemsPerPage)}},loadStore:function(A){this.store.protocol.removeEvent("dataLoaded",this.bound.loadStore);if(A.success()){if($defined(A.meta)){this.parseMetaData(A.meta)}this.loadData(A.data)}else{this.store.fireEvent("storeDataLoadFailed",this.store)}},loadData:function(A){this.store.loaded=false;this.store.addRecords(A,this.loadAt);this.store.loaded=true;this.loadedPages++;this.store.fireEvent("storeDataLoaded",this.store)},nextPage:function(A){if(!$defined(A)){A={}}if(this.options.dropRecords&&this.totalPages>this.startingPage+this.loadedPages){this.loadAt="bottom";if(this.loadedPages>=this.maxPages){this.startingPage++;this.store.removeRecords(0,this.itemsPerPage-1);this.loadedPages--}}this.page=this.startingPage+this.loadedPages+1;this.load($merge(this.params,A))},previousPage:function(A){if(!this.options.dropRecords){return }if(!$defined(A)){A={}}if(this.startingPage>0){this.loadAt="top";if(this.loadedPages>
 =this.maxPages){this.startingPage--;this.store.removeRecords(this.options.maxRecords-this.itemsPerPage,this.options.maxRecords);this.loadedPages--}this.page=this.startingPage;this.load($merge(this.params,A))}}});Jx.Store.Strategy.Save=new Class({Extends:Jx.Store.Strategy,name:"save",options:{autoSave:false},failedChanges:[],successfulChanges:[],totalChanges:0,init:function(){this.bound.save=this.saveRecord.bind(this);this.bound.update=this.updateRecord.bind(this);this.bound.completed=this.onComplete.bind(this);this.parent()},activate:function(){this.parent();if(Jx.type(this.options.autoSave)==="number"){this.periodicalId=this.save.periodical(this.options.autoSave,this)}else{if(this.options.autoSave){this.store.addEvent("storeRecordAdded",this.bound.save);this.store.addEvent("storeColumnChanged",this.bound.update);this.store.addEvent("storeRecordDeleted",this.bound.save)}}},deactivate:function(){this.parent();if($defined(this.periodicalId)){$clear(this.periodicalId)}else{if(t
 his.options.autoSave){this.store.removeEvent("storeRecordAdded",this.bound.save);this.store.removeEvent("storeColumnChanged",this.bound.update);this.store.removeEvent("storeRecordDeleted",this.bound.save)}}},updateRecord:function(B,C,A,D){var E=this.saveRecord(this.store,this.store.getRecord(B));if(E){E.index=B}},saveRecord:function(B,A){if(!this.updating&&$defined(A.state)){if(this.totalChanges===0){B.protocol.addEvent("dataLoaded",this.bound.completed)}this.totalChanges++;var C;switch(A.state){case Jx.Record.UPDATE:C=B.protocol.update(A);break;case Jx.Record.DELETE:C=B.protocol["delete"](A);break;case Jx.Record.INSERT:C=B.protocol.insert(A);break;default:break}return C}},save:function(){if(this.store.loaded){var A=[];A[Jx.Record.UPDATE]=[];A[Jx.Record.INSERT]=[];this.store.data.each(function(B){if($defined(B)&&$defined(B.state)){A[B.state].push(B)}},this);A[Jx.Record.DELETE]=this.store.deleted;if(!this.updating){if(this.totalChanges===0){store.protocol.addEvent("dataLoaded
 ",this.bound.completed)}this.totalChanges+=A[Jx.Record.UPDATE].length+A[Jx.Record.INSERT].length+A[Jx.Record.DELETE].length;if(A[Jx.Record.UPDATE].length){this.store.protocol.update(A[Jx.Record.UPDATE])}if(A[Jx.Record.INSERT].length){this.store.protocol.insert(A[Jx.Record.INSERT])}if(A[Jx.Record.DELETE].length){this.store.protocol["delete"](A[Jx.Record.DELETE])}}}},onComplete:function(B){if(!B.success()||($defined(B.meta)&&!B.meta.success)){this.failedChanges.push(B)}else{var A=[B.requestParams[0]].flatten(),C=$defined(B.data)?[B.data].flatten():null;A.each(function(D,E){if(B.requestType==="delete"){this.store.deleted.erase(D)}else{if(B.requestType==="insert"||B.requestType=="update"){if(C&&$defined(C[E])){this.updating=true;$H(C[E]).each(function(H,F){var G=D.set(F,H);if(G[1]!=H){G.unshift(E);D.store.fireEvent("storeColumnChanged",G)}});this.updating=false}}D.state=null}this.totalChanges--},this);this.successfulChanges.push(B)}if(this.totalChanges===0){this.store.protocol.r
 emoveEvent("dataLoaded",this.bound.completed);this.store.fireEvent("storeChangesCompleted",{successful:this.successfulChanges,failed:this.failedChanges})}}});Jx.Store.Strategy.Sort=new Class({Extends:Jx.Store.Strategy,name:"sort",options:{sortOnStoreEvents:["storeColumnChanged","storeDataLoaded"],defaultSort:"merge",separator:".",sortCols:[]},sorters:{quick:"Quicksort",merge:"Mergesort",heap:"Heapsort","native":"Nativesort"},init:function(){this.parent();this.bound.sort=this.sort.bind(this)},activate:function(){if($defined(this.options.sortOnStoreEvents)){this.options.sortOnStoreEvents.each(function(A){this.store.addEvent(A,this.bound.sort)},this)}},deactivate:function(){if($defined(this.options.sortOnStoreEvents)){this.options.sortOnStoreEvents.each(function(A){this.store.removeEvent(A,this.bound.sort)},this)}},sort:function(C,B,A){if(this.store.count()){this.store.fireEvent("sortStart",this);var D;if($defined(C)&&Jx.type(C)==="array"){D=this.options.sortCols=C}else{if($def
 ined(C)&&Jx.type(C)==="string"){this.options.sortCols=[];this.options.sortCols.push(C);D=this.options.sortCols}else{if($defined(this.options.sortCols)){D=this.options.sortCols}else{return null}}}this.sortType=B;this.store.data=this.doSort(D[0],B,this.store.data,true);if(D.length>1){this.store.data=this.subSort(this.store.data,0,1)}if($defined(A)&&A==="desc"){this.store.data.reverse()}this.store.fireEvent("storeSortFinished",this)}},subSort:function(E,F,B){if(B>=this.options.sortCols.length){return E}var I=[];var A=[];var C=this.options.sortCols[F];var G=this.options.sortCols[B];var H=E[0].get(C);this.sorter.setColumn(G);for(var D=0;D<E.length;D++){if(H===(E[D]).get(C)){A.push(E[D])}else{if(A.length>1){I=I.concat(this.subSort(this.doSort(G,this.sortType,A,true),F+1,B+1))}else{I=I.concat(A)}H=(E[D]).get(C);A.empty();A.push(E[D])}}if(A.length>1){this.sorter.setData(A);I=I.concat(this.subSort(this.doSort(G,this.sortType,A,true),F+1,B+1))}else{I=I.concat(A)}return I},doSort:funct
 ion(C,D,F,B,A){A={}||A;D=(D)?this.sorters[D]:this.sorters[this.options.defaultSort];F=F?F:this.data;B=B?true:false;if(!$defined(this.comparator)){this.comparator=new Jx.Compare({separator:this.options.separator})}this.col=C=this.resolveCol(C);var E=this.comparator[C.type].bind(this.comparator);if(!$defined(this.sorter)){this.sorter=new Jx.Sort[D](F,E,C.name,A)}else{this.sorter.setComparator(E);this.sorter.setColumn(C.name);this.sorter.setData(F)}var G=this.sorter.sort();if(B){return G}else{this.data=G}},resolveCol:function(A){var B=Jx.type(A);if(B==="number"){A=this.store.options.columns[A]}else{if(B==="string"){this.store.options.columns.each(function(C){if(C.name===A){A=C}},this)}}return A}});Jx.Store.Parser=new Class({Extends:Jx.Object,Family:"Jx.Store.Parser",parse:$empty,encode:$empty});Jx.Store.Parser.JSON=new Class({Extends:Jx.Store.Parser,options:{secure:false},parse:function(B){var A=Jx.type(B);if(A==="string"){return JSON.decode(B,this.options.secure)}return B},enc
 ode:function(A){var B;if(A instanceof Jx.Record){B=A.asHash()}else{B=A}return JSON.encode(B)}});Jx.Button=new Class({Family:"Jx.Button",Extends:Jx.Widget,options:{image:"",tooltip:"",label:"",toggle:false,toggleClass:"jxButtonToggle",pressedClass:"jxButtonPressed",activeClass:"jxButtonActive",active:false,enabled:true,href:"javascript:void(0);",target:"",template:'<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'},classes:new Hash({domObj:"jxButtonContainer",domA:"jxButton",domImg:"jxButtonIcon",domLabel:"jxButtonLabel"}),render:function(){this.parent();var B=this.options,C,A;if(B.toggle){this.domObj.addClass(B.toggleClass)}if(this.domA){this.domA.set({target:B.target,href:B.href,title:this.getText(B.tooltip),alt:this.getText(B.tooltip)});this.domA.addEvents({click:this.clicked.bindWithEvent(this),drag:(function(D){D.stop()}).bindWithEvent(
 this),mousedown:(function(D){this.domA.addClass(B.pressedClass);C=true;A=true;this.focus()}).bindWithEvent(this),mouseup:(function(D){this.domA.removeClass(B.pressedClass);A=false}).bindWithEvent(this),mouseleave:(function(D){this.domA.removeClass(B.pressedClass)}).bindWithEvent(this),mouseenter:(function(D){if(C&&A){this.domA.addClass(B.pressedClass)}}).bindWithEvent(this),keydown:(function(D){if(D.key=="enter"){this.domA.addClass(B.pressedClass)}}).bindWithEvent(this),keyup:(function(D){if(D.key=="enter"){this.domA.removeClass(B.pressedClass)}}).bindWithEvent(this),blur:function(){C=false}});if(typeof Drag!="undefined"){new Drag(this.domA,{onStart:function(){this.stop()}})}}if(this.domImg){if(B.image||!B.label){this.domImg.set({title:this.getText(B.tooltip),alt:this.getText(B.tooltip)});if(B.image&&B.image.indexOf(Jx.aPixel.src)==-1){this.domImg.setStyle("backgroundImage","url("+B.image+")")}if(B.imageClass){this.domImg.addClass(B.imageClass)}}else{this.domImg.setStyle("di
 splay","none")}}if(this.domLabel){if(B.label||this.domA.hasClass("jxDiscloser")){this.setLabel(B.label)}else{this.domLabel.setStyle("display","none")}}if(B.id){this.domObj.set("id",B.id)}this.setEnabled(B.enabled);if(B.active){B.active=false;this.setActive(true)}},clicked:function(A){var B=this.options;if(B.enabled&&!this.isBusy()){if(B.toggle){this.setActive(!B.active)}else{this.fireEvent("click",{obj:this,event:A})}}},isEnabled:function(){return this.options.enabled},setEnabled:function(A){this.options.enabled=A;if(A){this.domObj.removeClass("jxDisabled")}else{this.domObj.addClass("jxDisabled")}},isActive:function(){return this.options.active},setActive:function(B){var A=this.options;if(A.enabled&&!this.isBusy()){if(A.active==B){return }A.active=B;if(this.domA){if(A.active){this.domA.addClass(A.activeClass)}else{this.domA.removeClass(A.activeClass)}}this.fireEvent(B?"down":"up",this)}},setImage:function(A){this.options.image=A;if(this.domImg){this.domImg.setStyle("backgrou
 ndImage","url("+A+")");this.domImg.setStyle("display",A?null:"none")}},setLabel:function(A){this.options.label=A;if(this.domLabel){this.domLabel.set("html",this.getText(A));this.domLabel.setStyle("display",A||this.domA.hasClass("jxDiscloser")?null:"none")}},getLabel:function(){return this.options.label},setTooltip:function(B){if(this.domA){this.domA.set({title:this.getText(B),alt:this.getText(B)})}if(this.domImg){var A=this.domImg.get("title");if($defined(A)){this.domImg.set({title:this.getText(B),alt:this.getText(B)})}}},focus:function(){if(this.domA){this.domA.focus()}},blur:function(){if(this.domA){this.domA.blur()}},changeText:function(A){this.parent();this.setLabel(this.options.label);this.setTooltip(this.options.tooltip)}});Jx.Button.Flyout=new Class({Family:"Jx.Button.Flyout",Extends:Jx.Button,Binds:["keypressHandler","clickHandler"],options:{template:'<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img cla
 ss="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',contentTemplate:'<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>',position:{horizontal:["left left","right right"],vertical:["bottom top","top bottom"]},positionElement:null},contentClasses:new Hash({contentContainer:"jxFlyout",content:"jxFlyoutContent"}),content:null,render:function(){var A=this.options;if(!Jx.Button.Flyout.Stack){Jx.Button.Flyout.Stack=[]}this.parent();this.processElements(A.contentTemplate,this.contentClasses);if(A.contentClass){this.content.addClass(A.contentClass)}this.content.store("jxFlyout",this);if(!A.loadOnDemand||A.active){this.loadContent(this.content)}else{this.addEvent("contentLoaded",function(B){this.show(B)}.bind(this))}},cleanup:function(){this.content.eliminate("jxFlyout");this.content.destroy();this.contentClasses.each(function(B,A){this[A]=null},this);this.parent()},clicked:function(B){var A=this.options;if(!A.enabled){return }if(thi
 s.contentIsLoaded&&A.cacheContent){this.show(B)}else{if(A.loadOnDemand||!A.cacheContent){this.loadContent(this.content)}else{this.show(B)}}},show:function(G){var F,D,C=this.owner,B=Jx.Button.Flyout.Stack,E=this.options;if(!C){this.owner=C=document.body;var F=document.id(this.domObj.parentNode);while(F!=document.body&&C==document.body){var D=F.retrieve("jxFlyout");if(D){this.owner=C=D;break}else{F=document.id(F.parentNode)}}}if(B[B.length-1]==this){this.hide();return }else{if(C!=document.body){if(C.currentFlyout==this){this.hide();return }else{if(C.currentFlyout){C.currentFlyout.hide()}}C.currentFlyout=this}else{while(B.length){B[B.length-1].hide()}}}B.push(this);this.fireEvent("beforeOpen");E.active=true;this.domA.addClass(E.activeClass);this.contentContainer.setStyle("visibility","hidden");document.id(document.body).adopt(this.contentContainer);this.content.getChildren().each(function(I){if(I.resize){I.resize()}});this.showChrome(this.contentContainer);var A=E.positionEleme
 nt||this.domObj;var H=$merge(E.position,{offsets:this.chromeOffsets});this.position(this.contentContainer,A,H);this.contentContainer.setContentBoxSize(document.id(this.content).getMarginBoxSize());this.stack(this.contentContainer);this.contentContainer.setStyle("visibility","");document.addEvent("keydown",this.keypressHandler);document.addEvent("click",this.clickHandler);this.fireEvent("open",this)},hide:function(){if(this.owner!=document.body){this.owner.currentFlyout=null}Jx.Button.Flyout.Stack.pop();this.setActive(false);this.contentContainer.dispose();this.unstack(this.contentContainer);document.removeEvent("keydown",this.keypressHandler);document.removeEvent("click",this.clickHandler);this.fireEvent("close",this)},clickHandler:function(B){B=new Event(B);var C=document.id(B.target),A=Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length-1];if(!C.descendantOf(A.content)&&!C.descendantOf(A.domObj)){A.hide()}},keypressHandler:function(A){A=new Event(A);if(A.key=="esc"){Jx.Bu
 tton.Flyout.Stack[Jx.Button.Flyout.Stack.length-1].hide()}}});Jx.ColorPalette=new Class({Family:"Jx.ColorPalette",Extends:Jx.Widget,domObj:null,options:{parent:null,color:"#000000",alpha:1,hexColors:["00","33","66","99","CC","FF"]},render:function(){this.domObj=new Element("div",{id:this.options.id,"class":"jxColorPalette"});var G=new Element("div",{"class":"jxColorBar"});var P=new Element("div",{"class":"jxColorPreview"});this.selectedSwatch=new Element("div",{"class":"jxColorSelected"});this.previewSwatch=new Element("div",{"class":"jxColorHover"});P.adopt(this.selectedSwatch);P.adopt(this.previewSwatch);G.adopt(P);this.colorInputLabel=new Element("label",{"class":"jxColorLabel",html:"#"});G.adopt(this.colorInputLabel);var J=this.changed.bind(this);this.colorInput=new Element("input",{"class":"jxHexInput",type:"text",maxLength:6,events:{keyup:J,blur:J,change:J}});G.adopt(this.colorInput);this.alphaLabel=new Element("label",{"class":"jxAlphaLabel",html:this.getText({set:"Jx
 ",key:"colorpalette",value:"alphaLabel"})});G.adopt(this.alphaLabel);this.alphaInput=new Element("input",{"class":"jxAlphaInput",type:"text",maxLength:3,events:{keyup:this.alphaChanged.bind(this)}});G.adopt(this.alphaInput);this.domObj.adopt(G);var H=this.swatchClick.bindWithEvent(this);var F=this.swatchOver.bindWithEvent(this);var O=new Element("table",{"class":"jxColorGrid"});var A=new Element("tbody");O.adopt(A);for(var M=0;M<12;M++){var B=new Element("tr");for(var K=-3;K<18;K++){var E=false;var I,N,Q;if(K<0){if(K==-3||K==-1){I=N=Q=0;E=true}else{if(M<6){I=N=Q=M}else{if(M==6){I=5;N=0;Q=0}else{if(M==7){I=0;N=5;Q=0}else{if(M==8){I=0;N=0;Q=5}else{if(M==9){I=5;N=5;Q=0}else{if(M==10){I=0;N=5;Q=5}else{if(M==11){I=5;N=0;Q=5}}}}}}}}}else{I=parseInt(M/6,10)*3+parseInt(K/6,10);N=K%6;Q=M%6}var C="#"+this.options.hexColors[I]+this.options.hexColors[N]+this.options.hexColors[Q];var D=new Element("td");if(!E){D.setStyle("backgroundColor",C);var R=new Element("a",{"class":"colorSwatch "+
 (((I>2&&N>2)||(I>2&&Q>2)||(N>2&&Q>2))?"borderBlack":"borderWhite"),href:"javascript:void(0)",title:C,alt:C,events:{mouseover:F,click:H}});R.store("swatchColor",C);D.adopt(R)}else{var L=new Element("span",{"class":"emptyCell"});D.adopt(L)}B.adopt(D)}A.adopt(B)}this.domObj.adopt(O);this.updateSelected();if(this.options.parent){this.addTo(this.options.parent)}},swatchOver:function(B){var A=B.target;this.previewSwatch.setStyle("backgroundColor",A.retrieve("swatchColor"))},swatchClick:function(B){var A=B.target;this.options.color=A.retrieve("swatchColor");this.updateSelected();this.fireEvent("click",this)},changed:function(){var A=this.colorInput.value;if(A.substring(0,1)=="#"){A=A.substring(1)}if(A.toLowerCase().match(/^[0-9a-f]{6}$/)){this.options.color="#"+A.toUpperCase();this.updateSelected()}},alphaChanged:function(){var A=this.alphaInput.value;if(A.match(/^[0-9]{1,3}$/)){this.options.alpha=parseFloat(A/100);this.updateSelected()}},setColor:function(A){this.colorInput.value=
 A;this.changed()},setAlpha:function(A){this.alphaInput.value=A;this.alphaChanged()},updateSelected:function(){var A={backgroundColor:this.options.color};this.colorInput.value=this.options.color.substring(1);this.alphaInput.value=parseInt(this.options.alpha*100,10);if(this.options.alpha<1){A.opacity=this.options.alpha;A.filter="Alpha(opacity="+(this.options.alpha*100)+")"}else{A.opacity=1;A.filter=""}this.selectedSwatch.setStyles(A);this.previewSwatch.setStyles(A);this.fireEvent("change",this)},changeText:function(A){this.parent();if($defined(this.alphaLabel)){this.alphaLabel.set("html",this.getText({set:"Jx",key:"colorpalette",value:"alphaLabel"}))}}});Jx.Button.Color=new Class({Family:"Jx.Button.Color",Extends:Jx.Button.Flyout,swatch:null,options:{color:"#000000",alpha:100,template:'<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span cl
 ass="jxButtonLabel"></span></span></a></span>'},classes:new Hash({domObj:"jxButtonContainer",domA:"jxButton",swatch:"jxButtonSwatchColor",domLabel:"jxButtonLabel"}),render:function(){if(!Jx.Button.Color.ColorPalette){Jx.Button.Color.ColorPalette=new Jx.ColorPalette(this.options)}this.options.image=Jx.aPixel.src;this.parent();this.updateSwatch();this.bound.changed=this.changed.bind(this);this.bound.hide=this.hide.bind(this)},cleanup:function(){this.bound.changed=false;this.bound.hide=false;this.parent()},clicked:function(){var A=Jx.Button.Color.ColorPalette;if(A.currentButton){A.currentButton.hide()}A.currentButton=this;A.addEvent("change",this.bound.changed);A.addEvent("click",this.bound.hide);this.content.appendChild(A.domObj);A.domObj.setStyle("display","block");Jx.Button.Flyout.prototype.clicked.apply(this,arguments);A.options.color=this.options.color;A.options.alpha=this.options.alpha/100;A.updateSelected()},hide:function(){var A=Jx.Button.Color.ColorPalette;this.setActi
 ve(false);A.removeEvent("change",this.bound.changed);A.removeEvent("click",this.bound.hide);Jx.Button.Flyout.prototype.hide.apply(this,arguments);A.currentButton=null},setColor:function(A){this.options.color=A;this.updateSwatch()},setAlpha:function(A){this.options.alpha=A;this.updateSwatch()},changed:function(A){var B=false;if(this.options.color!=A.options.color){this.options.color=A.options.color;B=true}if(this.options.alpha!=A.options.alpha*100){this.options.alpha=A.options.alpha*100;B=true}if(B){this.updateSwatch();this.fireEvent("change",this)}},updateSwatch:function(){var A={backgroundColor:this.options.color};if(this.options.alpha<100){A.filter="Alpha(opacity="+(this.options.alpha)+")";A.opacity=this.options.alpha/100}else{A.opacity=1;A.filter=""}this.swatch.setStyles(A)}});Jx.Menu=new Class({Family:"Jx.Menu",Extends:Jx.Widget,button:null,subDomObj:null,list:null,parameters:["buttonOptions","options"],options:{exposeOnHover:false,hideDelay:0,template:"<div class='jxMen
 uContainer'><ul class='jxMenu'></ul></div>",buttonTemplate:'<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><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:new Hash({contentContainer:"jxMenuContainer",subDomObj:"jxMenu"}),init:function(){this.bound.stop=function(A){A.stop()};this.bound.remove=function(A){A.setOwner(null)};this.bound.show=this.show.bind(this);this.bound.mouseenter=this.onMouseEnter.bind(this);this.bound.mouseleave=this.onMouseLeave.bind(this);this.bound.keypress=this.keypressHandler.bind(this);this.bound.hide=this.hide.bind(this);this.parent()},render:function(){this.parent();if(!Jx.Menu.Menus){Jx.Menu.Menus=[]}this.contentClone=this.contentContainer.clone();this.list=new Jx.List(this.subDomObj,{onRemove:this.bound.remove});if(this.options.buttonOptions){this.butto
 n=new Jx.Button($merge(this.options.buttonOptions,{template:this.options.buttonTemplate,onClick:this.bound.show}));this.button.domA.addEvent("mouseenter",this.bound.mouseenter);this.button.domA.addEvent("mouseleave",this.bound.mouseleave);this.domObj=this.button.domObj;this.domObj.store("jxMenu",this)}this.subDomObj.addEvent("mouseenter",this.bound.mouseenter);this.subDomObj.addEvent("mouseleave",this.bound.mouseleave);this.subDomObj.store("jxSubMenu",this);if(this.options.parent){this.addTo(this.options.parent)}},cleanup:function(){if(this.hideTimer){window.clearTimeout(this.hideTimer)}this.list.removeEvent("remove",this.bound.remove);this.list.destroy();this.list=null;if(this.button){this.domObj.eliminate("jxMenu");this.domObj=null;this.button.removeEvent("click",this.bound.show);this.button.domA.removeEvents({mouseenter:this.bound.mouseenter,mouseleave:this.bound.mouseleave});this.button.destroy();this.button=null}this.subDomObj.removeEvents({mouseenter:this.bound.mouseen
 ter,mouseleave:this.bound.mouseleave});this.subDomObj.removeEvents();this.contentContainer.removeEvent("contextmenu",this.bound.stop);this.subDomObj.destroy();this.contentContainer.destroy();this.contentClone.destroy();this.bound.remove=null;this.bound.show=null;this.bound.stop=null;this.bound.mouseenter=null;this.bound.mouseleave=null;this.bound.keypress=null;this.bound.hide=null;this.parent()},add:function(C,B,A){if(Jx.type(C)=="array"){C.each(function(D){D.setOwner(A||this)},this)}else{C.setOwner(A||this)}this.list.add(C,B);return this},remove:function(A){this.list.remove(A);return this},replace:function(B,A){this.list.replace(B,A);return this},empty:function(){this.list.each(function(A){if(A.empty){A.empty()}A.setOwner(null)},this);this.list.empty()},deactivate:function(){this.hide()},onMouseEnter:function(A){if(this.hideTimer){window.clearTimeout(this.hideTimer);this.hideTimer=null}if(Jx.Menu.Menus[0]&&Jx.Menu.Menus[0]!=this){this.show.delay(1,this)}else{if(this.options
 .exposeOnHover){if(Jx.Menu.Menus[0]&&Jx.Menu.Menus[0]==this){Jx.Menu.Menus[0]=null}this.show.delay(1,this)}}},onMouseLeave:function(A){if(this.options.hideDelay>0){this.hideTimer=(function(){this.deactivate()}).delay(this.options.hideDelay,this)}},eventInMenu:function(D){var C=document.id(D.target);if(!C){return false}if(C.descendantOf(this.domObj)||C.descendantOf(this.subDomObj)){return true}else{var B=C.getParent("ul");if(B){var E=B.retrieve("jxSubMenu");if(E){if(E.eventInMenu(D)){return true}var A=E.owner;while(A){if(A==this){return true}A=A.owner}}}return false}},hide:function(A){if(A){if(this.visibleItem&&this.visibleItem.eventInMenu){if(this.visibleItem.eventInMenu(A)){return }}else{if(this.eventInMenu(A)){return }}}if(Jx.Menu.Menus[0]&&Jx.Menu.Menus[0]==this){Jx.Menu.Menus[0]=null}if(this.button&&this.button.domA){this.button.domA.removeClass(this.button.options.activeClass)}if(this.hideTimer){window.clearTimeout(this.hideTimer)}this.list.each(function(B){B.retrieve("
 jxMenuItem").hide(A)});document.removeEvent("mousedown",this.bound.hide);document.removeEvent("keydown",this.bound.keypress);this.unstack(this.contentContainer);this.contentContainer.dispose();this.visibleItem=null;this.fireEvent("hide",this)},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.hideTimer){window.clearTimeout(this.hideTimer)}this.subDomObj.dispose();this.contentContainer.destroy();this.contentContainer=this.contentClone.clone();this.contentContainer.empty().adopt(this.subDomObj);this.contentContainer.addEvent("contextmenu",this.bound.stop);this.contentContainer.setStyle("display","none");document.id(document.body).adopt(this.contentContainer);this.contentContainer.setStyles({visibility:"hidden",display:"block"});this.contentContainer.setContentBoxSize(this.subDomObj.get
 MarginBoxSize());this.showChrome(this.contentContainer);this.position(this.contentContainer,this.domObj,$merge({offsets:this.chromeOffsets},this.options.position));this.stack(this.contentContainer);this.contentContainer.setStyle("visibility","visible");if(this.button&&this.button.domA){this.button.domA.addClass(this.button.options.activeClass)}document.addEvent("mousedown",this.bound.hide);document.addEvent("keydown",this.bound.keypress);this.fireEvent("show",this)},setVisibleItem:function(A){if(this.hideTimer){window.clearTimeout(this.hideTimer)}if(this.visibleItem!=A){if(this.visibleItem&&this.visibleItem.hide){this.visibleItem.hide()}this.visibleItem=A;this.visibleItem.show()}},keypressHandler:function(A){A=new Event(A);if(A.key=="esc"){this.hide()}},isEnabled:function(){return this.button?this.button.isEnabled():this.options.enabled},setEnabled:function(A){return this.button?this.button.setEnabled(A):this.options.enable},isActive:function(){return this.button?this.button
 .isActive():this.options.active},setActive:function(A){if(this.button){this.button.setActive(A)}},setImage:function(A){if(this.button){this.button.setImage(A)}},setLabel:function(A){if(this.button){this.button.setLabel(A)}},getLabel:function(){return this.button?this.button.getLabel():""},setTooltip:function(A){if(this.button){this.button.setTooltip(A)}},focus:function(){if(this.button){this.button.focus()}},blur:function(){if(this.button){this.button.blur()}}});Jx.Menu.Item=new Class({Family:"Jx.Menu.Item",Extends:Jx.Button,owner:null,options:{label:"&nbsp;",toggleClass:"jxMenuItemToggle",pressedClass:"jxMenuItemPressed",activeClass:"jxMenuItemActive",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:new Hash({domObj:"jxMenuItemContainer",domA:"jxMenuItem",domImg:"jxMenuItemIcon",domLabel:"jxMenuItemLabel"}),init:f
 unction(){this.bound.mouseover=this.onMouseOver.bind(this);this.parent()},render:function(){if(!this.options.image){this.options.image=Jx.aPixel.src}this.parent();if(this.options.image&&this.options.image!=Jx.aPixel.src){this.domObj.removeClass(this.options.toggleClass)}if(this.options.target){this.domA.set("target",this.options.target)}this.domObj.addEvent("mouseover",this.bound.mouseover);this.domObj.store("jxMenuItem",this)},cleanup:function(){this.domObj.eliminate("jxMenuItem");this.domObj.removeEvent("mouseover",this.bound.mouseover);this.bound.mouseover=null;this.owner=null;this.parent()},setOwner:function(A){this.owner=A},hide:function(){this.blur.delay(1,this)},show:$empty,clicked:function(B){var A=this.options.href&&this.options.href.indexOf("javascript:")!=0;if(this.options.enabled){if(!A){if(this.options.toggle){this.setActive.delay(1,this,!this.options.active)}this.fireEvent.delay(1,this,["click",{obj:this}]);this.blur()}if(this.owner&&this.owner.deactivate){this
 .owner.deactivate.delay(1,this.owner,B.event)}}return A?true:false},onMouseOver:function(A){A.stop();if(this.owner&&this.owner.setVisibleItem){this.owner.setVisibleItem(this)}return false},changeText:function(A){this.parent();if(this.owner&&this.owner.deactivate){this.owner.deactivate()}}});Jx.ButtonSet=new Class({Family:"Jx.ButtonSet",Extends:Jx.Object,Binds:["buttonChanged"],buttons:[],cleanup:function(){this.buttons.each(function(A){A.removeEvent("down",this.buttonChanged);A.setActive=null},this);this.activeButton=null;this.buttons=null;this.parent()},add:function(){$A(arguments).each(function(A){if(A.domObj.hasClass(A.options.toggleClass)){A.domObj.removeClass(A.options.toggleClass);A.domObj.addClass(A.options.toggleClass+"Set")}A.addEvent("down",this.buttonChanged);A.setActive=function(B){if(A.options.active&&this.activeButton==A){return }else{Jx.Button.prototype.setActive.apply(A,[B])}}.bind(this);if(!this.activeButton||A.options.active){A.options.active=false;A.setAct
 ive(true)}this.buttons.push(A)},this);return this},remove:function(A){this.buttons.erase(A);if(this.activeButton==A){if(this.buttons.length){this.buttons[0].setActive(true)}A.removeEvent("down",this.buttonChanged);A.setActive=Jx.Button.prototype.setActive}},empty:function(){this.buttons=[];this.activeButton=null},setActiveButton:function(B){var A=this.activeButton;this.activeButton=B;if(A&&A!=B){A.setActive(false)}},buttonChanged:function(A){this.setActiveButton(A);this.fireEvent("change",this)}});Jx.Button.Multi=new Class({Family:"Jx.Button.Multi",Extends:Jx.Button,activeButton:null,buttons:null,options:{template:'<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>',menuOptions:{}},classes:new Hash({domObj:"jxButtonContainer",domA:
 "jxButton",domImg:"jxButtonIcon",domLabel:"jxButtonLabel",domDisclose:"jxButtonDisclose"}),render:function(){this.parent();this.buttons=[];this.menu=new Jx.Menu({},this.options.menuOptions);this.menu.button=this;this.buttonSet=new Jx.ButtonSet();this.bound.click=this.clicked.bind(this);if(this.domDisclose){var A=this;var B;this.bound.disclose={click:function(C){if(this.list.count()===0){return }if(!A.options.enabled){return }this.contentContainer.setStyle("visibility","hidden");this.contentContainer.setStyle("display","block");document.id(document.body).adopt(this.contentContainer);this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());this.showChrome(this.contentContainer);this.position(this.contentContainer,this.button.domObj,{horizontal:["right right"],vertical:["bottom top","top bottom"],offsets:this.chromeOffsets});this.contentContainer.setStyle("visibility","");document.addEvent("mousedown",this.bound.hide);document.addEvent("keyup",this.bound.keypr
 ess);this.fireEvent("show",this)}.bindWithEvent(this.menu),mouseenter:function(){document.id(this.domObj.firstChild).addClass("jxButtonHover");if(B){this.domDisclose.addClass(this.options.pressedClass)}}.bind(this),mouseleave:function(){document.id(this.domObj.firstChild).removeClass("jxButtonHover");this.domDisclose.removeClass(this.options.pressedClass)}.bind(this),mousedown:function(C){this.domDisclose.addClass(this.options.pressedClass);B=true;this.focus()}.bindWithEvent(this),mouseup:function(C){this.domDisclose.removeClass(this.options.pressedClass)}.bindWithEvent(this),keydown:function(C){if(C.key=="enter"){this.domDisclose.addClass(this.options.pressedClass)}}.bindWithEvent(this),keyup:function(C){if(C.key=="enter"){this.domDisclose.removeClass(this.options.pressedClass)}}.bindWithEvent(this),blur:function(){B=false}};this.domDisclose.addEvents({click:this.bound.disclose.click,mouseenter:this.bound.disclose.mouseenter,mouseleave:this.bound.disclose.mouseleave,mousedo
 wn:this.bound.disclose.mousedown,mouseup:this.bound.disclose.mouseup,keydown:this.bound.disclose.keydown,keyup:this.bound.disclose.keyup,blur:this.bound.disclose.blur});if(typeof Drag!="undefined"){new Drag(this.domDisclose,{onStart:function(){this.stop()}})}}this.bound.show=function(){this.domA.addClass(this.options.activeClass)}.bind(this);this.bound.hide=function(){if(this.options.active){this.domA.addClass(this.options.activeClass)}}.bind(this);this.menu.addEvents({show:this.bound.show,hide:this.bound.hide});if(this.options.items){this.add(this.options.items)}},cleanup:function(){var A=this,B=this.bound;if(A.domDisclose){A.domDisclose.removeEvents({click:B.disclose.click,mouseenter:B.disclose.mouseenter,mouseleave:B.disclose.mouseleave,mousedown:B.disclose.mousedown,mouseup:B.disclose.mouseup,keydown:B.disclose.keydown,keyup:B.disclose.keyup,blur:B.disclose.blur})}A.buttonSet.destroy();A.buttonSet=null;A.buttons.each(function(C){C.removeEvents();A.menu.remove(C.multiButt
 on);C.multiButton.destroy();C.multiButton=null;C.destroy()});A.buttons.empty();A.buttons=null;A.menu.removeEvents({show:B.show,hide:B.hide});A.menu.button=null;A.menu.destroy();A.menu=null;A.bound.show=null;A.bound.hide=null;A.bound.clicked=null;A.bound.disclose=null;A.activeButton=null;A.parent()},add:function(){$A(arguments).flatten().each(function(B){var D,C,A;if(!B instanceof Jx.Button){return }B.domA.addClass("jxDiscloser");B.setLabel(B.options.label);this.buttons.push(B);D=this.setButton.bind(this,B);C={image:B.options.image,imageClass:B.options.imageClass,label:B.options.label||"&nbsp;",enabled:B.options.enabled,tooltip:B.options.tooltip,toggle:true,onClick:D};if(!C.image||C.image.indexOf("a_pixel")!=-1){delete C.image}A=new Jx.Menu.Item(C);this.buttonSet.add(A);this.menu.add(A);B.multiButton=A;B.domA.addClass("jxButtonMulti");if(!this.activeButton){this.domA.dispose();this.setActiveButton(B)}},this)},remove:function(A){if(!A||!A.multiButton){return }if(this.menu.remo
 ve(A.multiButton)){A.multiButton=null;if(this.activeButton==A){if(!this.buttons.some(function(B){if(B!=A){this.setActiveButton(B);return true}else{return false}},this)){this.setActiveButton(null)}}this.buttons.erase(A)}},empty:function(){this.buttons.each(function(A){this.remove(A)},this)},setActiveButton:function(A){if(this.activeButton){this.activeButton.domA.dispose();this.activeButton.domA.removeEvent("click",this.bound.click)}if(A&&A.domA){this.domObj.grab(A.domA,"top");this.domA=A.domA;this.domA.addEvent("click",this.bound.click);if(this.options.toggle){this.options.active=false;this.setActive(true)}}this.activeButton=A},setButton:function(A){this.setActiveButton(A);A.clicked()}});Jx.Layout=new Class({Family:"Jx.Layout",Extends:Jx.Object,options:{resizeWithWindow:false,propagate:true,position:"absolute",left:0,right:0,top:0,bottom:0,width:null,height:null,minWidth:0,minHeight:0,maxWidth:-1,maxHeight:-1},parameters:["domObj","options"],init:function(){this.domObj=docume
 nt.id(this.options.domObj);this.domObj.resize=this.resize.bind(this);this.domObj.setStyle("position",this.options.position);this.domObj.store("jxLayout",this);if(this.options.resizeWithWindow||document.body==this.domObj.parentNode){window.addEvent("resize",this.windowResize.bindWithEvent(this));window.addEvent("load",this.windowResize.bind(this))}},windowResize:function(){this.resize();if(this.resizeTimer){$clear(this.resizeTimer);this.resizeTimer=null}this.resizeTimer=this.resize.delay(50,this)},resize:function(K){this.resizeTimer=null;var B=false;if(K){for(var G in K){if(G=="forceResize"){continue}if(this.options[G]!=K[G]){B=true;this.options[G]=K[G]}}if(K.forceResize){B=true}}if(!document.id(this.domObj.parentNode)){return }var C;if(this.domObj.parentNode.tagName=="BODY"){C=Jx.getPageDimensions()}else{C=document.id(this.domObj.parentNode).getContentBoxSize()}if(this.lastParentSize&&!B){B=(this.lastParentSize.width!=C.width||this.lastParentSize.height!=C.height)}else{B=tru
 e}this.lastParentSize=C;if(!B){return }var E,J,I,H;if(this.options.left!=null){E=this.options.left;if(this.options.right==null){if(this.options.width==null){I=C.width-E;if(I<this.options.minWidth){I=this.options.minWidth}if(this.options.maxWidth>=0&&I>this.options.maxWidth){I=this.options.maxWidth}}else{I=this.options.width}}else{if(this.options.width==null){I=C.width-E-this.options.right;if(I<this.options.minWidth){I=this.options.minWidth}if(this.options.maxWidth>=0&&I>this.options.maxWidth){I=this.options.maxWidth}}else{I=this.options.width}}}else{if(this.options.right==null){if(this.options.width==null){E=0;I=C.width;if(this.options.maxWidth>=0&&I>this.options.maxWidth){E=E+parseInt(I-this.options.maxWidth,10)/2;I=this.options.maxWidth}}else{I=this.options.width;E=parseInt((C.width-I)/2,10);if(E<0){E=0}}}else{if(this.options.width!=null){I=this.options.width;E=C.width-I-this.options.right;if(E<0){E=0}}else{E=0;I=C.width-this.options.right;if(I<this.options.minWidth){I=thi
 s.options.minWidth}if(this.options.maxWidth>=0&&I>this.options.maxWidth){E=I-this.options.maxWidth-this.options.right;I=this.options.maxWidth}}}}if(this.options.top!=null){J=this.options.top;if(this.options.bottom==null){if(this.options.height==null){H=C.height-J;if(H<this.options.minHeight){H=this.options.minHeight}if(this.options.maxHeight>=0&&H>this.options.maxHeight){H=this.options.maxHeight}}else{H=this.options.height;if(this.options.maxHeight>=0&&H>this.options.maxHeight){J=H-this.options.maxHeight;H=this.options.maxHeight}}}else{if(this.options.height==null){H=C.height-J-this.options.bottom;if(H<this.options.minHeight){H=this.options.minHeight}if(this.options.maxHeight>=0&&H>this.options.maxHeight){H=this.options.maxHeight}}else{H=this.options.height}}}else{if(this.options.bottom==null){if(this.options.height==null){J=0;H=C.height;if(H<this.options.minHeight){H=this.options.minHeight}if(this.options.maxHeight>=0&&H>this.options.maxHeight){J=parseInt((C.height-this.opt
 ions.maxHeight)/2,10);H=this.options.maxHeight}}else{H=this.options.height;J=parseInt((C.height-H)/2,10);if(J<0){J=0}}}else{if(this.options.height!=null){H=this.options.height;J=C.height-H-this.options.bottom;if(J<0){J=0}}else{J=0;H=C.height-this.options.bottom;if(H<this.options.minHeight){H=this.options.minHeight}if(this.options.maxHeight>=0&&H>this.options.maxHeight){J=C.height-this.options.maxHeight-this.options.bottom;H=this.options.maxHeight}}}}var F={width:I};if(this.options.position=="absolute"){var D=document.id(this.domObj.parentNode).measure(function(){return this.getSizes(["padding"],["left","top"]).padding});this.domObj.setStyles({position:this.options.position,left:E+D.left,top:J+D.top});F.height=H}else{if(this.options.height){F.height=this.options.height}}this.domObj.setBorderBoxSize(F);if(this.options.propagate){var A={forceResize:K?K.forceResize:false};$A(this.domObj.childNodes).each(function(L){if(L.resize&&L.getStyle("display")!="none"){L.resize.delay(0,L,A
 )}})}this.fireEvent("sizeChange",this)}});Jx.Toolbar=new Class({Family:"Jx.Toolbar",Extends:Jx.Widget,list:null,domObj:null,isActive:false,options:{position:"top",parent:null,autoSize:false,align:"left",scroll:true,template:'<ul class="jxToolbar"></ul>'},classes:new Hash({domObj:"jxToolbar"}),render:function(){this.parent();this.domObj.store("jxToolbar",this);if($defined(this.options.id)){this.domObj.id=this.options.id}this.list=new Jx.List(this.domObj,{onAdd:function(A){this.fireEvent("add",this)}.bind(this),onRemove:function(A){this.fireEvent("remove",this)}.bind(this)});if(this.options.parent){this.addTo(this.options.parent)}this.deactivateWatcher=this.deactivate.bindWithEvent(this);if(this.options.items){this.add(this.options.items)}},addTo:function(A){var B=document.id(A).retrieve("jxBarContainer");if(!B){B=new Jx.Toolbar.Container({parent:A,position:this.options.position,autoSize:this.options.autoSize,align:this.options.align,scroll:this.options.scroll})}B.add(this);re
 turn this},add:function(){$A(arguments).flatten().each(function(A){var B=A;if(B.domObj){B=B.domObj}if(B.tagName=="LI"){if(!B.hasClass("jxToolItem")){B.addClass("jxToolItem")}}else{B=new Jx.Toolbar.Item(A)}this.list.add(B)},this);this.update();return this},remove:function(B){if(B.domObj){B=B.domObj}var A=B.findElement("LI");this.list.remove(A);this.update();return this},empty:function(){this.list.each(function(A){this.remove(A)},this)},deactivate:function(){this.list.each(function(A){if(A.retrieve("jxMenu")){A.retrieve("jxMenu").hide()}});this.setActive(false)},isActive:function(){return this.isActive},setActive:function(A){this.isActive=A;if(this.isActive){document.addEvent("click",this.deactivateWatcher)}else{document.removeEvent("click",this.deactivateWatcher)}},setVisibleItem:function(A){if(this.visibleItem&&this.visibleItem.hide&&this.visibleItem!=A){this.visibleItem.hide()}this.visibleItem=A;if(this.isActive()){this.visibleItem.show()}},showItem:function(A){this.fireEve
 nt("show",A)},update:function(){this.fireEvent("update")},changeText:function(A){this.update()}});Jx.Toolbar.Container=new Class({Family:"Jx.Toolbar.Container",Extends:Jx.Widget,Binds:["update"],pluginNamespace:"ToolbarContainer",domObj:null,options:{parent:null,position:"top",autoSize:false,scroll:true,align:"left",template:"<div class='jxBarContainer'><div class='jxBarControls'></div></div>",scrollerTemplate:"<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"},classes:new Hash({domObj:"jxBarContainer",scroller:"jxBarScroller",wrapper:"jxBarWrapper",controls:"jxBarControls"}),updating:false,render:function(){this.parent();if(document.id(this.options.parent)){this.domObj=document.id(this.options.parent);this.elements=new Hash({jxBarContainer:this.domObj});this.domObj.addClass("jxBarContainer");this.domObj.grab(this.controls);this.domObj.addEvent("sizeChange",this.update)}if(!["center","right"].contains(this.options.align)&&this.options.scroll){this.processEle
 ments(this.options.scrollerTemplate,this.classes);this.domObj.grab(this.scroller,"top")}this.domObj.addClass("jxToolbarAlign"+this.options.align.capitalize());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)){this.addEvent("addTo",function(){this.domObj.getParent().addEvent("sizeChange",this.update);this.update()});this.scrollLeft=new Jx.Button({image:Jx.aPixel.src}).addTo(this.controls,"bottom");document.id(this.scrollLeft).addClass("jxBarScrollLeft");this.scrollLeft.addEvents({click:this.scroll.bind(this,"left")});this.scrollRight=new Jx.Button({image:Jx.aPixel.src}).addTo(this.controls,"bottom");document.id(this.scrollRight).addClass("jxBarScrollRight");this.scrollRight.addEvents({click:this.scroll.bind(this,"rig
 ht")})}else{if(this.options.scroll&&["left","right"].contains(this.options.position)){this.options.scroll=false}else{this.options.scroll=false}}this.addEvent("add",this.update);if(this.options.toolbars){this.add(this.options.toolbars)}},update:function(){if(this.options.scroll){if(["top","bottom"].contains(this.options.position)){var A=this.domObj.getContentBoxSize().width;var D=0;var B=this.wrapper.getChildren();if(B.length>0){B.each(function(F){D+=F.getMarginBoxSize().width},this);var E=A;if(D===0){this.scrollLeft.setEnabled(false);this.scrollRight.setEnabled(false)}else{var C=this.wrapper.getStyle("margin-left").toInt();E-=this.controls.getMarginBoxSize().width;if(C<0){this.scrollLeft.setEnabled(true)}else{this.scrollLeft.setEnabled(false)}if(D+C>E){this.scrollRight.setEnabled(true)}else{this.scrollRight.setEnabled(false)}}}else{this.scrollRight.setEnabled(false);this.scrollLeft.setEnabled(false)}this.scroller.setStyle("width",E);this.findFirstVisible();this.updating=fals
 e}}},findFirstVisible:function(){if($defined(this.scroller.retrieve("buttonPointer"))){return }var A=this.wrapper.getChildren();if(A.length>0){A.each(function(C){var B=C.getChildren();if(B.length>1){B.each(function(D){var E=D.getCoordinates(this.scroller);if(E.left>=0&&!$defined(this.scroller.retrieve("buttonPointer"))){this.scroller.store("buttonPointer",D)}},this)}},this)}},add:function(){$A(arguments).flatten().each(function(A){if(this.options.scroll){A.addEvent("update",this.update.bind(this));A.addEvent("show",this.scrollIntoView.bind(this))}if(this.wrapper){this.wrapper.adopt(A.domObj)}else{this.domObj.adopt(A.domObj)}this.domObj.addClass("jxBar"+this.options.position.capitalize())},this);if(arguments.length>0){this.fireEvent("add",this)}return this},scroll:function(D){if(this.updating){return }this.updating=true;var C=this.scroller.retrieve("buttonPointer");if(D==="left"){var A=this.scroller.retrieve("previousPointer");if(!A){A=this.getPreviousButton(C)}if(A){var B=A.
 getMarginBoxSize().width;var E=this.wrapper.getStyle("margin-left").toInt();E+=B;if(typeof Fx!="undefined"&&typeof Fx.Tween!="undefined"){this.wrapper.get("tween",{property:"margin-left",onComplete:this.afterTweenLeft.bind(this,A)}).start(E)}else{this.wrapper.setStyle("margin-left",E);this.afterTweenLeft(A)}}else{this.update()}}else{var B=C.getMarginBoxSize().width;var E=this.wrapper.getStyle("margin-left").toInt();E-=B;if(typeof Fx!="undefined"&&typeof Fx.Tween!="undefined"){this.wrapper.get("tween",{property:"margin-left",onComplete:this.afterTweenRight.bind(this,C)}).start(E)}else{this.wrapper.setStyle("margin-left",E);this.afterTweenRight(C)}}},afterTweenRight:function(A){var B=this.getNextButton(A);if(!B){B=A}this.scroller.store("buttonPointer",B);if(B!==A){this.scroller.store("previousPointer",A)}this.update()},afterTweenLeft:function(A){this.scroller.store("buttonPointer",A);var B=this.getPreviousButton(A);if($defined(B)){this.scroller.store("previousPointer",B)}else{
 this.scroller.eliminate("previousPointer")}this.update()},remove:function(A){if(A instanceof Jx.Widget){A.dispose()}else{document.id(A).dispose()}this.update()},scrollIntoView:function(C){var B=this.scroller.retrieve("buttonPointer");if(!$defined(B)){return }if($defined(C.domObj)){C=C.domObj;while(!C.hasClass("jxToolItem")){C=C.getParent()}}var H=C.getCoordinates(this.scroller);var F=this.scroller.getStyle("width").toInt();if(H.right>0&&H.right<=F&&H.left>0&&H.left<=F){return }if(H.right>F){var E=H.right-F;var G=this.wrapper.getStyle("margin-left").toInt();var A=B.getMarginBoxSize().width;var D;while(A<E&&$defined(B)){D=this.getNextButton(B);if(D){A+=D.getMarginBoxSize().width}else{break}B=D}G-=A;if(typeof Fx!="undefined"&&typeof Fx.Tween!="undefined"){this.wrapper.get("tween",{property:"margin-left",onComplete:this.afterTweenRight.bind(this,B)}).start(G)}else{this.wrapper.setStyle("margin-left",G);this.afterTweenRight(B)}}else{var G=this.wrapper.getStyle("margin-left").toIn
 t();G-=H.left;if(typeof Fx!="undefined"&&typeof Fx.Tween!="undefined"){this.wrapper.get("tween",{property:"margin-left",onComplete:this.afterTweenLeft.bind(this,C)}).start(G)}else{this.wrapper.setStyle("margin-left",G);this.afterTweenLeft(C)}}},getPreviousButton:function(A){pp=A.getPrevious();if(!$defined(pp)){pp=A.getParent().getPrevious();if(pp){pp=pp.getLast()}}return pp},getNextButton:function(A){np=A.getNext();if(!np){np=A.getParent().getNext();if(np){np=np.getFirst()}}return np}});Jx.Toolbar.Item=new Class({Family:"Jx.Toolbar.Item",Extends:Jx.Widget,options:{active:true,template:'<li class="jxToolItem"></li>'},classes:new Hash({domObj:"jxToolItem"}),parameters:["jxThing","options"],render:function(){this.parent();var A=document.id(this.options.jxThing);if(A){this.domObj.adopt(A)}}});Jx.Panel=new Class({Family:"Jx.Panel",Extends:Jx.Widget,toolbarContainers:{top:null,right:null,bottom:null,left:null},options:{position:null,collapsedClass:"jxPanelMin",collapseClass:"jxPan
 elCollapse",menuClass:"jxPanelMenu",maximizeClass:"jxPanelMaximize",closeClass:"jxPanelClose",label:"&nbsp;",height:null,collapse:true,close:false,closed:false,hideTitle:false,toolbars:[],type:"panel",template:'<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',controlButtonTemplate:'<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'},classes:new Hash({domObj:"jxPanel",title:"jxPanelTitle",domImg:"jxPanelIcon",domLabel:"jxPanelLabel",domControls:"jxPanelControls",contentContainer:"jxPanelContentContainer",content:"jxPanelContent"}),render:function(){this.parent();this.toolbars=this.options?this.options.toolbars||[]:[];this.options.position=($defined(this.options.height)&&!$defined(this.options.position))?"relative"
 :"absolute";if(this.options.image&&this.domImg){this.domImg.setStyle("backgroundImage","url("+this.options.image+")")}if(this.options.label&&this.domLabel){this.setLabel(this.options.label)}var A=new Element("div");this.domControls.adopt(A);this.toolbar=new Jx.Toolbar({parent:A,scroll:false});var E=this;if(this.options.menu){this.menu=new Jx.Menu({image:Jx.aPixel.src},{buttonTemplate:this.options.controlButtonTemplate});this.menu.domObj.addClass(this.options.menuClass);this.menu.domObj.addClass("jxButtonContentLeft");this.toolbar.add(this.menu)}if(this.options.collapse){if(this.title){this.title.addEvent("dblclick",function(){E.toggleCollapse()})}this.colB=new Jx.Button({template:this.options.controlButtonTemplate,image:Jx.aPixel.src,tooltip:{set:"Jx",key:"panel",value:"collapseTooltip"},onClick:function(){E.toggleCollapse()}});this.colB.domObj.addClass(this.options.collapseClass);this.addEvents({collapse:function(){this.colB.setTooltip({set:"Jx",key:"panel",value:"expandToo
 ltip"})}.bind(this),expand:function(){this.colB.setTooltip({set:"Jx",key:"panel",value:"collapseTooltip"})}.bind(this)});this.toolbar.add(this.colB);if(this.menu){this.colM=new Jx.Menu.Item({label:this.options.collapseLabel,onClick:function(){E.toggleCollapse()}});var D=this.colM;this.addEvents({collapse:function(){this.colM.setLabel({set:"Jx",key:"panel",value:"expandLabel"})}.bind(this),expand:function(){this.colM.setLabel({set:"Jx",key:"panel",value:"collapseLabel"})}.bind(this)});this.menu.add(D)}}if(this.options.maximize){this.maxB=new Jx.Button({template:this.options.controlButtonTemplate,image:Jx.aPixel.src,tooltip:{set:"Jx",key:"panel",value:"maximizeTooltip"},onClick:function(){E.maximize()}});this.maxB.domObj.addClass(this.options.maximizeClass);this.addEvents({maximize:function(){this.maxB.setTooltip({set:"Jx",key:"panel",value:"restoreTooltip"})}.bind(this),restore:function(){this.maxB.setTooltip({set:"Jx",key:"panel",value:"maximizeTooltip"})}.bind(this)});this.
 toolbar.add(this.maxB);if(this.menu){this.maxM=new Jx.Menu.Item({label:this.options.maximizeLabel,onClick:function(){E.maximize()}});this.addEvents({maximize:function(){this.maxM.setLabel({set:"Jx",key:"panel",value:"maximizeLabel"})}.bind(this),restore:function(){this.maxM.setLabel({set:"Jx",key:"panel",value:"restoreLabel"})}.bind(this)});this.menu.add(this.maxM)}}if(this.options.close){this.closeB=new Jx.Button({template:this.options.controlButtonTemplate,image:Jx.aPixel.src,tooltip:{set:"Jx",key:"panel",value:"closeTooltip"},onClick:function(){E.close()}});this.closeB.domObj.addClass(this.options.closeClass);this.toolbar.add(this.closeB);if(this.menu){this.closeM=new Jx.Menu.Item({label:{set:"Jx",key:"panel",value:"closeLabel"},onClick:function(){E.close()}});this.menu.add(D)}}if(this.options.id){this.domObj.id=this.options.id}var C=new Jx.Layout(this.domObj,$merge(this.options,{propagate:false}));var B=this.layoutContent.bind(this);C.addEvent("sizeChange",B);if(this.opt
 ions.hideTitle){this.title.dispose()}if(Jx.type(this.options.toolbars)=="array"){this.options.toolbars.each(function(G){var F=G.options.position;var H=this.toolbarContainers[F];if(!H){H=new Element("div");new Jx.Layout(H);this.contentContainer.adopt(H);this.toolbarContainers[F]=H}G.addTo(H)},this)}new Jx.Layout(this.contentContainer);new Jx.Layout(this.content);if(this.shouldLoadContent()){this.loadContent(this.content)}this.toggleCollapse(this.options.closed);this.addEvent("addTo",function(){this.domObj.resize()});if(this.options.parent){this.addTo(this.options.parent)}},layoutContent:function(){var F=0;var G=0;var A=0;var C=0;var I=0;var H;var D;var E;if(!this.options.hideTitle&&this.title.parentNode==this.domObj){F=this.title.getMarginBoxSize().height}var B=this.domObj.getContentBoxSize();if(B.height>F){this.contentContainer.setStyle("display","block");this.options.closed=false;this.contentContainer.resize({top:F,height:null,bottom:0});["left","right"].each(function(J){if
 (this.toolbarContainers[J]){this.toolbarContainers[J].style.width="auto"}},this);["top","bottom"].each(function(J){if(this.toolbarContainers[J]){this.toolbarContainers[J].style.height=""}},this);if(Jx.type(this.options.toolbars)=="array"){this.options.toolbars.each(function(J){J.update();E=J.options.position;H=this.toolbarContainers[E];if(Browser.Engine.trident4){var L=document.id(H.parentNode);H.style.visibility="hidden";document.id(document.body).adopt(H)}var K=H.getBorderBoxSize();if(Browser.Engine.trident4){L.adopt(H);H.style.visibility=""}switch(E){case"bottom":A=K.height;break;case"left":C=K.width;break;case"right":I=K.width;break;case"top":default:G=K.height;break}},this)}H=this.toolbarContainers.top;if(H){H.resize({top:0,left:C,right:I,bottom:null,height:G,width:null})}H=this.toolbarContainers.bottom;if(H){H.resize({top:null,left:C,right:I,bottom:0,height:A,width:null})}H=this.toolbarContainers.left;if(H){H.resize({top:G,left:0,right:null,bottom:A,height:null,width:C
 })}H=this.toolbarContainers.right;if(H){H.resize({top:G,left:null,right:0,bottom:A,height:null,width:I})}this.content.resize({top:G,bottom:A,left:C,right:I})}else{this.contentContainer.setStyle("display","none");this.options.closed=true}this.fireEvent("sizeChange",this)},setLabel:function(A){this.domLabel.set("html",this.getText(A))},getLabel:function(){return this.domLabel.get("html")},finalize:function(){this.domObj=null;this.deregisterIds()},maximize:function(){if(this.manager){this.manager.maximizePanel(this)}},setContent:function(A){this.content.innerHTML=A;this.bContentReady=true},setContentURL:function(B){this.bContentReady=false;this.setBusy(true);if(arguments[1]){this.onContentReady=arguments[1]}if(B.indexOf("?")==-1){B=B+"?"}var A=new Request({url:B,method:"get",evalScripts:true,onSuccess:this.panelContentLoaded.bind(this),requestHeaders:["If-Modified-Since","Sat, 1 Jan 2000 00:00:00 GMT"]}).send()},panelContentLoaded:function(A){this.content.innerHTML=A;this.bCont
 entReady=true;this.setBusy(false);if(this.onContentReady){window.setTimeout(this.onContentReady.bind(this),1)}},toggleCollapse:function(C){if($defined(C)){this.options.closed=C}else{this.options.closed=!this.options.closed}if(this.options.closed){if(!this.domObj.hasClass(this.options.collapsedClass)){this.domObj.addClass(this.options.collapsedClass);this.contentContainer.setStyle("display","none");var B=this.domObj.measure(function(){return this.getSizes(["margin"],["top","bottom"]).margin});var A=B.top+B.bottom;if(this.title.parentNode==this.domObj){A+=this.title.getMarginBoxSize().height}this.domObj.resize({height:A});this.fireEvent("collapse",this)}}else{if(this.domObj.hasClass(this.options.collapsedClass)){this.domObj.removeClass(this.options.collapsedClass);this.contentContainer.setStyle("display","block");this.domObj.resize({height:this.options.height});this.fireEvent("expand",this)}}},close:function(){this.domObj.dispose();this.fireEvent("close",this)},changeText:func
 tion(A){this.parent();if($defined(this.closeB)){this.closeB.setTooltip({set:"Jx",key:"panel",value:"closeTooltip"})}if($defined(this.closeM)){this.closeM.setLabel({set:"Jx",key:"panel",value:"closeLabel"})}if($defined(this.maxB)){this.maxB.setTooltip({set:"Jx",key:"panel",value:"maximizeTooltip"})}if($defined(this.colB)){this.colB.setTooltip({set:"Jx",key:"panel",value:"collapseTooltip"})}if($defined(this.colM)){if(this.options.closed==true){this.colM.setLabel({set:"Jx",key:"panel",value:"expandLabel"})}else{this.colM.setLabel({set:"Jx",key:"panel",value:"collapseLabel"})}}if(this.options.label&&this.domLabel){this.setLabel(this.options.label)}this.layoutContent()},shouldLoadContent:function(){return true}});Jx.Dialog=new Class({Family:"Jx.Dialog",Extends:Jx.Panel,options:{modal:true,maskOptions:{"class":"jxModalMask",maskMargins:true,useIframeShim:true,iframeShimOptions:{className:"jxIframeShim"}},eventMaskOptions:{"class":"jxEventMask",maskMargins:false,useIframeShim:false
 ,destroyOnHide:true},position:"absolute",width:250,height:250,horizontal:"center center",vertical:"center center",label:"",resize:false,move:true,limit:false,close:true,useKeyboard:false,keys:{esc:"close"},keyboardMethods:{},collapsedClass:"jxDialogMin",collapseClass:"jxDialogCollapse",menuClass:"jxDialogMenu",maximizeClass:"jxDialogMaximize",closeClass:"jxDialogClose",type:"dialog",template:'<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'},classes:new Hash({domObj:"jxDialog",title:"jxDialogTitle",domImg:"jxDialogIcon",domLabel:"jxDialogLabel",domControls:"jxDialogControls",contentContainer:"jxDialogContentContainer",content:"jxDialogContent"}),keyboard:null,render:function(){this.isOpening=false;this.firstShow=true;this.options=$merge({parent:doc
 ument.body},this.options,{position:"absolute"});this.parent();this.openOnLoaded=this.open.bind(this);this.options.parent=document.id(this.options.parent);this.domObj.setStyle("display","none");this.options.parent.adopt(this.domObj);if(this.options.move&&typeof Drag!="undefined"){this.title.addClass("jxDialogMoveable");this.options.limit=this.setDragLimit(this.options.limit);var A=this;new Drag(this.domObj,{handle:this.title,limit:this.options.limit,onBeforeStart:(function(){this.stack()}).bind(this),onStart:function(){if(!A.options.modal&&A.options.parent.mask){A.options.parent.mask(A.options.eventMaskOptions)}A.contentContainer.setStyle("visibility","hidden");A.chrome.addClass("jxChromeDrag");if(A.options.limit){var C=A.options.limitOrig.getCoordinates();for(var B in C){window.console?console.log(B,C[B]):false}this.options.limit=A.setDragLimit(A.options.limitOrig)}},onDrag:function(){if(this.options.limit){if(this.value.now.x+A.options.width>=this.options.limit.x[1]){this.v
 alue.now.x=this.options.limit.x[1]-A.options.width;this.element.setStyle("left",this.value.now.x)}if(this.value.now.y+A.options.height>=this.options.limit.y[1]){this.value.now.y=this.options.limit.y[1]-A.options.height;this.element.setStyle("top",this.value.now.y)}}},onComplete:(function(){if(!this.options.modal&&this.options.parent.unmask){this.options.parent.unmask()}this.chrome.removeClass("jxChromeDrag");this.contentContainer.setStyle("visibility","");var C=Math.max(this.chromeOffsets.left,parseInt(this.domObj.style.left,10));var B=Math.max(this.chromeOffsets.top,parseInt(this.domObj.style.top,10));this.options.horizontal=C+" left";this.options.vertical=B+" top";this.position(this.domObj,this.options.parent,this.options);this.options.left=parseInt(this.domObj.style.left,10);this.options.top=parseInt(this.domObj.style.top,10);if(!this.options.closed){this.domObj.resize(this.options)}}).bind(this)})}if(this.options.resize&&typeof Drag!="undefined"){this.resizeHandle=new El
 ement("div",{"class":"jxDialogResize",title:this.getText({set:"Jx",key:"panel",value:"resizeTooltip"}),styles:{display:this.options.closed?"none":"block"}});this.domObj.appendChild(this.resizeHandle);this.resizeHandleSize=this.resizeHandle.getSize();this.resizeHandle.setStyles({bottom:this.resizeHandleSize.height,right:this.resizeHandleSize.width});this.domObj.makeResizable({handle:this.resizeHandle,onStart:(function(){if(!this.options.modal&&this.options.parent.mask){this.options.parent.mask(this.options.eventMaskOptions)}this.contentContainer.setStyle("visibility","hidden");this.chrome.addClass("jxChromeDrag")}).bind(this),onDrag:(function(){this.resizeChrome(this.domObj)}).bind(this),onComplete:(function(){if(!this.options.modal&&this.options.parent.unmask){this.options.parent.unmask()}this.chrome.removeClass("jxChromeDrag");var B=this.domObj.getMarginBoxSize();this.options.width=B.width;this.options.height=B.height;this.layoutContent();this.domObj.resize(this.options);th
 is.contentContainer.setStyle("visibility","");this.fireEvent("resize");this.resizeChrome(this.domObj)}).bind(this)})}this.domObj.addEvent("mousedown",(function(){this.stack()}).bind(this));this.initializeKeyboard()},resize:function(B,A,C){this.options.width=B;this.options.height=A;if(this.domObj.getStyle("display")!="none"){this.layoutContent();this.domObj.resize(this.options);this.fireEvent("resize");this.resizeChrome(this.domObj);if(C){this.position(this.domObj,this.options.parent,this.options)}}else{this.firstShow=false}},sizeChanged:function(){if(!this.options.closed){this.layoutContent()}},toggleCollapse:function(C){if($defined(C)){this.options.closed=C}else{this.options.closed=!this.options.closed}if(this.options.closed){if(!this.domObj.hasClass(this.options.collapsedClass)){this.domObj.addClass(this.options.collapsedClass)}this.contentContainer.setStyle("display","none");if(this.resizeHandle){this.resizeHandle.setStyle("display","none")}}else{if(this.domObj.hasClass(t
 his.options.collapsedClass)){this.domObj.removeClass(this.options.collapsedClass)}this.contentContainer.setStyle("display","block");if(this.resizeHandle){this.resizeHandle.setStyle("display","block")}}if(this.options.closed){var A=this.domObj.measure(function(){return this.getSizes(["margin"],["top","bottom"]).margin});var B=this.title.getMarginBoxSize();this.domObj.resize({height:A.top+B.height+A.bottom});this.fireEvent("collapse")}else{this.domObj.resize(this.options);this.fireEvent("expand")}this.showChrome(this.domObj)},maximize:function(){if(!this.maximized){var B=this.options.parent;var A;if(B===document.body){A=Jx.getPageDimensions()}else{A=B.getBorderBoxSize()}this.previousSettings={width:this.options.width,height:this.options.height,horizontal:this.options.horizontal,vertical:this.options.vertical,left:this.options.left,right:this.options.right,top:this.options.top,bottom:this.options.bottom};this.options.width=A.width;this.options.height=A.height;this.options.verti
 cal="0 top";this.options.horizontal="0 left";this.options.right=0;this.options.left=0;this.options.top=0;this.options.bottom=0;this.domObj.resize(this.options);this.fireEvent("resize");this.resizeChrome(this.domObj);this.maximized=true;this.domObj.addClass("jxDialogMaximized");this.fireEvent("maximize")}else{this.options=$merge(this.options,this.previousSettings);this.domObj.resize(this.options);this.fireEvent("resize");this.resizeChrome(this.domObj);this.maximized=false;if(this.domObj.hasClass("jxDialogMaximized")){this.domObj.removeClass("jxDialogMaximized")}this.fireEvent("restore")}},show:function(){this.domObj.setStyles({display:"block",visibility:"hidden"});this.toolbar.update();if(this.options.modal&&this.options.parent.mask){var C=$merge(this.options.maskOptions||{},{style:{"z-index":Jx.getNumber(this.domObj.getStyle("z-index"))-1}});this.options.parent.mask(C);Jx.Stack.stack(this.options.parent.get("mask").element)}this.stack();if(this.options.closed){var A=this.dom
 Obj.measure(function(){return this.getSizes(["margin"],["top","bottom"]).margin});var B=this.title.getMarginBoxSize();this.domObj.resize({height:A.top+B.height+A.bottom})}else{this.domObj.resize(this.options)}if(this.firstShow){this.contentContainer.resize({forceResize:true});this.layoutContent();this.firstShow=false;if(this.chrome){this.chrome.dispose();this.chrome=null}}this.showChrome(this.domObj);this.position(this.domObj,this.options.parent,this.options);this.domObj.setStyle("visibility","visible")},hide:function(){this.domObj.setStyle("display","none");this.unstack();if(this.options.modal&&this.options.parent.unmask){Jx.Stack.unstack(this.options.parent.get("mask").element);this.options.parent.unmask()}if(this.options.useKeyboard&&this.keyboard!=null){this.keyboard.deactivate()}},openURL:function(A){if(A){this.options.contentURL=A;this.options.content=null;this.setBusy();this.loadContent(this.content);this.addEvent("contentLoaded",this.openOnLoaded)}else{this.open()}},
 open:function(){if(!this.isOpening){this.isOpening=true}if(!this.contentIsLoaded&&this.options.loadOnDemand){this.loadContent(this.content)}if(this.contentIsLoaded){this.removeEvent("contentLoaded",this.openOnLoaded);this.show();this.fireEvent("open",this);this.isOpening=false}else{this.addEvent("contentLoaded",this.openOnLoaded)}if(this.options.useKeyboard&&this.keyboard!=null){this.keyboard.activate()}},close:function(){this.isOpening=false;this.hide();this.fireEvent("close")},cleanup:function(){},isOpen:function(){return !((this.domObj.getStyle("display")==="none")||(this.domObj.getStyle("visibility")==="hidden"))},changeText:function(A){this.parent();if($defined(this.maxM)){if(this.maximize){this.maxM.setLabel(this.getText({set:"Jx",key:"panel",value:"restoreLabel"}))}else{this.maxM.setLabel(this.getText({set:"Jx",key:"panel",value:"maximizeLabel"}))}}if($defined(this.resizeHandle)){this.resizeHandle.set("title",this.getText({set:"Jx",key:"dialog",value:"resizeTooltip"})
 )}this.toggleCollapse(false)},initializeKeyboard:function(){if(this.options.useKeyboard){var A=this;this.keyboardEvents={};this.keyboardMethods={close:function(B){B.preventDefault();A.close()}};this.keyboard=new Keyboard({events:this.getKeyboardEvents()})}},getKeyboardEvents:function(){var A=this;for(var B in this.options.keys){if(!$defined(this.keyboardEvents[B])){if($defined(this.keyboardMethods[this.options.keys[B]])){this.keyboardEvents[B]=this.keyboardMethods[this.options.keys[B]]}else{if($defined(this.options.keyboardMethods[this.options.keys[B]])){this.keyboardEvents[B]=this.options.keyboardMethods[this.options.keys[B]].bind(A)}else{if(Jx.type(this.options.keys[B])=="function"){this.keyboardEvents[B]=this.options.keys[B].bind(A)}else{if(this.options.keyboardMethods[this.options.keys[B]]!=false){$defined(console)?console.warn("keyboard method %o not defined for %o",this.options.keys[B],this):false}}}}}}return this.keyboardEvents},setDragLimit:function(A){if($defined(A)
 ){this.options.limit=A}var C=this.options.limit!=null?Jx.type(this.options.limit):false;if(this.options.limit&&C!="object"){var B=false;switch(C){case"string":if(document.id(this.options.limit)){B=document.id(this.options.limit).getCoordinates()}break;case"element":case"document":case"window":B=this.options.limit.getCoordinates();break}if(B){this.options.limitOrig=this.options.limit;this.options.limit={x:[B.left,B.right],y:[B.top,B.bottom]}}else{this.options.limit=false}}return this.options.limit},shouldLoadContent:function(){return !this.options.loadOnDemand}});Jx.Splitter=new Class({Family:"Jx.Splitter",Extends:Jx.Object,domObj:null,elements:null,bars:null,firstUpdate:true,options:{useChildren:false,splitInto:2,elements:null,containerOptions:[],barOptions:[],layout:"horizontal",snaps:[],onStart:null,onFinish:null},parameters:["domObj","options"],init:function(){this.domObj=document.id(this.options.domObj);this.domObj.addClass("jxSplitContainer");var J=this.domObj.retrieve(
 "jxLayout");if(J){J.addEvent("sizeChange",this.sizeChanged.bind(this))}this.elements=[];this.bars=[];var E;var I=2;if(this.options.useChildren){this.elements=this.domObj.getChildren();I=this.elements.length}else{I=this.options.elements?this.options.elements.length:this.options.splitInto;for(E=0;E<I;E++){var C;if(this.options.elements&&this.options.elements[E]){if(this.options.elements[E].domObj){C=this.options.elements[E].domObj}else{C=document.id(this.options.elements[E])}if(!C){C=this.prepareElement();C.id=this.options.elements[E]}}else{C=this.prepareElement()}this.elements[E]=C;this.domObj.adopt(this.elements[E])}}this.elements.each(function(K){K.addClass("jxSplitArea")});for(E=0;E<I;E++){var H=this.elements[E].retrieve("jxLayout");if(!H){new Jx.Layout(this.elements[E],this.options.containerOptions[E])}else{if(this.options.containerOptions[E]){H.resize($merge(this.options.containerOptions[E],{position:"absolute"}))}else{H.resize({position:"absolute"})}}}for(E=1;E<I;E++){v
 ar G;if(this.options.prepareBar){G=this.options.prepareBar(E-1)}else{G=this.prepareBar()}G.store("splitterObj",this);G.store("leftSide",this.elements[E-1]);G.store("rightSide",this.elements[E]);this.elements[E-1].store("rightBar",G);this.elements[E].store("leftBar",G);this.domObj.adopt(G);this.bars[E-1]=G}if($defined(Drag)){this.establishConstraints()}for(E=0;E<this.options.barOptions.length;E++){if(!this.bars[E]){continue}var A=this.options.barOptions[E];if(A&&A.snap&&(A.snap=="before"||A.snap=="after")){var F;if(A.snap=="before"){F=this.bars[E].retrieve("leftSide")}else{if(A.snap=="after"){F=this.bars[E].retrieve("rightSide")}}var D;var B;if(A.snapElement){D=A.snapElement;B=A.snapEvents||["click","dblclick"]}else{D=this.bars[E];B=A.snapEvents||["dblclick"]}if(!D.parentNode){this.bars[E].adopt(D)}new Jx.Splitter.Snap(D,F,this,B)}}for(E=0;E<this.options.snaps.length;E++){if(this.options.snaps[E]){new Jx.Splitter.Snap(this.options.snaps[E],this.elements[E],this)}}this.sizeCha
 nged()},prepareElement:function(){var A=new Element("div",{styles:{position:"absolute"}});return A},prepareBar:function(){var A=new Element("div",{"class":"jxSplitBar"+this.options.layout.capitalize(),title:this.getText({set:"Jx",key:"splitter",value:"barToolTip"})});return A},establishConstraints:function(){var A={x:null,y:null};var B;if(this.options.layout=="horizontal"){A.x="left";B=this.dragHorizontal}else{A.y="top";B=this.dragVertical}if(typeof Drag!="undefined"){this.bars.each(function(D){var C;new Drag(D,{modifiers:A,onSnap:(function(E){E.addClass("jxSplitBarDrag");this.fireEvent("snap",[E])}).bind(this),onCancel:(function(E){C.destroy();this.fireEvent("cancel",[E])}).bind(this),onDrag:(function(F,E){this.fireEvent("drag",[F,E])}).bind(this),onComplete:(function(E){C.destroy();E.removeClass("jxSplitBarDrag");if(E.retrieve("splitterObj")!=this){return }B.apply(this,[E]);this.fireEvent("complete",[E]);this.fireEvent("finish",[E])}).bind(this),onBeforeStart:(function(E){
 this.fireEvent("beforeStart",[E]);C=new Element("div",{"class":"jxSplitterMask"}).inject(E,"after")}).bind(this),onStart:(function(F,E){this.fireEvent("start",[F,E])}).bind(this)})},this)}},dragHorizontal:function(F){var A=parseInt(F.style.left,10);var H=F.retrieve("leftSide");var C=F.retrieve("rightSide");var D=H.retrieve("jxLayout");var G=C.retrieve("jxLayout");var E=this.domObj.measure(function(){var P=this.getSizes(["padding"],["left"]);return P.padding.left});var I,O,K;var N=F.retrieve("size");if(!N){N=F.getBorderBoxSize();F.store("size",N)}I=A+N.width-E;var B=this.domObj.getContentBoxSize();if(G.options.width!=null){O=G.options.width+G.options.left-I;K=B.width-I-O}else{O=B.width-G.options.right-I;K=G.options.right}if(O<0){O=0}if(O<G.options.minWidth){O=G.options.minWidth}if(G.options.maxWidth>=0&&O>G.options.maxWidth){O=G.options.maxWidth}I=B.width-K-O;A=I-N.width;var J,M;J=D.options.left;M=A-J;if(M<0){M=0}if(M<D.options.minWidth){M=D.options.minWidth}if(D.options.maxW
 idth>=0&&M>D.options.maxWidth){M=D.options.maxWidth}if(J+M!=A){A=J+M;var L=A+N.width-I;I+=L;O-=L}F.style.left=E+A+"px";if(D.options.width==null){B=this.domObj.getContentBoxSize();H.resize({right:B.width-J-M})}else{H.resize({width:M})}if(G.options.width==null){C.resize({left:I})}else{C.resize({left:I,width:O})}},dragVertical:function(F){var J=parseInt(F.style.top,10);var D=F.retrieve("leftSide");var A=F.retrieve("rightSide");var E=D.retrieve("jxLayout");var H=A.retrieve("jxLayout");var N=this.domObj.measure(function(){var P=this.getSizes(["padding"],["top"]);return P.padding.top});var O=F.retrieve("size");if(!O){O=F.getBorderBoxSize();F.store("size",O)}var B=this.domObj.getContentBoxSize();var C,M,K;C=J+O.height-N;if(H.options.height!=null){M=H.options.height+H.options.top-C;K=B.height-C-M}else{M=B.height-H.options.bottom-C;K=H.options.bottom}if(M<0){M=0}if(M<H.options.minHeight){M=H.options.minHeight}if(H.options.maxHeight>=0&&M>H.options.maxHeight){M=H.options.maxHeight}C=B
 .height-K-M;J=C-O.height;var I,G;I=E.options.top;G=J-I;if(G<0){G=0}if(G<E.options.minHeight){G=E.options.minHeight}if(E.options.maxHeight>=0&&G>E.options.maxHeight){G=E.options.maxHeight}if(I+G!=J){J=I+G;var L=J+O.height-C;C+=L;M-=L}F.style.top=N+J+"px";if(E.options.height==null){D.resize({bottom:B.height-I-G})}else{D.resize({height:G})}if(H.options.height==null){A.resize({top:C})}else{A.resize({top:C,height:M})}},sizeChanged:function(){if(this.options.layout=="horizontal"){this.horizontalResize()}else{this.verticalResize()}},horizontalResize:function(){var O=this.domObj.getContentBoxSize().width;var D=O;var E,K,H;for(E=0;E<this.bars.length;E++){var L=this.bars[E];var Q=L.retrieve("size");if(!Q||Q.width==0){Q=L.getBorderBoxSize();L.store("size",Q)}O-=Q.width}var I=0,N=0;for(E=0;E<this.elements.length;E++){K=this.elements[E];H=K.retrieve("jxLayout").options;if(H.width!=null){O-=parseInt(H.width,10)}else{N=0;if(H.right!=0||H.left!=0){N=K.getBorderBoxSize().width}O-=N;I++}}if(I
 ==0){O+=H.width;H.width=null;I=1}var G=parseInt(O/I,10);var P=O%I;var B=this.domObj.measure(function(){var R=this.getSizes(["padding"],["left"]);return R.padding.left});var C=0;for(E=0;E<this.elements.length;E++){K=this.elements[E];var J=K.retrieve("jxLayout");H=J.options;if(H.width!=null){J.resize({left:C});C+=H.width}else{var M=G;if(I==1){M+=P}I--;if(H.right!=0||H.left!=0){N=K.getBorderBoxSize().width+M}else{N=M}if(N<0){if(I>0){G=G+N/I}N=0}if(N<H.minWidth){if(I>0){G=G+(N-H.minWidth)/I}N=H.minWidth}if(H.maxWidth>=0&&N>H.maxWidth){if(I>0){G=G+(N-H.maxWidth)/I}N=K.options.maxWidth}var A=D-C-N;J.resize({left:C,right:A});C+=N}var F=K.retrieve("rightBar");if(F){F.setStyle("left",B+C);C+=F.retrieve("size").width}}},verticalResize:function(){var N=this.domObj.getContentBoxSize().height;var B=N;var D,K,H;for(D=0;D<this.bars.length;D++){var L=this.bars[D];var Q=L.retrieve("size");if(!Q||Q.height==0){Q=L.getBorderBoxSize();L.store("size",Q)}N-=Q.height}var I=0,G=0;for(D=0;D<this.elem
 ents.length;D++){K=this.elements[D];H=K.retrieve("jxLayout").options;if(H.height!=null){N-=parseInt(H.height,10)}else{if(H.bottom!=0||H.top!=0){G=K.getBorderBoxSize().height}N-=G;I++}}if(I==0){N+=H.height;H.height=null;I=1}var F=parseInt(N/I,10);var O=N%I;var P=this.domObj.measure(function(){var R=this.getSizes(["padding"],["top"]);return R.padding.top});var C=0;for(D=0;D<this.elements.length;D++){K=this.elements[D];var J=K.retrieve("jxLayout");H=J.options;if(H.height!=null){J.resize({top:C});C+=H.height}else{var M=F;if(I==1){M+=O}I--;G=0;if(H.bottom!=0||H.top!=0){G=K.getBorderBoxSize().height+M}else{G=M}if(G<0){if(I>0){F=F+G/I}G=0}if(G<H.minHeight){if(I>0){F=F+(G-H.minHeight)/I}G=H.minHeight}if(H.maxHeight>=0&&G>H.maxHeight){if(I>0){F=F+(G-H.maxHeight)/I}G=H.maxHeight}var A=B-C-G;J.resize({top:C,bottom:A});C+=G}var E=K.retrieve("rightBar");if(E){E.style.top=P+C+"px";C+=E.retrieve("size").height}}},changeText:function(A){this.parent();this.bars.each(function(B){document.id(B
 ).set("title",this.getText({set:"Jx",key:"splitter",value:"barToolTip"}))},this)}});Jx.PanelSet=new Class({Family:"Jx.PanelSet",Extends:Jx.Widget,options:{parent:null,panels:[]},panels:null,height:null,firstLayout:true,render:function(){if(this.options.panels){this.panels=this.options.panels;this.options.panels=null}this.domObj=new Element("div");new Jx.Layout(this.domObj);var B=new Element("div",{styles:{position:"absolute"}});new Jx.Layout(B,{minHeight:0,maxHeight:0,height:0});var A=[B];this.panels.each(function(C){A.push(C.domObj);C.options.hideTitle=true;C.contentContainer.resize({top:0});C.toggleCollapse=this.maximizePanel.bind(this,C);C.domObj.store("Jx.Panel",C);C.manager=this},this);this.splitter=new Jx.Splitter(this.domObj,{splitInto:this.panels.length+1,layout:"vertical",elements:A,prepareBar:(function(E){var F=new Element("div",{"class":"jxPanelBar",title:this.getText({set:"Jx",key:"panelset",value:"barToolTip"})});var C=this.panels[E];C.title.setStyle("visibility
 ","hidden");document.id(document.body).adopt(C.title);var D=C.title.getBorderBoxSize();F.adopt(C.title);C.title.setStyle("visibility","");F.setStyle("height",D.height);F.store("size",D);return F}).bind(this)});this.addEvent("addTo",function(){document.id(this.domObj.parentNode).setStyle("overflow","hidden");this.domObj.resize()});if(this.options.parent){this.addTo(this.options.parent)}},maximizePanel:function(C){var F=this.domObj.getContentBoxSize().height;var B=F;var K=C.domObj.retrieve("jxLayout").options.maxHeight;var I,H,D,G,E,J;for(H=1;H<this.splitter.elements.length;H++){D=this.splitter.elements[H];B-=D.retrieve("leftBar").getBorderBoxSize().height;if(D!==C.domObj){G=D.retrieve("Jx.Panel");E=D.retrieve("jxLayout").options;B-=E.minHeight}else{I=H}}if(K==-1||K>=B){K=B;B=0}else{B=B-K}var L=0;for(H=1;H<this.splitter.elements.length;H++){D=this.splitter.elements[H];L+=D.retrieve("leftBar").getBorderBoxSize().height;if(D!==C.domObj){G=D.retrieve("Jx.Panel");E=D.retrieve("jxL
 ayout").options;J=$chk(E.height)?E.height:D.getBorderBoxSize().height;if(B>0){if(B>=J){B-=J;D.resize({top:L,height:J});L+=J}else{if(B>E.minHeight){D.resize({top:L,height:B});L+=B;B=0}else{D.resize({top:L,height:E.minHeight});L+=E.minHeight}}}else{D.resize({top:L,height:E.minHeight});L+=E.minHeight}D.retrieve("rightBar").style.top=L+"px"}else{break}}var A=F;for(H=this.splitter.elements.length-1;H>0;H--){D=this.splitter.elements[H];if(D!==C.domObj){E=D.retrieve("jxLayout").options;J=$chk(E.height)?E.height:D.getBorderBoxSize().height;if(B>0){if(B>=J){A-=J;B-=J;D.resize({top:A,height:J})}else{if(B>E.minHeight){A-=B;D.resize({top:A,height:B});B=0}else{A-=E.minHeight;D.resize({top:A,height:E.minHeight})}}}else{A-=E.minHeight;D.resize({top:A,height:E.minHeight,bottom:null})}A-=D.retrieve("leftBar").getBorderBoxSize().height;D.retrieve("leftBar").style.top=A+"px"}else{break}}C.domObj.resize({top:L,height:K,bottom:null});this.fireEvent("panelMaximize",C)},createText:function(A){this
 .parent()}});Jx.Dialog.Message=new Class({Family:"Jx.Dialog.Message",Extends:Jx.Dialog,Binds:["onOk"],options:{message:"",width:300,height:150,close:true,resize:true,collapse:false,useKeyboard:true,keys:{enter:"ok"}},render:function(){this.buttons=new Jx.Toolbar({position:"bottom",scroll:false});this.ok=new Jx.Button({label:this.getText({set:"Jx",key:"message",value:"okButton"}),onClick:this.onOk});this.buttons.add(this.ok);this.options.toolbars=[this.buttons];var B=Jx.type(this.options.message);if(B==="string"||B=="object"||B=="element"){this.question=new Element("div",{"class":"jxMessage"});switch(B){case"string":case"object":this.question.set("html",this.getText(this.options.message));break;case"element":this.options.message.inject(this.question);break}}else{this.question=this.options.question;document.id(this.question).addClass("jxMessage")}this.options.content=this.question;if(this.options.useKeyboard){var A=this;this.options.keyboardMethods.ok=function(C){C.preventDefa
 ult();A.close()}}this.parent();if(this.options.useKeyboard){this.keyboard.addEvents(this.getKeyboardEvents())}},onOk:function(){this.close()},setMessage:function(A){this.options.message=A;if($defined(this.question)){this.question.set("html",this.getText(A))}},changeText:function(A){this.parent();if($defined(this.ok)){this.ok.setLabel({set:"Jx",key:"message",value:"okButton"})}if(Jx.type(this.options.message)==="object"){this.question.set("html",this.getText(this.options.message))}}});Jx.Dialog.Confirm=new Class({Extends:Jx.Dialog,options:{question:"",useKeyboard:true,keys:{esc:"cancel",enter:"ok"},width:300,height:150,close:false,resize:true,collapse:false},keyboard:null,render:function(){this.buttons=new Jx.Toolbar({position:"bottom",scroll:false});this.ok=new Jx.Button({label:this.getText({set:"Jx",key:"confirm",value:"affirmativeLabel"}),onClick:this.onClick.bind(this,true)}),this.cancel=new Jx.Button({label:this.getText({set:"Jx",key:"confirm",value:"negativeLabel"}),onC
 lick:this.onClick.bind(this,false)});this.buttons.add(this.ok,this.cancel);this.options.toolbars=[this.buttons];var B=Jx.type(this.options.question);if(B==="string"||B==="object"||B=="element"){this.question=new Element("div",{"class":"jxConfirmQuestion"});switch(B){case"string":case"object":this.question.set("html",this.getText(this.options.question));break;case"element":this.options.question.inject(this.question);break}}else{this.question=this.options.question;document.id(this.question).addClass("jxConfirmQuestion")}this.options.content=this.question;if(this.options.useKeyboard){var A=this;this.options.keyboardMethods.ok=function(C){C.preventDefault();A.onClick(true)};this.options.keyboardMethods.cancel=function(C){C.preventDefault();A.onClick(false)}}this.parent();if(this.options.useKeyboard){this.keyboard.addEvents(this.getKeyboardEvents())}},onClick:function(A){this.isOpening=false;this.hide();this.fireEvent("close",[this,A])},changeText:function(A){this.parent();if($de
 fined(this.ok)){this.ok.setLabel({set:"Jx",key:"confirm",value:"affirmativeLabel"})}if($defined(this.cancel)){this.cancel.setLabel({set:"Jx",key:"confirm",value:"negativeLabel"})}if(Jx.type(this.options.question)==="object"){this.question.set("html",this.getText(this.options.question))}}});Jx.Tooltip=new Class({Family:"Jx.Widget",Extends:Jx.Widget,Binds:["enter","leave","move"],options:{offsets:{x:15,y:15},showDelay:100,cssClass:null},parameters:["target","tip","options"],render:function(){this.parent();this.target=document.id(this.options.target);var A=this.target.retrieve("Tip");if(A){this.target.eliminate("Tip")}this.domObj=new Element("div",{styles:{position:"absolute",top:0,left:0,visibility:"hidden"}}).inject(document.body);if(Jx.type(this.options.tip)==="string"||Jx.type(this.options.tip)=="object"){this.domObj.set("html",this.getText(this.options.tip))}else{this.domObj.grab(this.options.tip)}this.domObj.addClass("jxTooltip");if($defined(this.options.cssClass)){this.d
 omObj.addClass(this.options.cssClass)}this.options.target.store("Tip",this);this.options.target.addEvent("mouseenter",this.enter);this.options.target.addEvent("mouseleave",this.leave);this.options.target.addEvent("mousemove",this.move)},enter:function(A){this.timer=$clear(this.timer);this.timer=(function(){this.domObj.setStyle("visibility","visible");this.position(A)}).delay(this.options.delay,this)},leave:function(A){this.timer=$clear(this.timer);this.timer=(function(){this.domObj.setStyle("visibility","hidden")}).delay(this.options.delay,this)},move:function(A){this.position(A)},position:function(C){var B=window.getSize(),A=window.getScroll();var E=this.domObj.getMarginBoxSize();var D={x:this.domObj.offsetWidth,y:this.domObj.offsetHeight};var F={x:C.page.x+this.options.offsets.x,y:C.page.y+this.options.offsets.y};if(C.page.y+this.options.offsets.y+D.y+E.height-A.y>B.y){F.y=C.page.y-this.options.offsets.y-E.height-A.y}if(C.page.x+this.options.offsets.x+D.x+E.width-A.x>B.x){
 F.x=C.page.x-this.options.offsets.x-E.width-A.x}this.domObj.setStyle("top",F.y);this.domObj.setStyle("left",F.x)},detach:function(){this.target.eliminate("Tip");this.destroy()}});Jx.Fieldset=new Class({Family:"Jx.Fieldset",Extends:Jx.Widget,options:{legend:null,id:null,fieldsetClass:null,legendClass:null,template:'<fieldset class="jxFieldset"><legend><span class="jxFieldsetLegend"></span></legend></fieldset>',form:null},classes:new Hash({domObj:"jxFieldset",legend:"jxFieldsetLegend"}),legend:null,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}if(this.domObj){if($defined(this.options.id)){this.domObj.set("id",this.options.id)}if($defined(this.options.fieldsetClass)){this.domObj.addClass(this.options.fieldsetClass)}}if(this.legend){if($defined(this.options.legend)){this.legend.set("html",this.getText(this.options.legend));if($defined(this.options.legendClass)){this.legend
 .addClass(this.options.legendClass)}}else{this.legend.destroy()}}},add:function(){var B;for(var A=0;A<arguments.length;A++){B=arguments[A];if($defined(B.jxFamily)&&!$defined(B.form)&&$defined(this.form)){B.form=this.form;this.form.addField(B)}this.domObj.grab(B)}return this},addTo:function(A){if(A instanceof Jx.Form){this.form=A}else{if(A instanceof Jx.Fieldset){this.form=A.form}}return this.parent(A)}});Jx.Form=new Class({Family:"Jx.Form",Extends:Jx.Widget,options:{method:"post",action:"",fileUpload:false,formClass:null,name:"",acceptCharset:"utf-8",uploadFilesFirst:false,template:'<form class="jxForm"></form>'},defaultAction:null,fields:null,pluginNamespace:"Form",classes:$H({domObj:"jxForm"}),init:function(){this.parent();this.fields=new Hash();this.data={}},render:function(){this.parent();this.domObj.set({method:this.options.method,action:this.options.action,name:this.options.name,"accept-charset":this.options.acceptCharset,events:{keypress:function(A){if(A.key=="enter"&
 &A.target.tagName!="TEXTAREA"&&this.defaultAction&&this.defaultAction.click){document.id(this.defaultAction).focus();this.defaultAction.click();A.stop()}}.bind(this)}});if(this.options.fileUpload){this.domObj.set("enctype","multipart/form-data")}if($defined(this.options.formClass)){this.domObj.addClass(this.options.formClass)}},addField:function(A){this.fields.set(A.id,A);if(A.options.defaultAction){this.defaultAction=A}},isValid:function(A){return true},getValues:function(A){var B=this.domObj.toQueryString();if($defined(A)&&A){return B}else{return B.parseQueryString()}},setValues:function(A){if(Jx.type(A)==="object"){A=new Hash(A)}this.fields.each(function(B){B.setValue(A.get(B.name))},this)},add:function(){var B;for(var A=0;A<arguments.length;A++){B=arguments[A];if(B instanceof Jx.Field&&!$defined(B.form)){B.form=this;this.addField(B)}else{if(B instanceof Jx.Fieldset&&!$defined(B.form)){B.form=this}}this.domObj.grab(B)}return this},reset:function(){this.fields.each(functio
 n(B,A){B.reset()},this);this.fireEvent("reset",this)},getFieldsByName:function(B){var A=[];this.fields.each(function(C,D){if(C.name===B){A.push(C)}},this);return A},getField:function(A){if(this.fields.has(A)){return this.fields.get(A)}return null},setBusy:function(A){if(this.busy==A){return }this.parent(A);this.fields.each(function(B){B.setBusy(A,true)})},submit:function(){var B=this.options;if(B.fileUpload){var A=this.findFiles();A.each(function(D){var C=D.getFileInputs();if(C.length>1){C.each(function(E){E.set("name",E.get("name")+"[]")},this)}D.destroy();this.domObj.adopt(C)},this)}this.domObj.submit()},ajaxSubmit:function(){var B=this.options;if(B.fileUpload){var A=this.findFiles();this.files=A.length;this.completed=0;A.each(function(D,C){D.addEvent("onFileUploadComplete",this.fileUploadComplete.bind(this));if(C==(this.files-1)&&!B.uploadFilesFirst){D.upload(this)}else{D.upload()}},this)}else{this.submitForm()}},submitForm:function(){var B=this.getValues();var A=new Requ
 est.JSON({url:this.action,method:this.method,data:B,urlEncoded:true,onSuccess:function(D,C){this.fileUploadComplete(D,true)}.bind(this)});A.send()},findFiles:function(){var A=[];this.fields.each(function(B){if(B instanceof Jx.Field.File){A.push(B)}},this);return A},fileUploadComplete:function(A){this.completed++;$each(A,function(C,B){this.data[B]=C},this);if(this.completed==this.files&&this.options.uploadFilesFirst){this.submitForm()}else{this.fireEvent("formSubmitComplete",[this.data])}}});Jx.Field=new Class({Family:"Jx.Field",Extends:Jx.Widget,pluginNamespace:"Field",Binds:["changeText"],options:{id:null,name:null,label:null,labelSeparator:":",value:null,tag:null,tip:null,template:null,containerClass:null,labelClass:null,fieldClass:null,tagClass:null,required:false,readonly:false,disabled:false,defaultAction:false},overtextOptions:{element:"label"},field:null,label:null,tag:null,id:null,overText:null,type:"field",classes:new Hash({domObj:"jxInputContainer",label:"jxInputLa
 bel",tag:"jxInputTag"}),render:function(){this.classes.set("field","jxInput"+this.type);var A=$defined(this.options.name)?this.options.name:"";this.options.template=this.options.template.substitute({name:A});this.parent();this.id=this.generateId();this.name=this.options.name;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"}}if(this.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("readon
 ly","readonly");this.field.addClass("jxFieldReadonly")}if($defined(this.options.disabled)&&this.options.disabled){this.field.set("disabled","disabled");this.field.addClass("jxFieldDisabled")}this.field.addEvents({focus:this.onFocus.bind(this),blur:this.onBlur.bind(this),change:this.onChange.bind(this)});this.field.store("field",this);if(this.label){this.label.addEvent("click",function(){this.field.focus()}.bind(this))}}if(this.label){if($defined(this.options.labelClass)){this.label.addClass(this.options.labelClass)}if($defined(this.options.label)){this.label.set("html",this.getText(this.options.label)+this.options.labelSeparator)}this.label.set("for",this.id);if(this.options.required){this.requiredText=new Element("em",{html:this.getText({set:"Jx",key:"field",value:"requiredText"}),"class":"required"});this.requiredText.inject(this.label)}}if(this.tag){if($defined(this.options.tagClass)){this.tag.addClass(this.options.tagClass)}if($defined(this.options.tag)){this.tag.set("ht
 ml",this.options.tag)}}if($defined(this.options.form)&&this.options.form instanceof Jx.Form){this.form=this.options.form;this.form.addField(this)}},setValue:function(A){if(!this.options.readonly){this.field.set("value",A)}},getValue:function(){return this.field.get("value")},reset:function(){this.setValue(this.options.value);this.fireEvent("reset",this)},disable:function(){this.options.disabled=true;this.field.set("disabled","disabled");this.field.addClass("jxFieldDisabled")},enable:function(){this.options.disabled=false;this.field.erase("disabled");this.field.removeClass("jxFieldDisabled")},addTo:function(B,A){if(B instanceof Jx.Fieldset||B instanceof Jx.Form){B.add(this)}else{this.parent(B,A)}return this},changeText:function(A){this.parent();if($defined(this.options.label)&&this.label){this.label.set("html",this.getText(this.options.label)+this.options.labelSeparator)}if(this.options.required){this.requiredText=new Element("em",{html:this.getText({set:"Jx",key:"field",valu
 e:"requiredText"}),"class":"required"});this.requiredText.inject(this.label)}if($defined(this.requiredText)){this.requiredText.set("html",this.getText({set:"Jx",key:"field",value:"requiredText"}))}},onFocus:function(){this.fireEvent("focus",this)},onBlur:function(){this.fireEvent("blur",this)},onChange:function(){this.fireEvent("change",this)},setBusy:function(B,A){if(!A){this.parent(B)}this.field.set("readonly",B||this.options.readonly)}});Jx.Field.Text=new Class({Extends:Jx.Field,options:{overText:null,template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span></span>'},type:"Text",render:function(){this.parent();if($defined(this.options.overText)){var A=$extend({},this.options.overText);this.field.set("alt",this.options.tip);this.overText=new OverText(this.field,A);this.overText.show()}}});Jx.Dialog.Prompt=new Class({Extends:Jx.Dialog,options:{prompt:"",startingValue:""
 ,fieldOptions:{type:"Text",options:{},validate:true,validatorOptions:{validators:["required"],validateOnBlur:true,validateOnChange:false},showErrorMsg:true},width:400,height:200,close:true,resize:true,collapse:false,useKeyboard:true,keys:{esc:"cancel",enter:"ok"}},render:function(){this.buttons=new Jx.Toolbar({position:"bottom",scroll:false});this.ok=new Jx.Button({label:this.getText({set:"Jx",key:"prompt",value:"okButton"}),onClick:this.onClick.bind(this,true)});this.cancel=new Jx.Button({label:this.getText({set:"Jx",key:"prompt",value:"cancelButton"}),onClick:this.onClick.bind(this,false)});this.buttons.add(this.ok,this.cancel);this.options.toolbars=[this.buttons];var B=this.options.fieldOptions;B.options.label=this.getText(this.options.prompt);B.options.value=this.options.startingValue;B.options.containerClass="jxPrompt";if(Jx.type(B.type)==="string"&&$defined(Jx.Field[B.type.capitalize()])){this.field=new Jx.Field[B.type.capitalize()](B.options)}else{if(Jx.type(B.type)==
 ="Jx.Object"){this.field=B.type}else{window.console?console.warn("Field type does not exist %o, using Jx.Field.Text",B.type):false;this.field=new Jx.Field.Text(B.options)}}if(this.options.fieldOptions.validate){this.validator=new Jx.Plugin.Field.Validator(this.options.fieldOptions.validatorOptions);this.validator.attach(this.field)}this.options.content=document.id(this.field);if(this.options.useKeyboard){var A=this;this.options.keyboardMethods.ok=function(C){C.preventDefault();A.onClick(true)};this.options.keyboardMethods.cancel=function(C){C.preventDefault();A.onClick(false)}}this.parent();if(this.options.useKeyboard){this.keyboard.addEvents(this.getKeyboardEvents())}},onClick:function(A){if(A&&$defined(this.validator)){if(this.validator.isValid()){this.isOpening=false;this.hide();this.fireEvent("close",[this,A,this.field.getValue()])}else{this.field.field.focus.delay(50,this.field.field)}}else{this.isOpening=false;this.hide();this.fireEvent("close",[this,A,this.field.getVa
 lue()])}},changeText:function(A){this.parent();if($defined(this.ok)){this.ok.setLabel({set:"Jx",key:"prompt",value:"okButton"})}if($defined(this.cancel)){this.cancel.setLabel({set:"Jx",key:"prompt",value:"cancelButton"})}this.field.label.set("html",this.getText(this.options.prompt))}});Jx.Panel.DataView=new Class({Extends:Jx.Panel,options:{data:null,sortColumns:null,itemTemplate:null,emptyTemplate:null,containerClass:null,itemClass:null,listOptions:{select:true,hover:true}},init:function(){this.domA=new Element("div");this.list=this.createList(this.domA,this.options.listOptions);this.parent()},render:function(){if(!$defined(this.options.data)){return }this.options.content=this.domA;this.parent();this.domA.addClass(this.options.containerClass);this.itemCols=this.parseTemplate(this.options.itemTemplate);this.bound.update=this.update.bind(this);this.options.data.addEvent("storeDataLoaded",this.bound.update);this.options.data.addEvent("storeSortFinished",this.bound.update);this.
 options.data.addEvent("storeDataLoadFailed",this.bound.update);if(this.options.data.loaded){this.update()}},draw:function(){var D=this.options.data.count();if($defined(D)&&D>0){for(var A=0;A<D;A++){this.options.data.moveTo(A);var B=this.createItem();this.list.add(B)}}else{var C=new Element("div",{html:this.options.emptyTemplate});this.list.add(B)}this.fireEvent("renderDone",this)},createItem:function(){var A={};this.itemCols.each(function(D){A[D]=this.options.data.get(D)},this);var C=this.options.itemTemplate.substitute(A);var B=new Element("div",{"class":this.options.itemClass,html:C});return B},update:function(){if(!this.updating){this.updating=true;this.list.empty();this.options.data.sort(this.options.sortColumns);this.draw();this.updating=false}},parseTemplate:function(C){var B=this.options.data.getColumns();var A=[];B.each(function(D){var E="{"+D.name+"}";if(C.contains(E)){A.push(D.name)}},this);return A},enterItem:function(A,B){this.fireEvent("mouseenter",A,B)},leaveIt
 em:function(A,B){this.fireEvent("mouseleave",A,B)},selectItem:function(A,B){this.fireEvent("select",A,B)},unselectItem:function(A,B){this.fireEvent("unselect",A,B)},addItem:function(A,B){this.fireEvent("add",A,B)},removeItem:function(A,B){this.fireEvent("remove",A,B)},createList:function(A,B){return new Jx.List(A,$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)},B))}});Jx.Panel.DataView.Group=new Class({Extends:Jx.Panel.DataView,options:{groupTemplate:null,groupContainerClass:null,groupHeaderClass:null,listOptions:{select:false,hover:false},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"},s
 electClass:"jxItemSelected"});this.groupManager=new Jx.Selection({eventToFire:{select:"groupselect",unselect:"groupunselect"},selectClass:"jxGroupSelected"});this.parent()},render:function(){this.list=this.createList(this.domA,this.listOptions,this.groupManager);this.parent()},draw:function(){var J=this.options.data;var B=J.count();if($defined(B)&&B>0){var M="";var I=null;for(var E=0;E<B;E++){J.moveTo(E);var K=J.get(this.options.sortColumns[0]);if(K!==M){var A=new Element("div",{"class":this.options.groupContainerClass});var C=this.createList(A,{select:false,hover:false});this.list.add(C.container);M=K;var D={};this.groupCols.each(function(O){D[O]=J.get(O)},this);var L=this.options.groupTemplate.substitute(D);var H=new Element("div",{"class":this.options.groupHeaderClass,html:L,id:"group-"+K.replace(" ","-","g")});C.add(H);var G=new Element("div",{"class":this.options.containerClass});I=this.createList(G,this.options.itemOptions,this.itemManager);C.add(I.container)}var N=thi
 s.createItem();I.add(N)}}else{var F=new Element("div",{html:this.options.emptyTemplate});this.list.add(F)}this.fireEvent("renderDone",this)},createList:function(A,B,C){return new Jx.List(A,$extend({onMouseenter:this.enterItem.bind(this),onMouseleave:this.leaveItem.bind(this),onAdd:this.addItem.bind(this),onRemove:this.removeItem.bind(this)},B),C)}});Jx.ListItem=new Class({Family:"Jx.ListItem",Extends:Jx.Widget,options:{enabled:true,template:'<li class="jxListItemContainer jxListItem"></li>'},classes:new Hash({domObj:"jxListItemContainer",domContent:"jxListItem"}),render:function(){this.parent();this.domContent.store("jxListItem",this);this.domObj.store("jxListTarget",this.domContent);this.loadContent(this.domContent)},enable:function(A){}});Jx.ListView=new Class({Family:"Jx.Widget",Extends:Jx.Widget,pluginNamespace:"ListView",options:{template:'<ul class="jxListView jxList"></ul>',listOptions:{hover:true,press:true,select:true}},classes:new Hash({domObj:"jxListView",listObj:
 "jxList"}),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}}this.list=new Jx.List(this.listObj,this.options.listOptions,this.selection)},cleanup:function(){if(this.ownsSelection){this.selection.destroy()}this.list.destroy()},add:function(B,A){this.list.add(B,A);return this},remove:function(A){this.list.remove(A);return this},replace:function(B,A){this.list.replace(B,A);return this},empty:function(){this.list.empty();return this}});Jx.Field.Hidden=new Class({Extends:Jx.Field,options:{template:'<span class="jxInputContainer"><input class="jxInputHidden" type="hidden" name="{name}"/></span>'},type:"Hidden"});Jx.Field.File=new Class({Extends:Jx.Field,options:{template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="
 jxInputTag"></span></span>',autoUpload:false,progress:false,progressIDUrl:"",progressName:"APC_UPLOAD_PROGRESS",progressId:"progress_key",handlerUrl:"",progressUrl:"",debug:false,mode:"single"},type:"File",forms:null,init:function(){this.parent();this.forms=new Hash();this.isIFrameSetup=true;this.iframe=new Element("iframe",{name:this.generateId(),styles:{display:"none",visibility:"hidden"}});this.iframe.inject(document.body);this.iframe.addEvent("load",this.processIFrameUpload.bind(this))},render:function(){this.parent();if(!$defined(this.options.id)){this.field.set("id",this.generateId())}this.fake=new Element("div",{"class":"jxFileFake"});this.text=new Jx.Field.Text({template:'<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'});this.browseButton=new Jx.Button({label:this.getText({set:"Jx",key:"file",value:"browseLabel"})});this.fake.adopt(this.text,this.browseButton);this.field.grab(this.fake,"after");this.field.addEvents({change:this.copyVa
 lue.bind(this),mouseenter:this.mouseEnter.bind(this),mouseleave:this.mouseLeave.bind(this)})},copyValue:function(){if(this.options.mode=="single"&&this.field.value!==""&&(this.text.field.value!==this.field.value)){this.text.field.value=this.field.value;this.fireEvent("fileSelected",this);this.forms.set(this.field.value,this.prepForm());if(this.options.autoUpload){this.uploadSingle()}}else{if(this.options.mode=="multiple"){var A=this.field.value;var B=this.prepForm();this.forms.set(A,B);this.text.setValue("");this.fireEvent("fileSelected",A)}}},mouseEnter:function(){this.browseButton.domA.addClass("jxButtonPressed")},mouseLeave:function(){this.browseButton.domA.removeClass("jxButtonPressed")},prepForm:function(){var C=new Jx.Form({action:this.options.handlerUrl,name:"jxUploadForm",fileUpload:true});var B=document.id(this.field.getParent());var A=document.id(this.field).getPrevious();var D=this.field.clone().cloneEvents(this.field);document.id(C).grab(this.field);if(A){D.injec
 t(A,"after")}else{if(B){D.inject(B,"top")}}this.field=D;this.mouseLeave();return C},upload:function(B){if(this.forms.getLength()>0){var D=this.forms.getKeys();this.currentKey=D[0];var C=this.forms.get(this.currentKey);this.forms.erase(this.currentKey);if($defined(B)&&this.forms.getLength()==0){var A=B.fields;A.each(function(E){if(!(E instanceof Jx.Field.File)){document.id(E).clone().inject(C)}},this)}this.uploadSingle(C)}else{this.fireEvent("allUploadsComplete",this)}},uploadSingle:function(B){this.form=$defined(B)?B:this.prepForm();this.fireEvent("fileUploadBegin",[this.currentKey,this]);this.text.setValue("");document.id(this.form).set("target",this.iframe.get("name")).setStyles({visibility:"hidden",display:"none"}).inject(document.body);if(this.options.progress){var A=new Request.JSON({url:this.options.progressIDUrl,method:"get",onSuccess:this.submitUpload.bind(this)});A.send()}else{this.submitUpload()}},submitUpload:function(A){if($defined(A)&&A.success&&$defined(A.id)){
 this.progressID=A.id;var B=new Jx.Field.Hidden({name:this.options.progressName,id:this.options.progressId,value:this.progressID});B.addTo(this.form,"top")}document.id(this.form).submit();if(this.options.progress&&$defined(this.progressID)){this.pollUpload()}},pollUpload:function(){var B={id:this.progressID};var A=new Request.JSON({data:B,url:this.options.progressUrl,method:"get",onSuccess:this.processProgress.bind(this),onFailure:this.uploadFailure.bind(this)});A.send()},processProgress:function(A){if($defined(A)){this.fireEvent("fileUploadProgress",[A,this.currentKey,this]);if(A.current<A.total){this.polling=true;this.pollUpload()}else{this.polling=false}}},uploadFailure:function(A){this.fireEvent("fileUploadProgressError",[this,A])},processIFrameUpload:function(){if(this.isIFrameSetup){if(this.iframe.contentDocument&&this.iframe.contentDocument.defaultView){var A=this.iframe.contentDocument.defaultView.document.body.innerHTML}else{var A=this.iframe.contentWindow.document.b
 ody.innerHTML}var B=JSON.decode(A);if($defined(B)&&$defined(B.success)&&B.success){this.done=true;this.doneData=B;this.uploadCleanUp();this.fireEvent("fileUploadComplete",[B,this.currentKey,this])}else{this.fireEvent("fileUploadError",[B,this.currentKey,this])}if(this.options.mode=="multiple"){this.upload()}else{this.fireEvent("allUploadsComplete",this)}}},uploadCleanUp:function(){if(!this.options.debug){this.form.destroy()}},remove:function(A){if(this.forms.has(A)){this.forms.erase(A)}},changeText:function(A){this.parent();if($defined(this.browseButton)){this.browseButton.setLabel(this.getText({set:"Jx",key:"file",value:"browseLabel"}))}},getFileInputs:function(){var A=[];this.forms.each(function(C){var B=document.id(C).getElement("input[type=file]");A.push(B)},this);return A}});Jx.Progressbar=new Class({Family:"Jx.Progressbar",Extends:Jx.Widget,options:{onUpdate:$empty,onComplete:$empty,parent:null,progressText:null,template:'<div class="jxProgressBar-container"><div class
 ="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div></div>'},classes:new Hash({domObj:"jxProgressBar-container",message:"jxProgressBar-message",container:"jxProgressBar",outline:"jxProgressBar-outline",fill:"jxProgressBar-fill",text:"jxProgressBar-text"}),bar:null,text:null,render:function(){this.parent();if($defined(this.options.parent)){this.domObj.inject(document.id(this.options.parent))}this.domObj.addClass("jxProgressStarting");this.width=document.id(this.domObj).getContentBoxSize().width;if(this.message){if($defined(MooTools.lang.get("Jx","progressbar").messageText)){this.message.set("html",this.getText({set:"Jx",key:"progressbar",value:"messageText"}))}else{this.message.destroy()}}if(this.fill){this.fill.setStyles({width:0})}var B={};var A=this.options.progressText==null?this.getText({set:"Jx",key:"progressbar",value:"progressText"}):this.ge
 tText(this.options.progressText);if(A.contains("{progress}")){B.progress=0}if(A.contains("{total}")){B.total=0}if(this.text){this.text.set("html",A.substitute(B))}},update:function(C,A){if(this.domObj.hasClass("jxProgressStarting")){this.domObj.removeClass("jxProgressStarting").addClass("jxProgressWorking")}var B=(A*this.width)/C;this.text.get("tween",{property:"width",onComplete:function(){var F={};var E=this.options.progressText==null?this.getText({set:"Jx",key:"progressbar",value:"progressText"}):this.getText(this.options.progressText);if(E.contains("{progress}")){F.progress=A}if(E.contains("{total}")){F.total=C}var D=E.substitute(F);this.text.set("text",D)}.bind(this)}).start(B);this.fill.get("tween",{property:"width",onComplete:(function(){if(C===A){this.complete=true;this.domObj.removeClass("jxProgressWorking").addClass("jxProgressFinished");this.fireEvent("complete")}else{this.fireEvent("update")}}).bind(this)}).start(B)},changeText:function(A){this.parent();if(this.m
 essage){this.message.set("html",this.getText({set:"Jx",key:"progressbar",value:"messageText"}))}}});Jx.Panel.FileUpload=new Class({Family:"Jx.Panel.FileUpload",Extends:Jx.Panel,Binds:["moveToQueue","fileUploadBegin","fileUploadComplete","allUploadsComplete","fileUploadProgressError,","fileUploadError","fileUploadProgress"],options:{file:{autoUpload:false,progress:false,progressIDUrl:"",handlerUrl:"",progressUrl:""},progressOptions:{template:"<li class='jxListItemContainer jxProgressBar-container' id='{id}'><div class='jxProgressBar'><div class='jxProgressBar-outline'></div><div class='jxProgressBar-fill'></div><div class='jxProgressBar-text'></div></div></li>",containerClass:"progress-container",messageText:null,messageClass:"progress-message",progressText:"Uploading {filename}",progressClass:"progress-bar"},onFileComplete:$empty,onComplete:$empty,prompt:null,removeOnComplete:false},domObjA:null,fileQueue:[],listTemplate:"<li class='jxListItemContainer' id='{id}'><a class='j
 xListItem' href='javascript:void(0);'><span class='itemLabel jxUploadFileName'>{name}</span><span class='jxUploadFileDelete' title='Remove this file from the queue.'></span></a></li>",render:function(){this.domObjA=new Element("div",{"class":"jxFileUploadPanel"});if($defined(this.options.prompt)){var B;if(Jx.type(this.options.prompt==="string")){B=new Element("p",{html:this.options.prompt})}else{B=this.options.prompt}B.inject(this.domObjA)}this.fileOpt=this.options.file;this.fileOpt.template='<div class="jxInputContainer jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';this.file=new Jx.Field.File(this.fileOpt);this.file.addEvent("fileSelected",this.moveToQueue);this.file.addTo(this.domObjA);this.listView=new Jx.ListView({template:'<ul class="jxListView jxList jxUploadQueue"></ul>'}).addTo(this.domObjA);if(!this.options.file.autoUpload){this.uploadBtn=new Jx.Button({label:this.getText({set:"Jx",key:"upload",value:"buttonText"}),onClick:this.upload.bi
 nd(this)});var A=new Jx.Toolbar({position:"bottom",scroll:false}).add(this.uploadBtn);this.uploadBtn.setEnabled(false);this.options.toolbars=[A]}this.options.content=this.domObjA;this.parent(this.options)},moveToQueue:function(B){var A=new String(this.listTemplate).substitute({name:B,id:B});var C=new Jx.ListItem({template:A,enabled:true});$(C).getElement(".jxUploadFileDelete").addEvent("click",function(){this.listView.remove(C);this.file.remove(B);if(this.listView.list.count()==0){this.uploadBtn.setEnabled(false)}}.bind(this));this.listView.add(C);if(!this.uploadBtn.isEnabled()){this.uploadBtn.setEnabled(true)}},upload:function(){this.file.addEvents({fileUploadBegin:this.fileUploadBegin,fileUploadComplete:this.fileUploadComplete,allUploadsComplete:this.allUploadsComplete,fileUploadError:this.fileUploadError,fileUploadProgress:this.fileUploadProgress,fileUploadProgressError:this.fileUploadError});this.file.upload()},fileUploadBegin:function(A){if(this.options.file.progress){v
 ar B=$merge({},this.options.progressOptions);B.progressText=B.progressText.substitute({filename:A});B.template=B.template.substitute({id:A});this.pb=new Jx.Progressbar(B);var D=document.id(A);this.oldContents=D;this.listView.replace(D,$(this.pb))}else{var C=document.id(A).getElement(".jxUploadFileDelete");C.addClass("jxUploadFileProgress").set("title","File Uploading...")}},fileUploadComplete:function(B,A){if($defined(B.success)&&B.success){this.removeUploadedFile(A)}else{this.fileUploadError(B,A)}},fileUploadError:function(D,A){if(this.options.file.progress){this.listView.replace(document.id(A),this.oldContents)}var C=document.id(A).getElement(".jxUploadFileDelete");C.erase("title");if(C.hasClass("jxUploadFileProgress")){C.removeClass("jxUploadFileProgress").addClass("jxUploadFileError")}else{C.addClass("jxUploadFileError")}if($defined(D.error)&&$defined(D.error.message)){var B=new Jx.Tooltip(C,D.error.message,{cssClass:"jxUploadFileErrorTip"})}},removeUploadedFile:function
 (B){if(this.options.removeOnComplete){this.listView.remove(document.id(B))}else{if(this.options.file.progress){this.listView.replace(document.id(B),this.oldContents)}var A=document.id(B).getElement(".jxUploadFileDelete");if(A.hasClass("jxUploadFileDelete")){A.addClass("jxUploadFileComplete")}else{if(A.hasClass("jxUploadFileProgress")){A.removeClass("jxUploadFileProgress").addClass("jxUploadFileComplete")}}}this.fireEvent("fileUploadComplete",B)},fileUploadProgress:function(B,A){if(this.options.progress){this.pb.update(B.total,B.current)}},allUploadsComplete:function(){this.uploadBtn.setEnabled(false);this.fireEvent("allUploadsCompleted",this)},changeText:function(A){this.parent();if($defined(this.uploadBtn)){this.uploadBtn.setLabel({set:"Jx",key:"upload",value:"buttonText"})}}});Jx.Column=new Class({Family:"Jx.Column",Extends:Jx.Widget,options:{renderMode:"fixed",width:100,isEditable:false,isSortable:false,isResizable:false,isHidden:false,name:"",template:null,renderer:null}
 ,classes:$H({domObj:"jxGridCellContent"}),grid:null,parameters:["options","grid"],init:function(){this.name=this.options.name;if(!$defined(this.options.template)){this.options.template='<span class="jxGridCellContent">'+this.name.capitalize()+"</span>"}this.parent();if($defined(this.options.grid)&&this.options.grid instanceof Jx.Grid){this.grid=this.options.grid}if(!$defined(this.options.renderer)){this.options.renderer=new Jx.Grid.Renderer.Text({textTemplate:"{"+this.name+"}"})}else{if(!(this.options.renderer instanceof Jx.Grid.Renderer)){var A=Jx.type(this.options.renderer);if(A==="object"){if(!$defined(this.options.renderer.options.textTemplate)){this.options.renderer.options.textTemplate="{"+this.name+"}"}if(!$defined(this.options.renderer.name)){this.options.renderer.name="Text"}this.options.renderer=new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](this.options.renderer.options)}}}this.options.renderer.setColumn(this)},getTemplate:function(A){return"<span c
 lass='jxGridCellContent' title='{col"+A+"}'>{col"+A+"}</span>"},getHeaderHTML:function(){if(this.isSortable()&&!this.sortImage){this.sortImage=new Element("img",{src:Jx.aPixel.src});this.sortImage.inject(this.domObj)}else{if(!this.isSortable()&&this.sortImage){this.sortImage.dispose();this.sortImage=null}}return this.domObj},setWidth:function(B,A){A=$defined(A)?A:false;var C=this.cellWidth-this.width;if(!A){this.width=parseInt(B,10);this.cellWidth=this.width+C;this.options.width=B}else{this.width=parseInt(B,10)-C;this.cellWidth=B;this.options.width=this.width}if(this.rule&&parseInt(this.width,10)>=0){this.rule.style.width=parseInt(this.width,10)+"px"}if(this.cellRule&&parseInt(this.cellWidth,10)>=0){this.cellRule.style.width=parseInt(this.cellWidth,10)+"px"}},getWidth:function(){return this.width},getCellWidth:function(){return this.cellWidth},calculateWidth:function(C){C=$defined(C)?C:false;var E,H,D=this.grid.getStore(),G,I,A,F,B;D.first();if((this.options.renderMode=="fix
 ed"||this.options.renderMode=="expand")&&D.valid()){G=new Element("span",{"class":"jxGridCellContent",html:"a",styles:{width:this.options.width}});I=this.measure(G,"jxGridCell");E=I.content.width;H=I.cell.width}else{A=D.getPosition();E=H=0;while(D.valid()){this.options.renderer.render();F=document.id(this.options.renderer);B="jxGridCell";if(this.grid.row.useHeaders()&&this.options.name===this.grid.row.getRowHeaderColumn()){B="jxGridRowHead"}I=this.measure(F,B,C,D.getPosition());if(I.content.width>E){E=I.content.width}if(I.cell.width>H){H=I.cell.width}if(D.hasNext()){D.next()}else{break}}if(!(this.grid.row.useHeaders()&&this.options.name===this.grid.row.getRowHeaderColumn())){B="jxGridColHead";if(this.isEditable()){B+=" jxColEditable"}if(this.isResizable()){B+=" jxColResizable"}if(this.isSortable()){B+=" jxColSortable"}I=this.measure(this.domObj.clone(),B);if(I.content.width>E){E=I.content.width}if(I.cell.width>H){H=I.cell.width}}}this.width=E;this.cellWidth=H;D.moveTo(A);ret
 urn this.width},measure:function(F,A,C,E){var D=new Element("span",{"class":A}),B;F.inject(D);D.setStyles({visibility:"hidden",width:"auto"});D.inject(document.body,"bottom");B=D.measure(function(){var G=this;if(!C){G=G.getFirst()}return{content:G.getMarginBoxSize(),cell:G.getMarginBoxSize()}});D.destroy();return B},isEditable:function(){return this.options.isEditable},isSortable:function(){return this.options.isSortable},isResizable:function(){return this.options.isResizable},isHidden:function(){return this.options.isHidden},isAttached:function(){return this.options.renderer.attached},getHTML:function(){this.options.renderer.render();return document.id(this.options.renderer)}});Jx.Columns=new Class({Family:"Jx.Columns",Extends:Jx.Object,options:{headerRowHeight:20,useHeaders:false,columns:[]},columns:[],rowTemplate:null,parameters:["options","grid"],hasExpandable:null,init:function(){this.parent();if($defined(this.options.grid)&&this.options.grid instanceof Jx.Grid){this.gr
 id=this.options.grid}this.hasExpandable=false;this.options.columns.each(function(A){if(A instanceof Jx.Column){this.columns.push(A)}else{if(Jx.type(A)==="object"){this.columns.push(new Jx.Column(A,this.grid))}}var B=this.columns[this.columns.length-1]},this);this.buildTemplates()},addColumns:function(A){this.columns.extend(A);this.buildTemplates()},buildTemplates:function(){if(!this.grid){return }var F="",D=false,C=this.grid,E=C.row,B=C.row.useHeaders()?this.getByName(E.options.headerColumn):null,A;this.columns.each(function(I,H){var G="";if(!I.isHidden()&&I!=B){D|=I.options.renderMode=="expand";if(!I.options.renderer||!I.options.renderer.domInsert){G=I.getTemplate(H)}F+="<td class='jxGridCell jxGridCol"+H+" jxGridCol"+I.options.name+"'>"+G+"</td>"}});if(!D){F+="<td><span class='jxGridCellUnattached'></span></td>"}this.rowTemplate=F;this.hasExpandable=D},getHeaderHeight:function(A){if(!$defined(this.height)||A){if($defined(this.options.headerRowHeight)&&this.options.headerRo
 wHeight!=="auto"){this.height=this.options.headerRowHeight}}return this.height},useHeaders:function(){return this.options.useHeaders},getByName:function(B){var A;this.columns.each(function(C){if(C.name===B){A=C}},this);return A},getByField:function(B){var A;this.columns.each(function(C){if(C.options.modelField===B){A=C}},this);return A},getByGridIndex:function(B){var E=this.options.useHeaders?this.grid.colTableBody.getFirst().getChildren():this.grid.gridTableBody.getFirst().getChildren();var A=E[B];var C=A.get("class").split(" ").filter(function(F){if(this.options.useHeaders){return F.test("jxColHead-")}else{return F.test("jxCol-")}}.bind(this));var D=C[0].split("-");return this.getByName(D[1])},getHeaders:function(C){var B=this.grid,D=B.row,A=B.row.useHeaders()?this.getByName(D.options.headerColumn):null;if(this.useHeaders()){this.columns.each(function(F,E){if(!F.isHidden()&&F!=A){var G=["jxGridColHead","jxGridCol"+E,"jxCol-"+F.options.name,"jxColHead-"+F.options.name],H;if
 (F.isEditable()){G.push("jxColEditable")}if(F.isResizable()){G.push("jxColResizable")}if(F.isSortable()){G.push("jxColSortable")}H=new Element("th",{"class":G.join(" ")});H.store("jxCellData",{column:F,colHeader:true,index:E});H.adopt(F.getHeaderHTML());H.inject(C)}});if(!this.hasExpandable){new Element("th",{"class":"jxGridColHead jxGridCellUnattached"}).inject(C)}}},getRow:function(F,E){var D={},A=this.grid,G=A.store,I=A.row,B=A.row.useHeaders()?this.getByName(I.options.headerColumn):null,H=[],C=0;this.columns.each(function(L,J){if(!L.isHidden()&&L!=B){if(L.options.renderer&&L.options.renderer.domInsert){H.push({column:L,index:C})}else{var M=L.options.renderer,K=M.options.formatter,N="";if(M.options.textTemplate){N=G.fillTemplate(null,M.options.textTemplate,M.columnsNeeded)}else{N=E.data.get(L.name)}if(K){N=K.format(N)}D["col"+J]=N}C++}});F.set("html",this.rowTemplate.substitute(D));H.each(function(J){F.childNodes[J.index].adopt(J.column.getHTML())})},calculateWidths:funct
 ion(){var E=null,A=0,C=0,B=this.grid.contentContainer.getContentBoxSize(),D=0;this.columns.each(function(G,F){var H=false;if(G.options.renderMode=="fixed"){G.calculateWidth()}else{if(G.options.renderMode=="fit"){G.calculateWidth(H)}else{if(G.options.renderMode=="expand"&&!$defined(E)){E=G}else{if($defined(G.options.width)){G.setWidth(G.options.width)}else{G.calculateWidth(H)}}}}if(!G.isHidden()){A+=Jx.getNumber(G.getCellWidth());if(H){C=G.getWidth()}}},this);if(B.width>A){if($defined(E)){D=B.width-A;if(Browser.Engine.gecko){D-=this.getColumnCount(true)}else{D-=2}if(D>=E.options.width){E.options.width=D;E.calculateWidth();E.setWidth(D,true);A+=D}else{E.setWidth(E.options.width)}}}this.grid.gridObj.setContentBoxSize({width:A});this.grid.colObj.setContentBoxSize({width:A})},createRules:function(C,A){var B=this.grid.row.options.rowHeight=="auto";this.columns.each(function(F,E){var D=A+" .jxGridCol"+E,G="";if(B){G="white-space: normal !important"}F.cellRule=Jx.Styles.insertCssRul
 e(D,G,C);F.cellRule.style.width=F.getCellWidth()+"px";D=A+" .jxGridCol"+E+" .jxGridCellContent";F.rule=Jx.Styles.insertCssRule(D,G,C);F.rule.style.width=F.getWidth()+"px"},this)},updateRule:function(B){var A=this.getByName(B);if(A.options.renderMode==="fit"){A.calculateWidth()}A.rule.style.width=A.getWidth()+"px";A.cellRule.style.width=A.getCellWidth()+"px"},getColumnCount:function(A){A=$defined(A)?false:true;var B=this.columns.length;if(A){this.columns.each(function(C){if(C.isHidden()){B-=1}},this)}return B},getIndexFromGrid:function(B){var D=this.options.useHeaders?this.grid.colTableBody.getFirst().getChildren():this.grid.gridTableBody.getFirst().getChildren(),E,C=-1,A=this;D.each(function(F){C++;var G=F.get("class").split(" ").filter(function(H){if(A.options.useHeaders){return H.test("jxColHead-")}else{return H.test("jxCol-")}});G.each(function(H){if(H.test(B)){E=C}})},this);return E}});Jx.Row=new Class({Family:"Jx.Row",Extends:Jx.Object,options:{useHeaders:false,alternat
 eRowColors:false,rowClasses:{odd:"jxGridRowOdd",even:"jxGridRowEven",all:"jxGridRowAll"},rowHeight:20,headerWidth:40,headerColumn:null},grid:null,heights:[],rules:$H(),parameters:["options","grid"],init:function(){this.parent();if($defined(this.options.grid)&&this.options.grid instanceof Jx.Grid){this.grid=this.options.grid}},getGridRowElement:function(E,D){var C=this.options,B=C.rowClasses,F=C.alternateRowColors?(E%2?B.even:B.odd):B.all,A=new Element("tr",{"class":"jxGridRow"+E+" "+F,html:D||""});return A},getRowHeaderCell:function(A){A=A?'<span class="jxGridCellContent">'+A+"</span>":"";return new Element("th",{"class":"jxGridRowHead",html:A})},getRowHeaderWidth:function(){var A,B;if(this.options.headerColumn){A=this.grid.columns.getByName(this.options.headerColumn);if(!$defined(A.getWidth())){A.calculateWidth(true)}B=A.getWidth()}else{B=this.options.headerWidth}return B},getHeight:function(C){var B=this.options.rowHeight,A;if($defined(this.heights[C])){B=this.heights[C]}e
 lse{if($defined(this.options.rowHeight)){if(this.options.rowHeight=="auto"){B=20;A=this.grid.gridTableBody.rows[C];if(A){B=A.getContentBoxSize().height}}else{if(Jx.type(this.options.rowHeight)!=="number"){B=20}}}}return B},calculateHeights:function(){if(this.options.rowHeight==="auto"||!$defined(this.options.rowHeight)){document.id(this.grid.gridTableBody).getChildren().each(function(C){C=document.id(C);var B=C.retrieve("jxRowData");var A=C.getContentBoxSize();this.heights[B.row]=A.height},this);document.id(this.grid.rowTableHead).getChildren().each(function(C){C=document.id(C);var B=C.retrieve("jxRowData");if(B){var A=C.getContentBoxSize();this.heights[B.row]=Math.max(this.heights[B.row],A.height);if(Browser.Engine.webkit){this.heights[B.row]-=1}}},this)}else{document.id(this.grid.rowTableHead).getChildren().each(function(B,A){this.heights[A]=this.options.rowHeight},this)}},useHeaders:function(){return this.options.useHeaders},getRowHeader:function(B){var A=this.getRowHeade
 rCell();A.store("jxCellData",{rowHeader:true,row:this.grid.model.getPosition()});B.add(A)},getRowHeaderColumn:function(){return this.options.headerColumn}});Jx.Plugin=new Class({Family:"Jx.Plugin",Extends:Jx.Object,options:{},attach:function(A){A.registerPlugin(this)},detach:function(A){A.deregisterPlugin(this)},changeText:function(A){if(this.busy){this.setBusy(false);this.setBusy(true)}}});Jx.Plugin.Grid={};Jx.Grid=new Class({Family:"Jx.Grid",Extends:Jx.Widget,Binds:["storeLoaded","clickColumnHeader","moveColumnHeader","clickRowHeader","moveRowHeader","clickCell","dblclickCell","moveCell","leaveGrid","resize","drawStore","scroll","addRow","removeRow","removeRows","updateRow","storeChangesCompleted"],pluginNamespace:"Grid",options:{parent:null,template:"<div class='jxWidget'><div class='jxGridContainer jxGridRowCol'></div><div class='jxGridContainer jxGridColumnsContainer'><table class='jxGridTable jxGridHeader jxGridColumns'><thead class='jxGridColumnHead'></thead></table><
 /div><div class='jxGridContainer jxGridHeader jxGridRowContainer'><table class='jxGridTable jxGridRows'><thead class='jxGridRowBody'></thead></table></div><div class='jxGridContainer jxGridContentContainer'><table class='jxGridTable jxGridContent'><tbody class='jxGridTableBody'></tbody></table></div></div>",columns:null,row:null,store:null},classes:$H({domObj:"jxWidget",columnContainer:"jxGridColumnsContainer",colObj:"jxGridColumns",colTableBody:"jxGridColumnHead",rowContainer:"jxGridRowContainer",rowObj:"jxGridRows",rowColContainer:"jxGridRowCol",rowTableBody:"jxGridRowBody",contentContainer:"jxGridContentContainer",gridObj:"jxGridContent",gridTableBody:"jxGridTableBody"}),columns:null,row:null,parameters:["store","options"],store:null,styleSheet:"JxGridStyles",hooks:null,uniqueId:null,init:function(){this.uniqueId=this.generateId("jxGrid_");this.store=this.options.store;var A=this.options,B;if($defined(A.row)){if(A.row instanceof Jx.Row){this.row=A.row;this.row.grid=this}e
 lse{if(Jx.type(A.row)=="object"){this.row=new Jx.Row($extend({grid:this},A.row))}}}else{this.row=new Jx.Row({grid:this})}if($defined(A.columns)){if(A.columns instanceof Jx.Columns){this.columns=A.columns;this.columns.grid=this}else{if(Jx.type(A.columns)==="object"){this.columns=new Jx.Columns($extend({grid:this},A.columns))}}}else{this.columns=new Jx.Columns({grid:this})}this.hooks=$H({gridScroll:false,gridColumnEnter:false,gridColumnLeave:false,gridColumnClick:false,gridRowEnter:false,gridRowLeave:false,gridRowClick:false,gridCellClick:false,gridCellDblClick:false,gridCellEnter:false,gridCellLeave:false,gridMouseLeave:false});this.storeEvents={storeDataLoaded:this.storeLoaded,storeRecordAdded:this.addRow,storeColumnChanged:this.updateRow,storeRecordRemoved:this.removeRow,storeMultipleRecordsRemoved:this.removeRows,storeChangesCompleted:this.storeChangesCompleted};this.parent()},wantEvent:function(A){var B=this.hooks.get(A);if(B===false){switch(A){case"gridColumnEnter":case"
 gridColumnLeave":this.colObj.addEvent("mousemove",this.moveColumnHeader);this.hooks.set({gridColumnEnter:true,gridColumnLeave:true});break;case"gridColumnClick":this.colObj.addEvent("click",this.clickColumnHeader);this.hooks.set({gridColumnClick:true});break;case"gridRowEnter":case"gridRowLeave":this.rowObj.addEvent("mousemove",this.moveRowHeader);this.hooks.set({gridRowEnter:true,gridRowLeave:true});break;case"gridRowClick":this.rowObj.addEvent("click",this.clickRowHeader);this.hooks.set({gridRowClick:true});break;case"gridCellEnter":case"gridCellLeave":this.gridObj.addEvent("mousemove",this.moveCell);this.hooks.set({gridCellEnter:true,gridCellLeave:true});break;case"gridCellClick":this.gridObj.addEvent("click",this.clickCell);this.hooks.set("gridCellClick",true);break;case"gridCellDblClick":this.gridObj.addEvent("dblclick",this.dblclickCell);this.hooks.set("gridCellDblClick",true);break;case"gridMouseLeave":this.rowObj.addEvent("mouseleave",this.leaveGrid);this.colObj.addE
 vent("mouseleave",this.leaveGrid);this.gridObj.addEvent("mouseleave",this.leaveGrid);this.hooks.set("gridMouseLeave",true);break;case"gridScroll":this.contentContainer.addEvent("scroll",this.scroll);default:break}}},scroll:function(){this.columnContainer.scrollLeft=this.contentContainer.scrollLeft;this.rowContainer.scrollTop=this.contentContainer.scrollTop},render:function(){if(this.domObj){this.redraw();return }this.parent();var A=this.store;this.domObj.addClass(this.uniqueId);new Jx.Layout(this.domObj,{onSizeChange:this.resize});if(A instanceof Jx.Store){A.addEvents(this.storeEvents);if(A.loaded){this.storeLoaded(A)}}if(!this.columns.useHeaders()){this.columnContainer.dispose()}else{this.wantEvent("gridScroll")}if(!this.row.useHeaders()){this.rowContainer.dispose()}else{this.wantEvent("gridScroll")}this.contentContainer.setStyle("overflow","auto");this.hooks.each(function(C,B){if(C){this.hooks.set(B,false);this.wantEvent(B)}},this);if(document.id(this.options.parent)){this
 .addTo(this.options.parent);this.resize()}},resize:function(){var C=this.domObj.getParent(),D=C.getSize(),A=0,B=0;if(this.columns.useHeaders()){A=this.columns.getHeaderHeight()}if(this.row.useHeaders()){B=this.row.getRowHeaderWidth()}this.rowColContainer.setBorderBoxSize({width:B,height:A});this.columnContainer.setStyles({top:0,left:B}).setBorderBoxSize({width:D.x-B,height:A});this.rowContainer.setStyles({top:A,left:0}).setBorderBoxSize({width:B,height:D.y-A});this.contentContainer.setStyles({top:A,left:B}).setBorderBoxSize({width:D.x-B,height:D.y-A})},setStore:function(A){if(this.store){this.store.removeEvents(this.storeEvents)}if(A instanceof Jx.Store){this.store=A;A.addEvents(this.storeEvents);if(A.loaded){this.storeLoaded(A)}this.render();this.domObj.resize()}else{this.destroyGrid()}},getStore:function(){return this.store},storeLoaded:function(A){this.redraw()},storeChangesCompleted:function(A){if(A&&A.successful){}},redraw:function(){var A=this.store,C="",D,B=[],E=this.
 row.useHeaders();this.fireEvent("beginCreateGrid");this.gridObj.getElement("tbody").empty();this.hoverColumn=this.hoverRow=this.hoverCell=null;A.options.columns.each(function(G,F){if(!this.columns.getByName(G.name)){var J=new Jx.Grid.Renderer.Text(),K=$defined(G.format)?G.format:null,I="<span class='jxGridCellContent'>"+($defined(G.label)?G.label:G.name).capitalize()+"</span>",H;if($defined(G.renderer)){if($type(G.renderer)=="string"){if(Jx.Grid.Renderer[G.renderer.capitalize()]){J=new Jx.Grid.Renderer[G.renderer.capitalize()]()}}else{if($type(G.renderer)=="object"&&$defined(G.renderer.type)&&Jx.Grid.Renderer[G.renderer.type.capitalize()]){J=new Jx.Grid.Renderer[G.renderer.type.capitalize()](G.renderer)}}}if(K){if($type(K)=="string"&&$defined(Jx.Formatter[K.capitalize()])){J.options.formatter=new Jx.Formatter[K.capitalize()]()}else{if($type(K)=="object"&&$defined(K.type)&&$defined(Jx.Formatter[K.type.capitalize()])){J.options.formatter=new Jx.Formatter[K.type.capitalize()](K
 )}}}H=new Jx.Column({grid:this,template:I,renderMode:$defined(G.renderMode)?G.renderMode:$defined(G.width)?"fixed":"fit",width:$defined(G.width)?G.width:null,isEditable:$defined(G.editable)?G.editable:false,isSortable:$defined(G.sortable)?G.sortable:false,isResizable:$defined(G.resizable)?G.resizable:false,isHidden:$defined(G.hidden)?G.hidden:false,name:G.name||"",renderer:J});B.push(H)}},this);this.columns.addColumns(B);if(this.columns.useHeaders()){D=new Element("tr");this.columns.getHeaders(D);D.adopt(new Element("th",{"class":"jxGridColHead",html:"&nbsp",styles:{width:1000}}));this.colObj.getElement("thead").empty().adopt(D)}this.columns.calculateWidths();this.columns.createRules(this.styleSheet+"Columns","."+this.uniqueId);this.drawStore();this.fireEvent("doneCreateGrid")},addRow:function(C,B,A){if(this.store.loaded){if(A==="bottom"){this.store.last()}else{this.store.first()}this.drawRow(B,this.store.index,A)}},updateRow:function(B){var A=this.store.getRecord(B);this.dr
 awRow(A,B,"replace")},removeRow:function(A,B){this.gridObj.deleteRow(B);this.rowObj.deleteRow(B)},removeRows:function(A,D,C){for(var B=D;B<=C;B++){this.removeRow(A,D)}},setColumnWidth:function(B,A){if(B){B.width=A;if(B.rule){B.rule.style.width=A+"px"}if(B.cellRule){B.cellRule.style.width=A+"px"}}},drawStore:function(){var A=this.row.useHeaders(),B;this.domObj.resize();this.gridTableBody.empty();if(A){this.rowTableBody.empty()}this.store.each(function(C,D){this.store.index=D;this.drawRow(C,D)},this);if(A){B=new Element("tr",{styles:{height:1000}});B.adopt(new Element("th",{"class":"jxGridRowHead",html:"&nbsp"}));this.rowTableBody.adopt(B)}},drawRow:function(D,I,S){var B=this.columns,K=this.gridTableBody,H=this.row,G=this.store,E=H.useHeaders(),O=H.options.rowHeight=="auto",R=this.rowTableBody,J,C,P,Q,L,A,F,M=I+1,N;if(!$defined(S)||!["top","bottom","replace"].contains(S)){S="bottom"}A=H.getGridRowElement(I,"");if(S=="replace"&&I<K.childNodes.length){A.inject(K.childNodes[I],"a
 fter");K.childNodes[I].dispose()}else{A.inject(K,S)}B.getRow(A,D);if(E){if(H.options.headerColumn){J=B.getByName(H.options.headerColumn);P=J.options.renderer;if(!P.domInsert){Q=J.options.formatter;C=B.columns.indexOf(J);L=function(T){var U={},V="";if(P.options.textTemplate){V=G.fillTemplate(null,P.options.textTemplate,P.columnsNeeded)}else{V=T.data.get(J.name)}U["col"+C]=V;return U};M=J.getTemplate(C).substitute(L(D))}else{M=""}}F=H.getRowHeaderCell(M);if(H.options.headerColumn&&P.domInsert){F.adopt(J.getHTML())}N=new Element("tr").adopt(F);if(S=="replace"&&I<R.childNodes.length){N.inject(R.childNodes[I],"after");R.childNodes[I].dispose()}else{N.inject(R,S)}if(O){N.setBorderBoxSize({height:A.getBorderBoxSize().height})}}this.fireEvent("gridDrawRow",[I,D])},clickColumnHeader:function(B){var A=B.target;if(A.getParent("thead")){A=A.tagName=="TH"?A:A.getParent("th");this.fireEvent("gridColumnClick",A)}},moveColumnHeader:function(B){var A=B.target;A=A.tagName=="TH"?A:A.getParent(
 "th.jxGridColHead");if(A){if(this.hoverColumn!=A){if(this.hoverColumn){this.fireEvent("gridColumnLeave",this.hoverColumn)}if(!A.hasClass("jxGridColHead")){this.leaveGrid(B)}else{this.hoverColumn=A;this.fireEvent("gridColumnEnter",A)}}}},clickRowHeader:function(B){var A=B.target;if(A.getParent("tbody")){A=A.tagName=="TH"?A:A.getParent("th");this.fireEvent("gridRowClick",A)}},moveRowHeader:function(B){var A=B.target;A=A.tagName=="TH"?A:A.getParent("th.jxGridRowHead");if(A){if(this.hoverRow!=A){if(this.hoverRow){this.fireEvent("gridRowLeave",this.hoverRow)}if(!A.hasClass("jxGridRowHead")){this.leaveGrid(B)}else{this.hoverRow=A;this.fireEvent("gridRowEnter",A)}}}},clickCell:function(B){var A=B.target;if(A.getParent("tbody")){A=A.tagName=="TD"?A:A.getParent("td");this.fireEvent("gridCellClick",A)}},dblclickCell:function(B){var A=B.target;if(A.getParent("tbody")){A=A.tagName=="TD"?A:A.getParent("td");this.fireEvent("gridCellDblClick",A)}},moveCell:function(F){var E=F.target,D,A,G,
 B,C;E=E.tagName=="TD"?E:E.getParent("td.jxGridCell");if(E){if(this.hoverCell!=E){if(this.hoverCell){this.fireEvent("gridCellLeave",this.hoverCell)}if(!E.hasClass("jxGridCell")){this.leaveGrid(F)}else{this.hoverCell=E;this.getCellData(E);this.fireEvent("gridCellEnter",E)}}}},getCellData:function(A){var D=null,B,C,E;if(!A.hasClass("jxGridCell")){A=A.getParent("td.jxGridCell")}if(A){body=this.gridTableBody;E=body.getChildren().indexOf(A.getParent("tr"));this.columns.columns.some(function(G,F){if(A.hasClass("jxGridCol"+F)){B=F;C=G;return true}return false});D={row:E,column:C,index:B};A.store("jxCellData",D)}return D},leaveGrid:function(A){this.hoverCell=null;this.fireEvent("gridMouseLeave")},changeText:function(A){this.parent();this.render()},addEvent:function(A,B){this.wantEvent(A);this.parent(A,B)}});Jx.Grid.Renderer=new Class({Family:"Jx.Grid.Renderer",Extends:Jx.Widget,parameters:["options"],options:{deferRender:true,template:'<span class="jxGridCellContent"></span>'},attach
 ed:null,domInsert:false,classes:$H({domObj:"jxGridCellContent"}),column:null,init:function(){this.parent();this.attached=false},render:function(){this.parent()},setColumn:function(A){if(A instanceof Jx.Column){this.column=A}}});Jx.Grid.Renderer.Text=new Class({Family:"Jx.Grid.Renderer.Text",Extends:Jx.Grid.Renderer,options:{formatter:null,textTemplate:null,css:null},store:null,columnsNeeded:null,init:function(){this.parent();var A=this.options,B;if($defined(A.formatter)&&!(A.formatter instanceof Jx.Formatter)){B=Jx.type(A.formatter);if(B==="object"){if(!$defined(A.formatter.options)){A.formatter.options={}}A.formatter=new Jx.Formatter[A.formatter.name](A.formatter.options)}}},setColumn:function(A){this.parent();this.store=A.grid.getStore();this.attached=true;if($defined(this.options.textTemplate)){this.columnsNeeded=this.store.parseTemplate(this.options.textTemplate)}},render:function(){this.parent();var A="";if($defined(this.options.textTemplate)){if(!$defined(this.columnsN
 eeded)||(Jx.type(this.columnsNeeded)==="array"&&this.columnsNeeded.length===0)){this.columnsNeeded=this.store.parseTemplate(this.options.textTemplate)}A=this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded)}if($defined(this.options.formatter)){A=this.options.formatter.format(A)}this.domObj.set("html",A);if($defined(this.options.css)&&Jx.type(this.options.css)==="function"){this.domObj.addClass(this.options.css.run(A))}else{if($defined(this.options.css)&&Jx.type(this.options.css)==="string"){this.domObj.addClass(this.options.css)}}}});Jx.Grid.Renderer.Checkbox=new Class({Family:"Jx.Grid.Renderer.Checkbox",Extends:Jx.Grid.Renderer,Binds:["onBlur","onChange"],options:{useStore:false,field:null,updateStore:false,checkboxOptions:{template:'<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',name:""}},domInsert:true,init:function(){this.parent()},render:function(){this.parent();var A=new Jx.Field.Checkbox(this.options.checkboxOptions)
 ;this.domObj.adopt(document.id(A));if(this.options.useStore){A.setValue(this.store.get(this.options.field))}A.addEvents({blur:this.onBlur,change:this.onChange})},setColumn:function(A){this.column=A;if(this.options.useStore){this.store=this.column.grid.getStore();this.attached=true}},onBlur:function(A){if(this.options.updateStore){this.updateStore(A)}this.column.grid.fireEvent("checkBlur",[this.column,A])},onChange:function(A){if(this.options.updateStore){this.updateStore(A)}this.fireEvent("change",[this.column,A])},updateStore:function(C){var B=C.getValue();var A=document.id(C).getParent().retrieve("jxCellData");var D=A.row;if(this.store.get(this.options.field,D)!==B){this.store.set(this.options.field,B,D)}}});Jx.Grid.Renderer.Button=new Class({Family:"Jx.Grid.Renderer.Button",Extends:Jx.Grid.Renderer,Binds:[],options:{template:'<span class="buttons"></span>',buttonOptions:null},domInsert:true,classes:$H({domObj:"buttons"}),init:function(){this.parent()},render:function(){th
 is.parent();$A(this.options.buttonOptions).each(function(B){var A=new Jx.Button(B);this.domObj.grab(document.id(A))},this)}});Jx.Plugin.Grid.Selector=new Class({Family:"Jx.Plugin.Grid.Selector",Extends:Jx.Plugin,name:"Selector",Binds:["select","checkSelection","checkAll","afterGridRender","onCellClick","sort","updateCheckColumn","updateSelectedRows"],options:{cell:false,row:false,column:false,multiple:false,useCheckColumn:false,checkAsHeader:false,sortableColumn:false},domInsert:true,selected:null,init:function(){this.parent();this.selected=$H({cells:[],columns:[],rows:[],rowHeads:[],columnHeads:[]})},attach:function(B){if(!$defined(B)&&!(B instanceof Jx.Grid)){return }this.parent(B);var A=this.options,C;this.grid=B;this.grid.addEvent("gridSortFinished",this.updateSelectedRows);if(A.useCheckColumn){B.addEvent("gridDrawRow",this.updateCheckColumn);C='<span class="jxGridCellContent">';if(A.multiple){C+='<span class="jxInputContainer jxInputContainerCheck"><input class="jxInput
 Check" type="checkbox" name="checkAll" id="checkAll"/></span>'}else{C+="</span>"}C+="</span>";this.checkColumn=new Jx.Column({template:C,renderMode:"fixed",width:20,renderer:null,name:"selection",isSortable:A.sortableColumn||false,sort:A.sortableColumn?this.sort:null},B);this.checkColumn.options.renderer=this;B.columns.columns.reverse();B.columns.columns.push(this.checkColumn);B.columns.columns.reverse();if(A.checkAsHeader){this.oldHeaderColumn=B.row.options.headerColumn;B.row.options.useHeaders=true;B.row.options.headerColumn="selection";if(A.multiple){B.addEvent("doneCreateGrid",this.afterGridRender)}}if(A.multiple){document.id(this.checkColumn).getElement("input").addEvents({change:this.checkAll})}}else{B.addEvent("gridCellClick",this.onCellClick)}},render:function(){this.domObj=new Element("span",{"class":"jxGridCellContent"});new Element("input",{"class":"jxGridSelector",type:"checkbox",events:{change:this.checkSelection}}).inject(this.domObj)},toElement:function(){retu
 rn this.domObj},updateCheckColumn:function(B,A){var E=this.selected.get("rows").contains(B),C=this.grid.gridTableBody.rows,D=document.id((B>=0&&B<C.length)?C[B]:null);if(D){D.store("jxRowData",{row:B});if(E){D.addClass("jxGridRowSelected")}else{D.removeClass("jxGridRowSelected")}this.setCheckField(B,E)}},afterGridRender:function(){if(this.options.checkAsHeader){var A=document.id(this.checkColumn).clone();A.getElement("input").addEvent("change",this.checkAll);this.grid.rowColContainer.adopt(A)}this.grid.removeEvent("doneCreateGrid",this.afterGridRender)},detach:function(){var C=this.grid,B=this.options,A;if(C){C.gridTableBody.removeEvents({click:this.onCellClick});if(this.checkColumn){C.columns.columns.erase(this.checkColumn);this.checkColumn.destroy();this.checkColumn=null}if(B.useCheckColumn){C.removeEvent("gridDrawRow",this.updateCheckColumn);if(B.checkAsHeader){C.row.options.headerColumn=this.oldHeaderColumn}}}this.grid.removeEvent("gridSortFinished",this.updateSelectedRo
 ws);this.grid=null},activate:function(A){this.options[A]=true},deactivate:function(C){var A=this.grid.gridTableBody.rows,D=this.selected,B;this.options[C]=false;if(C==="cell"){D.get("cells").each(function(E){E.removeClass("jxGridCellSelected")});D.set("cells",[])}else{if(C==="row"){this.getSelectedRows().each(function(E){idx=E.retrieve("jxRowData").row;E.removeClass("jxGridRowSelected");this.setCheckField(idx,false)},this);D.set("rows",[]);D.get("rowHeads").each(function(E){E.removeClass("jxGridRowHeaderSelected")});D.set("rowHeads",[])}else{D.get("columns").each(function(E){for(B=0;B<A.length;B++){A[B].cells[E].removeClass("jxGridColumnSelected")}});D.set("columns",[]);D.get("columnHeads").each(function(E){E.removeClass("jxGridColumnHeaderSelected")},this);D.set("columnHeads",[])}}},onCellClick:function(A){if(A){this.select(A)}},select:function(A){var D=A.retrieve("jxCellData"),C=this.options,B;if(C.cell&&$defined(D.row)&&$defined(D.index)){this.selectCell(A)}if(C.row&&$def
 ined(D.row)){this.selectRow(D.row)}if(C.column&&$defined(D.index)){if(this.grid.row.useHeaders()){this.selectColumn(D.index-1)}else{this.selectColumn(D.index)}}},selectCell:function(A){if(!this.options.cell){return }var B=this.selected.get("cells");if(A.hasClass("jxGridCellSelected")){A.removeClass("jxGridCellSelected");B.erase(A);this.fireEvent("unselectCell",A)}else{A.addClass("jxGridCellSelected");B.push(A);this.fireEvent("selectCell",A)}},updateSelectedRows:function(){if(!this.options.row){return }var A=this.options,C=this.grid.gridTableBody.rows,D=[];for(var B=0;B<C.length;B++){if(C[B].hasClass("jxGridRowSelected")){D.push(B)}}this.selected.set("rows",D)},selectRow:function(F,G){if(!this.options.row){return }var A=this.options,C=this.grid.gridTableBody.rows,E=document.id((F>=0&&F<C.length)?C[F]:null),D=this.selected.get("rows"),G=$defined(G)?G:false;if(E){if(E.hasClass("jxGridRowSelected")){E.removeClass("jxGridRowSelected");this.setCheckField(F,false);if(A.multiple&&A.
 useCheckColumn){if(A.checkAsHeader){document.id(this.grid.rowColContainer).getElement("input").removeProperty("checked")}else{document.id(this.checkColumn).getElement("input").removeProperty("checked")}}D.erase(F);if(!G){this.fireEvent("unselectRow",F)}}else{E.store("jxRowData",{row:F});D.push(F);E.addClass("jxGridRowSelected");this.setCheckField(F,true);if(!G){this.fireEvent("selectRow",F)}}if(!this.options.multiple){var B=[];this.getSelectedRows().each(function(I){var H;if(I!==E){H=I.retrieve("jxRowData").row;I.removeClass("jxGridRowSelected");this.setCheckField(H,false);D.erase(I);B.push(H);if(!G){this.fireEvent("unselectRow",I)}}},this);if(B.length&&!G){this.fireEvent("unselectRows",[B])}}}this.selectRowHeader(F)},setCheckField:function(G,F){var E=this.grid,D=this.options,B,C,A;if(D.useCheckColumn){if(D.checkAsHeader){A=document.id(E.rowTableBody.rows[G].cells[0])}else{C=E.columns.getIndexFromGrid(this.checkColumn.name);A=document.id(E.gridTableBody.rows[G].cells[C])}B=A
 .getElement(".jxGridSelector");B.set("checked",F)}},selectRowHeader:function(D){if(!this.grid.row.useHeaders()){return }var C=this.grid.rowTableBody.rows,A=document.id((D>=0&&D<C.length)?C[D].cells[0]:null),B;if(!A){return }B=this.selected.get("rowHeads");if(B.contains(A)){A.removeClass("jxGridRowHeaderSelected");B.erase(A)}else{A.addClass("jxGridRowHeaderSelected");B.push(A)}if(!this.options.multiple){B.each(function(E){if(E!==A){E.removeClass("jxGridRowHeaderSelected");B.erase(E)}},this)}},selectColumn:function(B){var E=this.grid.gridTableBody,D=this.selected.get("columns"),A="",C;if(B>=0&&B<E.rows[0].cells.length){if(D.contains(B)){A="removeClass";D.erase(B);this.fireEvent("unselectColumn",B)}else{A="addClass";D.push(B);this.fireEvent("selectColumn",B)}for(C=0;C<E.rows.length;C++){E.rows[C].cells[B][A]("jxGridColumnSelected")}if(!this.options.multiple){D.each(function(F){if(F!==B){for(C=0;C<E.rows.length;C++){E.rows[C].cells[F].removeClass("jxGridColumnSelected")}D.erase(
 F);this.fireEvent("unselectColumn",F)}},this)}this.selectColumnHeader(B)}},selectColumnHeader:function(B){var C=this.grid.colTableBody;if(C.length===0||!this.grid.row.useHeaders()){return }var A=(B>=0&&B<C[0].cells.length)?C[0].cells[B]:null;if(A===null){return }A=document.id(A);cells=this.selected.get("columnHeads");if(cells.contains(A)){A.removeClass("jxGridColumnHeaderSelected");cells.erase(A)}else{A.addClass("jxGridColumnHeaderSelected");cells.push(A)}if(!this.options.multiple){cells.each(function(D){if(D!==A){D.removeClass("jxGridColumnHeaderSelected");cells.erase(D)}})}},checkSelection:function(B){var A=B.target.getParent("tr"),C;if(A){C=A.getParent().getChildren().indexOf(A);this.selectRow(C)}},checkAll:function(){var B=this.grid,A,F,C=[],E=this.options.checkAsHeader?B.rowColContainer.getElement("input").get("checked"):this.checkColumn.domObj.getElement("input").get("checked"),D=E?"selectRows":"unselectRows";if(this.options.checkAsHeader){A=0;F=B.rowTableBody.rows}els
 e{A=B.columns.getIndexFromGrid(this.checkColumn.name);F=B.gridTableBody.rows}$A(F).each(function(J,G){var H=J.cells[A].getElement("input");if($defined(H)){var I=H.get("checked");if(I!==E){this.selectRow(G,true);C.push(G)}}},this);this.fireEvent(D,[C])},sort:function(D){var A=this.grid,I=A.store,G=I.data,E=A.gridTableBody,B=E.getParent(),H=A.row.useHeaders(),C=A.rowTableBody,J=C.getParent(),F=this.getSelectedRows();if(!this.options.row||F.length==0){console.log("not sorting by selection, nothing to sort");return }I.each(function(K,L){K.dom={cell:E.childNodes[L],row:H?C.childNodes[L]:null}});E.dispose();if(H){C.dispose()}F.sort(function(L,K){return L.retrieve("jxRowData").row-K.retrieve("jxRowData").row}).each(function(K){console.log("moving row "+K.retrieve("jxRowData").row+" to beginning of array");G.unshift(G.splice(K.retrieve("jxRowData").row,1)[0])});if(D=="desc"){G.reverse()}I.each(function(K,L){K.dom.cell.inject(E);K.dom.cell.store("jxRowData",{row:L});if(H){K.dom.row.i
 nject(C)}});if(B){B.adopt(E)}if(H&&J){J.adopt(C)}},getSelectedRows:function(){var C=[],A=this.selected.get("rows"),B=this.grid.gridTableBody.rows;A.each(function(E){var D=document.id((E>=0&&E<B.length)?B[E]:null);if(D){C.push(D)}});return C}});Jx.Plugin.Grid.Prelighter=new Class({Extends:Jx.Plugin,name:"Prelighter",options:{cell:false,row:false,column:false,rowHeader:false,columnHeader:false},init:function(){this.parent();this.bound.lighton=this.lighton.bind(this);this.bound.lightoff=this.lightoff.bind(this);this.bound.mouseleave=this.mouseleave.bind(this)},attach:function(A){if(!$defined(A)&&!(A instanceof Jx.Grid)){return }this.parent(A);this.grid=A;this.grid.addEvent("gridCellEnter",this.bound.lighton);this.grid.addEvent("gridCellLeave",this.bound.lightoff);this.grid.addEvent("gridRowEnter",this.bound.lighton);this.grid.addEvent("gridRowLeave",this.bound.lightoff);this.grid.addEvent("gridColumnEnter",this.bound.lighton);this.grid.addEvent("gridColumnLeave",this.bound.ligh
 toff);this.grid.addEvent("gridMouseLeave",this.bound.mouseleave)},detach:function(){if(this.grid){this.grid.removeEvent("gridCellEnter",this.bound.lighton);this.grid.removeEvent("gridCellLeave",this.bound.lightoff);this.grid.removeEvent("gridRowEnter",this.bound.lighton);this.grid.removeEvent("gridRowLeave",this.bound.lightoff);this.grid.removeEvent("gridColumnEnter",this.bound.lighton);this.grid.removeEvent("gridColumnLeave",this.bound.lightoff);this.grid.removeEvent("gridMouseLeave",this.bound.mouseleave)}this.grid=null},activate:function(A){this.options[A]=true},deactivate:function(A){this.options[A]=false},lighton:function(A){this.light(A,true)},lightoff:function(A){this.light(A,false)},light:function(A,B){var D=A.getParent(),E=D.getParent().getChildren().indexOf(D),C=A.getParent().getChildren().indexOf(A);if(this.options.cell){this.prelightCell(A,B)}if(this.options.row){this.prelightRow(E,B)}if(this.options.column){this.prelightColumn(C,B)}if(this.options.rowHeader){thi
 s.prelightRowHeader(E,B)}if(this.options.columnHeader){this.prelightColumnHeader(C,B)}},prelightRowHeader:function(B,A){if($defined(this.prelitRowHeader)&&!A){this.prelitRowHeader.removeClass("jxGridRowHeaderPrelight")}else{if(A){this.prelitRowHeader=(B>=0&&B<this.grid.rowTableBody.rows.length)?this.grid.rowTableBody.rows[B].cells[0]:null;if(this.prelitRowHeader){this.prelitRowHeader.addClass("jxGridRowHeaderPrelight")}}}},prelightColumnHeader:function(B,A){if(this.grid.colTableBody.rows.length===0){return }if($defined(this.prelitColumnHeader)&&!A){this.prelitColumnHeader.removeClass("jxGridColumnHeaderPrelight")}else{if(A){this.prelitColumnHeader=(B>=0&&B<this.grid.colTableBody.rows[0].cells.length)?this.grid.colTableBody.rows[0].cells[B]:null;if(this.prelitColumnHeader){this.prelitColumnHeader.addClass("jxGridColumnHeaderPrelight")}}}},prelightRow:function(B,A){if($defined(this.prelitRow)&&!A){this.prelitRow.removeClass("jxGridRowPrelight")}else{if(A){this.prelitRow=(B>=0&
 &B<this.grid.gridTableBody.rows.length)?this.grid.gridTableBody.rows[B]:null;if(this.prelitRow){this.prelitRow.addClass("jxGridRowPrelight")}}}this.prelightRowHeader(B,A)},prelightColumn:function(B,A){if(B>=0&&B<this.grid.gridTableBody.rows[0].cells.length){if($defined(this.prelitColumn)&&!A){for(var C=0;C<this.grid.gridTableBody.rows.length;C++){this.grid.gridTableBody.rows[C].cells[this.prelitColumn].removeClass("jxGridColumnPrelight")}}else{if(A){this.prelitColumn=B;for(C=0;C<this.grid.gridTableBody.rows.length;C++){this.grid.gridTableBody.rows[C].cells[B].addClass("jxGridColumnPrelight")}}}this.prelightColumnHeader(B,A)}},prelightCell:function(A,B){if($defined(this.prelitCell)&&!B){this.prelitCell.removeClass("jxGridCellPrelight")}else{if(B){this.prelitCell=A;if(this.prelitCell){this.prelitCell.addClass("jxGridCellPrelight")}}}},mouseleave:function(){if($defined(this.prelitCell)){this.prelitCell.removeClass("jxGridCellPrelight")}if($defined(this.prelitColumn)){for(var A=
 0;A<this.grid.gridTableBody.rows.length;A++){this.grid.gridTableBody.rows[A].cells[this.prelitColumn].removeClass("jxGridColumnPrelight")}}if($defined(this.prelitRow)){this.prelitRow.removeClass("jxGridRowPrelight")}if($defined(this.prelitColumnHeader)){this.prelitColumnHeader.removeClass("jxGridColumnHeaderPrelight")}if($defined(this.prelitRowHeader)){this.prelitRowHeader.removeClass("jxGridRowHeaderPrelight")}}});Jx.Plugin.Grid.Sorter=new Class({Family:"Jx.Plugin.Grid.Sorter",Extends:Jx.Plugin,name:"Sorter",Binds:["sort","modifyHeaders"],current:null,direction:null,options:{sortableClass:"jxColSortable",ascendingClass:"jxGridColumnSortedAsc",descendingClass:"jxGridColumnSortedDesc"},attach:function(A){if(!$defined(A)&&!(A instanceof Jx.Grid)){return }this.parent(A);this.grid=A;this.grid.addEvent("gridColumnClick",this.sort);this.grid.addEvent("doneCreateGrid",this.modifyHeaders)},detach:function(){if(this.grid){this.grid.removeEvent("gridColumnClick",this.sort)}this.grid=n
 ull},modifyHeaders:function(){var B=this.grid,C=B.colObj,A=B.store,D=this.options.sortableClass;if(B.columns.useHeaders()){B.columns.columns.each(function(F,E){if(!F.isHidden()&&F.isSortable()){var G=C.getElement(".jxGridCol"+E);G.addClass(D)}})}},sort:function(D){var J=this.current,A=this.grid,G=A.gridTableBody,B=G.getParent(),E=A.rowTableBody,L=E.getParent(),I=A.row.useHeaders(),K=A.store,M=K.getStrategy("sort"),H=D.retrieve("jxCellData"),F="asc",C=this.options;if($defined(H.column)&&H.column.isSortable()){if(D.hasClass(C.ascendingClass)){D.removeClass(C.ascendingClass).addClass(C.descendingClass);F="desc"}else{if(D.hasClass(C.descendingClass)){D.removeClass(C.descendingClass).addClass(C.ascendingClass)}else{D.addClass(C.ascendingClass)}}if(J&&D!=J){J.removeClass(C.ascendingClass).removeClass(C.descendingClass)}this.current=D;this.grid.fireEvent("gridSortStarting");if($defined(H.column.options.sort)&&Jx.type(H.column.options.sort)=="function"){H.column.options.sort(F)}else
 {if(M){G.dispose();if(I){E.dispose()}K.each(function(N,O){N.dom={cell:G.childNodes[O],row:I?E.childNodes[O]:null}});M.sort(H.column.name,null,F);K.each(function(N,O){N.dom.cell.inject(G);if(I){N.dom.row.inject(E)}});if(B){B.adopt(G)}if(I&&L){L.adopt(E)}}}this.grid.fireEvent("gridSortFinished")}}});Jx.Plugin.Grid.Resize=new Class({Extends:Jx.Plugin,name:"Resize",Binds:["createHandles","removeHandles"],options:{column:false,row:false,tooltip:""},els:{column:[],row:[]},drags:{column:[],row:[]},attach:function(A){if(!$defined(A)&&!(A instanceof Jx.Grid)){return }this.parent(A);this.grid=A;if(A.columns.useHeaders()){this.grid.addEvent("doneCreateGrid",this.createHandles);this.grid.addEvent("beginCreateGrid",this.removeHandles);this.createHandles()}},detach:function(){this.parent();if(this.grid){this.grid.removeEvent("doneCreateGrid",this.createHandles);this.grid.removeEvent("beginCreateGrid",this.removeHandles)}this.grid=null},activate:function(A){if($defined(this.options[A])){th
 is.options[A]=true}if(this.grid.columns.useHeaders()){this.createHandles()}},deactivate:function(A){if($defined(this.options[A])){this.options[A]=false}this.createHandles()},removeHandles:function(){["column","row"].each(function(A){this.els[A].each(function(B){B.dispose()});this.els[A]=[];this.drags[A].each(function(B){B.detach()});this.drags[A]=[]},this)},createHandles:function(){var B=this.grid,A=B.store;this.removeHandles();if(this.options.column&&B.columns.useHeaders()){B.columns.columns.each(function(E,C){if(E.isResizable()&&!E.isHidden()){var D=B.colObj.getElement(".jxGridCol"+C+" .jxGridCellContent");var F=new Element("div",{"class":"jxGridColumnResize",title:this.options.tooltip==""?this.getText({set:"Jx",key:"plugin.resize",value:"tooltip"}):this.getText(this.options.tooltip),events:{dblclick:function(){}}}).inject(D);this.els.column.push(F);this.drags.column.push(new Drag(F,{limit:{y:[0,0]},snap:2,onBeforeStart:function(H){var G=H.getPosition(H.parentNode).x.toInt
 ();H.setStyles({left:G,right:null})},onStart:function(H){var G=H.getPosition(H.parentNode).x.toInt();H.setStyles({left:G,right:null})},onDrag:function(H){var G=H.getPosition(H.parentNode).x.toInt();E.setWidth(G)},onComplete:function(G){G.setStyle("left",null)}}))}},this)}},changeText:function(B){this.parent();var A=this.options.tooltip==""?this.getText({set:"Jx",key:"plugin.resize",value:"tooltip"}):this.getText(this.options.tooltip);["column","row"].each(function(C){this.els[C].each(function(D){D.set("title",A)})},this)}});Jx.Plugin.Grid.Editor=new Class({Extends:Jx.Plugin,name:"Editor",Binds:["activate","deactivate","changeText","onCellClick"],options:{enabled:true,blurDelay:500,popup:{use:true,useLabels:false,useButtons:true,button:{submit:{label:"",image:"images/accept.png"},cancel:{label:"",image:"images/cancel.png"}},template:'<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'},validate:true,fieldOptions:[{field:"*",type:"Text",opti
 ons:{},validatorOptions:{validators:[],validateOnBlur:true,validateOnChange:false}}],fieldFormatted:true,cellChangeFx:{use:true,success:"#090",error:"#F00"},cellOutline:{use:true,style:"2px solid #88c3e7"},useKeyboard:true,keys:{"ctrl+shift+enter":"saveNGoUp",tab:"saveNGoRight","ctrl+enter":"saveNGoDown","shift+tab":"saveNGoLeft",enter:"saveNClose","ctrl+up":"cancelNGoUp","ctrl+right":"cancelNGoRight","ctrl+down":"cancelNGoDown","ctrl+left":"cancelNGoLeft",esc:"cancelNClose",up:"valueIncrement",down:"valueDecrement"},keyboardMethods:{},keypressLoop:true,linkClickListener:true},classes:["jxGridEditorPopup","jxGridEditorPopupInnerWrapper"],activeCell:{field:null,cell:null,span:null,oldValue:null,newValue:{data:null,error:false},timeoutId:null,data:{},fieldOptions:{}},popup:{domObj:null,innerWarpper:null,closeIcon:null,button:{submit:null,cancel:null}},keyboard:null,keyboardMethods:{},init:function(){this.parent()},attach:function(D){if(!$defined(D)&&!(D instanceof Jx.Grid)){re
 turn }this.parent(D);this.grid=D;this.grid.addEvent("gridCellClick",this.onCellClick);if(this.getFieldOptionsByColName("*").field!="*"){this.options.fieldOptions.unshift({field:"*",type:"Text",options:{},validatorOptions:{validators:[],validateOnBlur:true,validateOnChange:false}})}var A=this;this.keyboardMethods={saveNClose:function(E){if(A.activeCell.fieldOptions.type!="Textarea"||(A.activeCell.fieldOptions.type=="Textarea"&&E.key!="enter")){A.deactivate()}},saveNGoUp:function(E){E.preventDefault();A.getPrevCellInCol()},saveNGoRight:function(E){E.preventDefault();A.getNextCellInRow()},saveNGoDown:function(E){E.preventDefault();A.getNextCellInCol()},saveNGoLeft:function(E){E.preventDefault();A.getPrevCellInRow()},cancelNClose:function(E){E.preventDefault();A.deactivate(false)},cancelNGoUp:function(E){E.preventDefault();A.getPrevCellInCol(false)},cancelNGoRight:function(E){E.preventDefault();A.getNextCellInRow(false)},cancelNGoDown:function(E){E.preventDefault();A.getNextCell
 InCol(false)},cancelNGoLeft:function(E){E.preventDefault();A.getPrevCellInRow(false)},valueIncrement:function(E){E.preventDefault();A.cellValueIncrement(true)},valueDecrement:function(E){E.preventDefault();A.cellValueIncrement(false)}};var C={};for(var B in this.options.keys){if($defined(this.keyboardMethods[this.options.keys[B]])){C[B]=this.keyboardMethods[this.options.keys[B]]}else{if($defined(this.options.keyboardMethods[this.options.keys[B]])){C[B]=this.options.keyboardMethods[this.options.keys[B]].bind(A)}else{if(Jx.type(this.options.keys[B])=="function"){C[B]=this.options.keys[B].bind(A)}else{$defined(console)?console.warn("keyboard method %o not defined",this.options.keys[B]):false}}}}this.keyboard=new Keyboard({events:C});this.addFormatterUriClickListener()},detach:function(){if(this.grid){this.grid.removeEvent("gridCellClick",this.onCellClick)}this.grid=null;this.keyboard=null},enable:function(){this.options.enabled=true},disable:function(B,A){B=$defined(B)?B:true;A
 =$defined(A)?A:false;if(B&&this.activeCell.cell!=null){this.deactivate(A)}this.options.enabled=false},onCellClick:function(A){this.activate(A)},activate:function(K){if(!this.options.enabled||!K){return }if(this.activeCell.cell){if(this.activeCell.cell!=K){if(!this.deactivate()){return }}else{return }}var G=this.grid.getCellData(K);if(!G||!$defined(G.row)||!$defined(G.column)){if($defined(console)){console.warn("out of grid %o",K);console.warn("data was %o",G)}return }if(!G.column.options.isEditable){return }if(this.activeCell.timeoutId){clearTimeout(F.timeoutId)}this.grid.store.moveTo(G.row);var N=this.options,B=this.grid,J=B.getStore(),H=B.columns.getIndexFromGrid(G.column.name),L=G.column.options,F={oldValue:J.get(G.column.name),newValue:{data:null,error:false},fieldOptions:this.getFieldOptionsByColName(G.column.name),data:G,cell:K,span:K.getElement("span.jxGridCellContent"),validator:null,field:null,timeoutId:null},M=F.fieldOptions.options,A,D,E,C;if(!$defined(G.column.op
 tions.validate)||typeof (G.column.options.validate)!="boolean"){G.column.options.validate=N.validate;K.store("jxCellData",G)}switch(F.fieldOptions.type){case"Text":case"Color":case"Password":case"File":M.value=F.oldValue;break;case"Textarea":M.value=F.oldValue.replace(/<br \/>/gi,"\n");break;case"Select":M.value=A=F.oldValue.toString();function I(R,P){for(var Q=0,O=R.length;Q<O;Q++){if(R[Q].value==P){R[Q].selected=true}else{R[Q].selected=false}}return R}if(M.comboOpts){M.comboOpts=I(M.comboOpts,A)}else{if(M.optGroups){D=M.optGroups;for(E=0,C=D.length;E<C;E++){D[E].options=I(D[E].options,A)}M.optGroups=D}}break;case"Radio":case"Checkbox":default:$defined(console)?console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.",F.fieldOptions.type):false;return ;break}if(N.fieldFormatted&&L.renderer.options.formatter!=null){if(!$defined(L.fieldFormatted)||L.fieldFormatted==true){M.value=L.renderer.optio
 ns.formatter.format(M.value);F.oldValue=M.value}}F.field=new Jx.Field[F.fieldOptions.type.capitalize()](M);if(N.validate&&L.validate){F.validator=new Jx.Plugin.Field.Validator(F.fieldOptions.validatorOptions);F.validator.attach(F.field)}this.activeCell=F;this.setStyles(K);if(N.useKeyboard){this.keyboard.activate()}if(typeof (N.blurDelay)=="string"){N.blurDelay=N.blurDelay.toInt()?N.blurDelay.toInt():false}if(N.blurDelay!==false&&typeof (N.blurDelay)=="number"){F.field.field.addEvents({blur:function(){clearTimeout(F.timeoutId);F.timeoutId=this.deactivate.delay(this.options.blurDelay)}.bind(this),focus:function(){clearTimeout(F.timeoutId)},mouseover:function(){clearTimeout(F.timeoutId)}});if(this.popup.domObj!=null){this.popup.domObj.addEvent("mouseenter",function(){clearTimeout(F.timeoutId)})}}F.field.field.focus()},deactivate:function(F){var B={data:null,error:false},E,C=this.activeCell,A=this.grid,H=A.store,J=this.options,G,D;clearTimeout(C.timeoutId);if(C.field!==null){F=$
 defined(F)?F:true;if(F&&C.field.getValue().toString()!=C.oldValue.toString()){H.moveTo(C.data.row);switch(C.fieldOptions.type){case"Select":E=C.field.field.selectedIndex;B.data=document.id(C.field.field.options[E]).get("value");break;case"Textarea":B.data=C.field.getValue().replace(/\n/gi,"<br />");break;default:B.data=C.field.getValue();break}if(F){C.newValue.data=B.data}if(C.validator!=null&&!C.validator.isValid()){B.error=true;C.field.field.focus.delay(50,C.field.field)}}else{C.span.show()}if(F&&B.data!=null&&B.error==false){H.set(C.data.column.name,B.data);this.addFormatterUriClickListener()}else{if(B.error==true){C.span.show()}}if($defined(C.data.row)&&$defined(C.data.index)){var I=A.row.useHeaders()?C.data.index-1:C.data.index;this.activeCell.cell=A.gridTableBody.rows[this.activeCell.data.row].cells[I]}if(J.useKeyboard){C.field.removeEvent("keypress",this.setKeyboard)}if(J.cellChangeFx.use){G=new Fx.Tween(this.activeCell.cell,{duration:250,onComplete:function(K){this.e
 lement.removeProperty("style")}});D=C.cell.getStyle("background-color");D=D=="transparent"?"#fff":D;if(B.data!=null&&B.error==false){G.start("background-color",J.cellChangeFx.success,D)}else{if(B.error){G.start("background-color",J.cellChangeFx.error,D)}}}if(B.error){if(J.cellChangeFx.use){C.field.field.highlight(J.cellChangeFx.error)}C.field.field.setStyle("border","1px solid "+J.cellChangeFx.error);C.field.field.focus();return false}else{this.keyboard.deactivate();this.unsetActiveField();return true}}},setStyles:function(A){var E,D,C=this.options,B=this.activeCell;if(C.popup.use){if(C.popup.useLabels){B.field.options.label=B.data.column.options.header;B.field.render()}E={field:{width:B.field.type=="Select"?A.getContentBoxSize().width+5+"px":A.getContentBoxSize().width-14+"px",margin:"auto 0"}};B.field.field.setStyles(E.field);this.showPopUp(A)}else{D=A.getContentBoxSize();E={domObj:{position:"absolute"},field:{width:D.width+"px","margin-left":0}};B.field.domObj.setStyles(E
 .domObj);B.field.field.setStyles(E.field);B.field.domObj.inject(document.body);Jx.Widget.prototype.position(B.field.domObj,A,{horizontal:["left left"],vertical:["top top"]});B.span.hide()}if(C.cellOutline.use){A.setStyle("outline",C.cellOutline.style)}},showPopUp:function(A){if(this.popup.domObj!=null){Jx.Widget.prototype.position(this.popup.domObj,A,{horizontal:["left left"],vertical:["top top"]});this.activeCell.field.domObj.inject(this.popup.innerWrapper,"top");this.popup.domObj.show();this.setPopUpButtons();this.setPopUpStylesAfterRendering()}else{this.createPopUp(A)}},createPopUp:function(G){var E=G.getCoordinates(),I=this,A=null,C=null,B=null,D=null,H=null,F=Jx.Widget.prototype.processTemplate(this.options.popup.template,this.classes);A=F.jxGridEditorPopup;C=F.jxGridEditorPopupInnerWrapper;A.setStyles({left:E.left+"px",top:E.top+"px"});this.popup.domObj=A;this.popup.innerWrapper=C;this.popup.closeIcon=B;this.setPopUpButtons();this.activeCell.field.domObj.inject(this.po
 pup.innerWrapper,"top");this.popup.domObj.inject(document.body);this.setPopUpStylesAfterRendering()},setPopUpStylesAfterRendering:function(){if(this.options.popup.useButtons&&this.popup.button.submit!=null&&this.popup.button.cancel!=null){this.popup.domObj.setStyle("min-width",this.popup.button.submit.domObj.getSize().x+this.popup.button.cancel.domObj.getSize().x+"px")}else{if(this.popup.button.submit!=null){this.popup.button.submit.domObj.hide()}if(this.popup.button.cancel!=null){this.popup.button.cancel.domObj.hide()}}this.activeCell.field.field.setStyle("width",this.activeCell.field.type=="Select"?this.popup.domObj.getSize().x-7+"px":this.popup.domObj.getSize().x-17+"px")},setPopUpButtons:function(){var A=this,B={submit:null,cancel:null};if(this.options.popup.useButtons&&this.popup.innerWrapper!=null&&this.popup.button.submit==null){B.submit=new Jx.Button({label:this.options.popup.button.submit.label.length==0?this.getText({set:"Jx",key:"plugin.editor",value:"submitButton
 "}):this.getText(this.options.popup.button.submit.label),image:this.options.popup.button.submit.image,onClick:function(){A.deactivate(true)}}).addTo(this.popup.innerWrapper);B.cancel=new Jx.Button({label:this.options.popup.button.cancel.label.length==0?this.getText({set:"Jx",key:"plugin.editor",value:"cancelButton"}):this.getText(this.options.popup.button.cancel.label),image:this.options.popup.button.cancel.image,onClick:function(){A.deactivate(false)}}).addTo(this.popup.innerWrapper)}else{if(this.options.popup.useButtons&&this.popup.button.submit!=null){B={submit:this.popup.button.submit,cancel:this.popup.button.cancel}}else{if(this.options.popup.useButtons==false&&this.popup.button.submit!=null){this.popup.button.submit.cleanup();this.popup.button.cancel.cleanup()}}}this.popup.button=B},unsetActiveField:function(){this.activeCell.field.destroy();if(this.popup.domObj!=null){this.popup.domObj.removeEvent("mouseenter");this.popup.domObj.hide()}this.activeCell.cell.setStyle("o
 utline","0px");this.activeCell={field:null,oldValue:null,newValue:{data:null,error:false},cell:null,span:null,timeoutId:null,data:{},fieldOptions:{},validator:null}},unsetPopUp:function(){if(this.popup.domObj!=null){this.popup.domObj.destroy();this.popup.innerWrapper=null;this.popup.closeIcon=null;this.popup.button.submit=null;this.popup.button.cancel=null}},getNextCellInRow:function(D){D=$defined(D)?D:true;var G=true,F=true,I=this.grid.columns.columns.length,A="td.jxGridCell:not(.jxGridCellUnattached)",C=0,B,E=this.activeCell.cell,H=this.options;if(this.activeCell.cell!=null){do{G=C>0?G.getNext(A):E.getNext(A);if(G==null){F=E.getParent("tr").getNext();if(F==null&&H.keypressLoop){F=E.getParent("tbody").getFirst()}else{if(F==null&&!H.keypressLoop){return }}G=F.getFirst(A)}B=this.grid.getCellData(G);C++;if(C==I*2){this.deactivate(D);return }}while(B&&!B.column.options.isEditable);if(D===false){this.deactivate(D)}this.activate(G)}},getPrevCellInRow:function(F){F=$defined(F)?F:t
 rue;var G,A,D=0,C,K,E,H=this.activeCell.cell,J=this.grid.columns.columns.length,B="td.jxGridCell:not(.jxGridCellUnattached)",I=this.options;if(H!=null){do{G=D>0?G.getPrevious(B):H.getPrevious(B);if(G==null){A=H.getParent("tr").getPrevious();if(A==null&&I.keypressLoop){A=H.getParent("tbody").getLast()}else{if(A==null&&!I.keypressLoop){return }}G=A.getLast(B)}C=this.grid.getCellData(G);K=C.row;E=C.index;D++;if(D==J*2){this.deactivate(F);return }}while(C&&!C.column.options.isEditable);if(F===false){this.deactivate(F)}this.activate(G)}},getNextCellInCol:function(D){var C,B,A=this.activeCell;D=$defined(D)?D:true;if(A.cell!=null){C=A.cell.getParent().getNext();if(C==null){C=A.cell.getParent("tbody").getFirst()}B=C.getElement("td.jxGridCol"+A.data.index);if(D===false){this.deactivate(D)}this.activate(B)}},getPrevCellInCol:function(C){var D,B,A=this.activeCell;C=$defined(C)?C:true;if(A.cell!=null){D=A.cell.getParent().getPrevious();if(D==null){D=A.cell.getParent("tbody").getLast()}B
 =D.getElement("td.jxGridCol"+A.data.index);if(C===false){this.deactivate(C)}this.activate(B)}},cellValueIncrement:function(B){var C=this.activeCell,A=C.data.column.options.dataType,E=null,D;switch(A){case"numeric":case"currency":E=C.field.getValue().toInt();if(typeof (E)=="number"){if(B){E++}else{E--}}break;case"date":E=Date.parse(C.field.getValue());if(E instanceof Date){if(B){E.increment()}else{E.decrement()}D=new Jx.Formatter.Date();E=D.format(E)}break}if(E!=null){C.field.setValue(E)}},cellIsInGrid:function(B,A){if($defined(B)&&$defined(A)){if(B>=0&&A>=0&&B<=this.grid.gridTableBody.rows.length&&A<=this.grid.gridTableBody.rows[B].cells.length){return true}else{return false}}else{return false}},getFieldOptionsByColName:function(D){var C=this.options.fieldOptions,E=this.options.fieldOptions[0];for(var B=0,A=C.length;B<A;B++){if(C[B].field==D){E=C[B];break}}return E},addFormatterUriClickListener:function(){if(this.options.linkClickListener){var B=[],C,A;this.grid.columns.colu
 mns.each(function(D,E){if(D.options.renderer.options.formatter!=null&&D.options.renderer.options.formatter instanceof Jx.Formatter.Uri){B.push(E)}});this.grid.gridObj.getElements("tr").each(function(G,F){C=G.getElements("td.jxGridCell");for(var E=0,D=B.length;E<D;E++){A=C[B[E]-1].getElement("a");if(A){A.removeEvent("click");A.addEvent("click",function(H){if(!H.control){H.preventDefault()}})}}})}},changeText:function(A){this.parent();if(this.options.popup.use&&this.options.popup.useButtons){if(this.popup.button.submit!=null){this.popup.button.submit.cleanup();this.popup.button.cancel.cleanup();this.popup.button.submit=null;this.popup.button.cancel=null;this.setPopUpButtons()}}}});Jx.Plugin.DataView={};Jx.Slide=new Class({Family:"Jx.Slide",Implements:Jx.Object,Binds:["handleClick"],options:{target:null,trigger:null,type:"height",setOpenTo:"auto",onSlideOut:$empty,onSlideIn:$empty},init:function(){this.target=document.id(this.options.target);this.target.set("tween",{onComplete:
 this.setDisplay.bind(this)});if($defined(this.options.trigger)){this.trigger=document.id(this.options.trigger);this.trigger.addEvent("click",this.handleClick)}this.target.store("slider",this)},handleClick:function(){var A=this.target.getMarginBoxSize();if(A.height===0){this.slide("in")}else{this.slide("out")}},setDisplay:function(){var A=this.target.getStyle(this.options.type).toInt();if(A===0){this.target.setStyle("display","none");this.fireEvent("slideOut",this.target)}else{if(this.target.getStyle("position")!=="absolute"){this.target.setStyle(this.options.type,this.options.setOpenTo)}this.fireEvent("slideIn",this.target)}},slide:function(A){var B;if(A==="in"){B=this.target.retrieve(this.options.type);this.target.setStyles({overflow:"hidden",display:"block"});this.target.setStyles(this.options.type,0);this.target.tween(this.options.type,B)}else{if(this.options.type==="height"){B=this.target.getMarginBoxSize().height}else{B=this.target.getMarginBoxSize().width}this.target.s
 tore(this.options.type,B);this.target.setStyle("overflow","hidden");this.target.setStyle(this.options.type,B);this.target.tween(this.options.type,0)}}});Jx.Plugin.DataView.GroupFolder=new Class({Extends:Jx.Plugin,options:{headerClass:null},headerState:null,init:function(){this.headerState=new Hash()},attach:function(A){if(!$defined(A)&&!(dataview instanceof Jx.Panel.DataView)){return }this.dv=A;this.dv.addEvent("renderDone",this.setHeaders.bind(this))},setHeaders:function(){var A=this.dv.domA.getElements("."+this.dv.options.groupHeaderClass);A.each(function(E){var D=E.get("id");var B=new Jx.Slide({target:E.getNext(),trigger:D,onSlideOut:this.onSlideOut.bind(this,E),onSlideIn:this.onSlideIn.bind(this,E)});if(this.headerState.has(D)){var C=this.headerState.get(D);if(C==="open"){B.slide("in")}else{B.slide("out")}}else{B.slide("in")}},this)},onSlideIn:function(A){this.headerState.set(A.get("id"),"open");if(A.hasClass(this.options.headerClass+"-closed")){A.removeClass(this.option
 s.headerClass+"-closed")}A.addClass(this.options.headerClass+"-open")},onSlideOut:function(A){this.headerState.set(A.get("id"),"closed");if(A.hasClass(this.options.headerClass+"-open")){A.removeClass(this.options.headerClass+"-open")}A.addClass(this.options.headerClass+"-closed")}});Jx.Plugin.Field={};Jx.Plugin.Field.Validator=new Class({Extends:Jx.Plugin,name:"Field.Validator",options:{validators:[],validateOnBlur:true,validateOnChange:true},valid:null,errors:null,validators:null,init:function(){this.parent();this.errors=[];this.validators=new Hash();this.bound.validate=this.validate.bind(this);this.bound.reset=this.reset.bind(this)},attach:function(A){if(!$defined(A)&&!(A instanceof Jx.Field)){return }this.field=A;if(this.field.options.required&&!this.options.validators.contains("required")){this.options.validators.reverse().push("required");this.options.validators.reverse()}this.options.validators.each(function(B){var C=Jx.type(B);if(C==="string"){this.field.field.addClas
 s(B)}else{if(C==="object"){this.validators.set(B.validator.name,new InputValidator(B.validator.name,B.validator.options));this.field.field.addClass(B.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)},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},validate:function(){$clear(this.timer);this.timer=this.validateField.delay(50,this)},validateField:function(){this.valid=true;this.errors=[];this.options.validators.each(function(A){var B=(Jx.type(A)==="string")?Form.Validator.getValidator(A):this.validators.get(A.validator.name);if(B){if(!B.test(this.field.field)){this.valid=false;this.errors.push(B.ge
 tError(this.field.field))}}},this);if(!this.valid){this.field.domObj.removeClass("jxFieldSuccess").addClass("jxFieldError");this.fireEvent("fieldValidationFailed",[this.field,this])}else{this.field.domObj.removeClass("jxFieldError").addClass("jxFieldSuccess");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("jxFieldError").removeClass("jxFieldSuccess")},getErrors:function(){return this.errors}});Jx.Plugin.Form={};Jx.Plugin.Form.Validator=new Class({Extends:Jx.Plugin,name:"Form.Validator",options:{fields:null,fieldDefaults:{validateOnBlur:true,validateOnChange:true},validateOnSubmit:true,suspendSubmit:false},errorMessage:null,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)},attach:function(C){if(!$defined(C)&
 &!(C instanceof Jx.Form)){return }this.form=C;var B=this,A=this.options;C.isValid=function(){return B.isValid()};if(A.validateOnSubmit&&!A.suspendSubmit){document.id(this.form).addEvent("submit",this.bound.validate)}else{if(A.suspendSubmit){document.id(this.form).addEvent("submit",function(D){D.stop()})}}this.plugins=$H();$H(A.fields).each(function(G,E){var F=$merge(this.options.fieldDefaults,G),D=this.form.getFieldsByName(E).p;if(D&&D.length){p=new Jx.Plugin.Field.Validator(F);this.plugins.set(E,p);p.attach(D[0]);p.addEvent("fieldValidationFailed",this.bound.failed);p.addEvent("fieldValidationPassed",this.bound.passed)}},this)},detach:function(){if(this.form){document.id(this.form).removeEvent("submit")}this.form=null;this.plugins.each(function(A){A.detach();A=null},this);this.plugins=null},isValid:function(){return this.validate()},validate:function(){var A=true;this.errors=$H();this.plugins.each(function(B){if(!B.isValid()){A=false;this.errors.set(B.field.id,B.getErrors()
 )}},this);if(A){this.fireEvent("formValidationPassed",[this.form,this])}else{this.fireEvent("formValidationFailed",[this.form,this])}return A},fieldFailed:function(B,A){this.fireEvent("fieldValidationFailed",[B,A])},fieldPassed:function(B,A){this.fireEvent("fieldValidationPassed",[B,A])},getErrors:function(){if(!$defined(this.errors)){this.validate()}return this.errors}});Jx.Plugin.ToolbarContainer={};Jx.Plugin.ToolbarContainer.TabMenu=new Class({Family:"Jx.Plugin.ToolbarContainer.TabMenu",Extends:Jx.Plugin,Binds:["addButton"],options:{},tabs:[],init:function(){this.parent()},attach:function(B){this.parent(B);this.container=B;if(!this.container.options.scroll){return }this.menu=new Jx.Menu({},{buttonTemplate:'<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'}).addTo(this.container.controls,"bottom");document.id(this.menu).addClass("jxTabMenuRevealer");this.containe
 r.update();var A=document.id(this.container).getElement("ul").retrieve("jxToolbar");A.list.each(function(C){this.addButton(C)},this);A.list.addEvent("add",this.addButton)},detach:function(){this.parent()},addButton:function(D){var C;C=(D instanceof Jx.Tab)?D:document.id(D).getFirst().retrieve("jxTab");var A=C.getLabel();if(!$defined(A)){A=""}var B=new Jx.Menu.Item({label:A,image:C.options.image,onClick:function(){if(C.isActive()){this.container.scrollIntoView(C)}else{C.setActive(true)}}.bind(this)});document.id(C).store("menuItem",B);C.addEvent("close",function(){this.menu.remove(B)}.bind(this));this.menu.add([B])}});Jx.Adaptor=new Class({Extends:Jx.Plugin,Family:"Jx.Adaptor",name:"Jx.Adaptor",options:{template:"",useTemplate:true,store:null},columnsNeeded:null,init:function(){var A=this.options;this.parent();this.store=A.store;if(A.useTemplate&&$defined(this.store.getColumns())){this.columnsNeeded=this.store.parseTemplate(A.template)}},attach:function(A){this.parent(A);this
 .widget=A},detach:function(){this.parent()}});Jx.Adaptor.Tree=new Class({Extends:Jx.Adaptor,Family:"Jx.Adaptor.Tree",Binds:["fill","checkFolder"],options:{monitorFolders:false,startingNodeKey:-1,folderOptions:null,itemOptions:null},folders:null,currentRecord:-1,init:function(){this.folders=new Hash();this.parent()},attach:function(A){this.parent(A);this.tree=A;if(this.options.monitorFolders){this.strategy=this.store.getStrategy("progressive");if(!$defined(this.strategy)){this.strategy=new Jx.Store.Strategy.Progressive({dropRecords:false,getPaginationParams:function(){return{}}});this.store.addStrategy(this.strategy)}else{this.strategy.options.dropRecords=false;this.strategy.options.getPaginationParams=function(){return{}}}}this.store.addEvent("storeDataLoaded",this.fill)},detach:function(){this.parent();this.store.removeEvent("storeDataLoaded",this.fill)},firstLoad:function(){this.busy="tree";this.tree.setBusy(true);this.store.load({node:this.options.startingNodeKey})},fill:
 function(){var C,D,F,G,E,B=this.option;if(this.busy=="tree"){this.tree.setBusy(false);this.busy="none"}else{if(this.busy=="folder"){this.busyFolder.setBusy(false);this.busy="none"}}var A=this.store.count()-1;for(C=this.currentRecord+1;C<=A;C++){D=this.store.fillTemplate(C,B.template,this.columnsNeeded);if(this.hasChildren(C)){F=new Jx.TreeFolder($merge(B.folderOptions,{label:D}));if(B.monitorFolders){F.addEvent("disclosed",this.checkFolder)}this.folders.set(C,F)}else{F=new Jx.TreeItem($merge(B.itemOptions,{label:D}))}document.id(F).store("index",C).store("jxAdaptor",this);if(this.hasParent(C)){var G=this.getParentIndex(C);var E=this.folders.get(G);E.add(F)}else{this.tree.add(F)}}this.currentRecord=A},checkFolder:function(D){var A=D.items(),B,C;if(!$defined(A)||A.length===0){B=document.id(D).retrieve("index");C=this.store.get("primaryKey",B);this.busyFolder=D;this.busyFolder.setBusy(true);this.busy="folder";this.store.load({node:C})}},hasChildren:$empty,hasParent:$empty,getPa
 rentIndex:$empty});Jx.Adaptor.Tree.Mptt=new Class({Family:"Jx.Adaptor.Tree.Mptt",Extends:Jx.Adaptor.Tree,name:"tree.mptt",options:{left:"left",right:"right"},hasChildren:function(B){var A=this.store.get(this.options.left,B).toInt(),C=this.store.get(this.options.right,B).toInt();return(A+1!==C)},hasParent:function(B){var C=this.getParentIndex(B),A=false;if($defined(C)){A=true}return A},getParentIndex:function(D){var B=this.store,C=this.options,A,G,E,F,H;A=B.get(C.left,D).toInt();G=B.get(C.right,D).toInt();for(E=D-1;E>=0;E--){F=B.get(C.left,E).toInt();H=B.get(C.right,E).toInt();if(F<A&&H>G){return E}}return null}});Jx.Adaptor.Tree.Parent=new Class({Extends:Jx.Adaptor.Tree,Family:"Jx.Adaptor.Tree.Parent",options:{parentColumn:"parent",folderColumn:"folder"},hasChildren:function(A){return this.store.get(this.options.folderColumn,A)},hasParent:function(A){if(this.store.get(this.options.parentColumn,A).toInt()!==-1){return true}return false},getParentIndex:function(A){var B=this.s
 tore.get(this.options.parentColumn,A);return this.store.findByColumn("primaryKey",B)}});Jx.Adaptor.Combo={};Jx.Adaptor.Combo.Fill=new Class({Family:"Jx.Adaptor.Combo.Fill",Extends:Jx.Adaptor,name:"combo.fill",Binds:["fill"],options:{imagePathColumn:null,imageClassColumn:null,selectedFn:null,noRepeats:false},labels:null,init:function(){this.parent();if(this.options.noRepeat){this.labels=[]}},attach:function(A){this.parent(A);this.store.addEvent("storeDataLoaded",this.fill);if(this.store.loaded){this.fill()}},detach:function(){this.parent();this.store.removeEvent("storeDataLoaded",this.fill)},fill:function(){var E,A=[],D,F,C=this.options,B=this.options.noRepeat;this.widget.empty();this.store.first();A=[];this.store.each(function(G){E=this.store.fillTemplate(G,C.template,this.columnsNeeded);if(!B||(B&&!this.labels.contains(E))){D=false;if($type(C.selectedFn)=="function"){D=C.selectedFn.run(G)}F={label:E,image:G.get(C.imagePathColumn),imageClass:G.get(C.imageClassColumn),selecte
 d:D};A.push(F);if(B){this.labels.push(E)}}},this);this.widget.add(A)}});Jx.Menu.Context=new Class({Family:"Jx.Menu.Context",Extends:Jx.Menu,parameters:["id"],render:function(){this.id=document.id(this.options.id);if(this.id){this.id.addEvent("contextmenu",this.show.bindWithEvent(this))}this.parent()},show:function(A){if(this.list.count()==0){return }this.target=A.target;this.contentContainer.setStyle("visibility","hidden");this.contentContainer.setStyle("display","block");document.id(document.body).adopt(this.contentContainer);this.contentContainer.setStyles({width:null,height:null});this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());this.position(this.contentContainer,document.body,{horizontal:[A.page.x+" left"],vertical:[A.page.y+" top",A.page.y+" bottom"],offsets:this.chromeOffsets});this.contentContainer.setStyle("visibility","");this.showChrome(this.contentContainer);document.addEvent("mousedown",this.bound.hide);document.addEvent("keyup",this.bo
 und.keypress);A.stop()}});Jx.Menu.Separator=new Class({Family:"Jx.Menu.Separator",Extends:Jx.Widget,domObj:null,owner:null,options:{template:"<li class='jxMenuItemContainer jxMenuItem'><span class='jxMenuSeparator'>&nbsp;</span></li>"},classes:new Hash({domObj:"jxMenuItem"}),render:function(){this.parent();this.domObj.store("jxMenuItem",this)},cleanup:function(){this.domObj.eliminate("jxMenuItem");this.owner=null;this.parent()},setOwner:function(A){this.owner=A},hide:$empty,show:$empty});Jx.Menu.SubMenu=new Class({Family:"Jx.Menu.SubMenu",Extends:Jx.Menu.Item,subDomObj:null,owner:null,visibleItem: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"]}},render:function(){this.parent();this.open=false;this.menu=new Jx.Menu(
 null,{position:this.options.position});this.menu.domObj=this.domObj},cleanup:function(){this.menu.domObj=null;this.menu.destroy();this.menu=null;this.parent()},setOwner:function(A){this.owner=A;this.menu.owner=A},show:function(){if(this.open||this.menu.list.count()==0){return }this.menu.show();this.open=true},eventInMenu:function(A){if(this.visibleItem&&this.visibleItem.eventInMenu&&this.visibleItem.eventInMenu(A)){return true}return document.id(A.target).descendantOf(this.domObj)||this.menu.eventInMenu(A)},hide:function(){if(!this.open){return }this.open=false;this.menu.hide();this.visibleItem=null},add:function(B,A){this.menu.add(B,A,this);return this},remove:function(A){this.menu.remove(A);return this},replace:function(B,A){this.menu.replace(B,A);return this},empty:function(){this.menu.empty()},deactivate:function(A){if(this.owner){this.owner.deactivate(A)}},isActive:function(){if(this.owner){return this.owner.isActive()}else{return false}},setActive:function(A){if(this.o
 wner&&this.owner.setActive){this.owner.setActive(A)}},setVisibleItem:function(A){if(this.visibleItem!=A){if(this.visibleItem&&this.visibleItem.hide){this.visibleItem.hide()}this.visibleItem=A;this.visibleItem.show()}}});Jx.Splitter.Snap=new Class({Family:"Jx.Splitter.Snap",Extends:Jx.Object,snap:null,element:null,splitter:null,layout:"vertical",parameters:["snap","element","splitter","events"],init:function(){this.snap=this.options.snap;this.element=this.options.element;this.splitter=this.options.splitter;this.events=this.options.events;var C=this.element.retrieve("jxLayout");C.addEvent("sizeChange",this.sizeChange.bind(this));this.layout=this.splitter.options.layout;var A=C.options;var B=this.element.getContentBoxSize();if(this.layout=="vertical"){this.originalSize=B.height;this.minimumSize=A.minHeight?A.minHeight:0}else{this.originalSize=B.width;this.minimumSize=A.minWidth?A.minWidth:0}this.events.each(function(D){this.snap.addEvent(D,this.toggleElement.bind(this))},this)}
 ,toggleElement:function(){var B=this.element.getContentBoxSize();var A={};if(this.layout=="vertical"){if(B.height==this.minimumSize){A.height=this.originalSize}else{this.originalSize=B.height;A.height=this.minimumSize}}else{if(B.width==this.minimumSize){A.width=this.originalSize}else{this.originalSize=B.width;A.width=this.minimumSize}}this.element.resize(A);this.splitter.sizeChanged()},sizeChange:function(){var A=this.element.getContentBoxSize();if(this.layout=="vertical"){if(A.height==this.minimumSize){this.snap.addClass("jxSnapClosed");this.snap.removeClass("jxSnapOpened")}else{this.snap.addClass("jxSnapOpened");this.snap.removeClass("jxSnapClosed")}}else{if(A.width==this.minimumSize){this.snap.addClass("jxSnapClosed");this.snap.removeClass("jxSnapOpened")}else{this.snap.addClass("jxSnapOpened");this.snap.removeClass("jxSnapClosed")}}}});Jx.Tab=new Class({Family:"Jx.Tab",Extends:Jx.Button,content:null,options:{toggleClass:"jxTabToggle",pressedClass:"jxTabPressed",activeCla
 ss:"jxTabActive",activeTabClass:"tabContentActive",template:'<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon" src="'+Jx.aPixel.src+'"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></a></span>',contentTemplate:'<div class="tabContent"></div>',close:false,shouldClose:true},classes:new Hash({domObj:"jxTabContainer",domA:"jxTab",domImg:"jxTabIcon",domLabel:"jxTabLabel",domClose:"jxTabClose",content:"tabContent"}),render:function(){this.options=$merge(this.options,{toggle:true});this.parent();this.domObj.store("jxTab",this);this.processElements(this.options.contentTemplate,this.classes);new Jx.Layout(this.content,this.options);if(!this.options.loadOnDemand||this.options.active){this.loadContent(this.content);if(this.options.active){this.clicked()}}else{this.addEvent("contentLoaded",function(A){this.setActive(true)}.bind(this))}this.addEvent("down",function(){this.content.addClass(this.options.activeTabClass)}.bind(
 this));this.addEvent("up",function(){this.content.removeClass(this.options.activeTabClass)}.bind(this));if(this.domClose){if(this.options.close){this.domObj.addClass("jxTabClose");this.domClose.addEvent("click",(function(){var A=true;if($defined(this.options.shouldClose)){if(typeof this.options.shouldClose=="function"){A=this.options.shouldClose()}else{A=this.options.shouldClose}}if(A){this.fireEvent("close")}}).bind(this))}else{this.domClose.dispose()}}},clicked:function(A){if(this.options.enabled){if(this.contentIsLoaded&&this.options.cacheContent){this.setActive(true)}else{if(this.options.loadOnDemand||!this.options.cacheContent){this.loadContent(this.content)}else{this.setActive(true)}}}}});Jx.Button.Tab=new Class({Extends:Jx.Tab,init:function(){if(console.warn){console.warn("WARNING: Jx.Button.Tab has been renamed to Jx.Tab")}else{console.log("WARNING: Jx.Button.Tab has been renamed to Jx.Tab")}this.parent()}});Jx.TabSet=new Class({Family:"Jx.TabSet",Extends:Jx.Object,t
 abs:null,domObj:null,parameters:["domObj","options"],init:function(){this.tabs=[];this.domObj=document.id(this.options.domObj);if(!this.domObj.hasClass("jxTabSetContainer")){this.domObj.addClass("jxTabSetContainer")}this.setActiveTabFn=this.setActiveTab.bind(this)},resizeTabBox:function(){if(this.activeTab&&this.activeTab.content.resize){this.activeTab.content.resize({forceResize:true})}},add:function(){$A(arguments).flatten().each(function(A){if(A instanceof Jx.Tab){A.addEvent("down",this.setActiveTabFn);A.tabSet=this;this.domObj.appendChild(A.content);this.tabs.push(A);if((!this.activeTab||A.options.active)&&A.options.enabled){A.options.active=false;A.setActive(true)}}},this);return this},remove:function(A){if(A instanceof Jx.Tab&&this.tabs.indexOf(A)!=-1){this.tabs.erase(A);if(this.activeTab==A){if(this.tabs.length){this.tabs[0].setActive(true)}}A.removeEvent("down",this.setActiveTabFn);A.content.dispose()}},setActiveTab:function(A){if(this.activeTab&&this.activeTab!=A){t
 his.activeTab.setActive(false)}this.activeTab=A;if(this.activeTab.content.resize){this.activeTab.content.resize({forceResize:true})}this.fireEvent("tabChange",[this,A])}});Jx.TabBox=new Class({Family:"Jx.TabBox",Extends:Jx.Widget,options:{parent:null,position:"top",height:null,width:null,scroll:true},tabBar:null,tabSet:null,render:function(){this.parent();this.tabBar=new Jx.Toolbar({position:this.options.position,scroll:this.options.scroll});this.panel=new Jx.Panel({toolbars:[this.tabBar],hideTitle:true,height:this.options.height,width:this.options.width});this.panel.domObj.addClass("jxTabBox");this.tabSet=new Jx.TabSet(this.panel.content);this.tabSet.addEvent("tabChange",function(A,B){this.showItem(B)}.bind(this.tabBar));this.domObj=this.panel.domObj;this.panel.addEvent("sizeChange",(function(){this.tabSet.resizeTabBox();this.tabBar.domObj.getParent(".jxBarContainer").retrieve("jxBarContainer").update();this.tabBar.domObj.getParent(".jxBarContainer").addClass("jxTabBar"+thi
 s.options.position.capitalize())}).bind(this));this.tabBar.addEvents({add:(function(){this.domObj.resize({forceResize:true})}).bind(this),remove:(function(){this.domObj.resize({forceResize:true})}).bind(this)});this.addEvent("addTo",function(){this.domObj.resize({forceResize:true})});if(this.options.parent){this.addTo(this.options.parent)}},add:function(){this.tabBar.add.apply(this.tabBar,arguments);this.tabSet.add.apply(this.tabSet,arguments);$A(arguments).flatten().each(function(A){A.addEvents({close:(function(){this.tabBar.remove(A);this.tabSet.remove(A)}).bind(this)})},this);return this},remove:function(A){this.tabBar.remove(A);this.tabSet.remove(A)}});Jx.Toolbar.Separator=new Class({Family:"Jx.Toolbar.Separator",Extends:Jx.Widget,render:function(){this.domObj=new Element("li",{"class":"jxToolItem"});this.domSpan=new Element("span",{"class":"jxBarSeparator"});this.domObj.appendChild(this.domSpan)}});Jx.Tree=new Class({Family:"Jx.Tree",Extends:Jx.Widget,parameters:["optio
 ns","container","selection"],pluginNamespace:"Tree",selection:null,ownsSelection:false,list:null,dirty:true,domObj:null,options:{select:true,template:'<ul class="jxTreeRoot"></ul>'},classes:new Hash({domObj:"jxTreeRoot"}),frozen:false,render:function(){this.parent();if($defined(this.options.container)&&document.id(this.options.container)){this.domObj=this.options.container}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.bound.select=function(A){this.fireEvent("select",A.retrieve("jxTreeItem"))}.bind(this);this.bound.unselect=function(A){this.fireEvent("unselect",A.retrieve("jxTreeItem"))}.bind(this);this.bound.onAdd=function(A){this.update()}.bind(this);this.bound.onRemove=function(A){this.update()}.bind(this);if(this.selection&&this.ownsSelection){this.selection.addEvents({select:this.bound.select,unselect:this.bound.unselect})}this.list=new Jx.List(this
 .domObj,{hover:true,press:true,select:true,onAdd:this.bound.onAdd,onRemove:this.bound.onRemove},this.selection);if(this.options.parent){this.addTo(this.options.parent)}},freeze:function(){this.frozen=true},thaw:function(){this.frozen=false;this.update(true)},setDirty:function(A){this.dirty=A;if(A&&this.owner&&this.owner.setDirty){this.owner.setDirty(A)}},add:function(B,A){if($type(B)=="array"){B.each(function(C){this.add(C,A)}.bind(this));return }B.addEvents({add:function(C){this.fireEvent("add",C).bind(this)},remove:function(C){this.fireEvent("remove",C).bind(this)},disclose:function(C){this.fireEvent("disclose",C).bind(this)}});B.setSelection(this.selection);B.owner=this;this.list.add(B,A);this.setDirty(true);this.update(true);return this},remove:function(A){A.removeEvents("add");A.removeEvents("remove");A.removeEvents("disclose");A.owner=null;this.list.remove(A);A.setSelection(null);this.setDirty(true);this.update(true);return this},replace:function(B,A){B.owner=null;A.ow
 ner=this;this.list.replace(B,A);A.setSelection(this.selection);B.setSelection(null);this.setDirty(true);this.update(true);return this},cleanup:function(){this.freeze();if(this.selection&&this.ownsSelection){this.selection.removeEvents({select:this.bound.select,unselect:this.bound.unselect});this.selection.destroy();this.selection=null}this.list.removeEvents({remove:this.bound.onRemove,add:this.bound.onAdd});this.list.destroy();this.list=null;this.bound.select=null;this.bound.unselect=null;this.bound.onRemove=null;this.bound.onAdd=null;this.parent()},update:function(C,B){if(!this.list){return }if(this.frozen){return }if($defined(B)){if(B){this.domObj.removeClass("jxTreeNest")}else{this.domObj.addClass("jxTreeNest")}}var A=this.list.count()-1;this.list.each(function(F,D){var E=D==A;if(F.retrieve("jxTreeFolder")){F.retrieve("jxTreeFolder").update(C,E)}if(F.retrieve("jxTreeItem")){F.retrieve("jxTreeItem").update(E)}});this.setDirty(false)},items:function(){return this.list.items
 ().map(function(A){return A.retrieve("jxTreeItem")})},empty:function(){this.list.items().each(function(A){var B=A.retrieve("jxTreeItem");if(B&&B instanceof Jx.TreeFolder){B.empty()}if(B&&B instanceof Jx.TreeItem){this.remove(B);B.destroy()}},this);this.setDirty(true)},findChild:function(C){if(C.length==0){return false}var B=C[0];var A=false;this.list.items().some(function(E){var F=E.retrieve("jxTreeItem");if(F&&F.getLabel()==B){if(C.length>0){var D=E.retrieve("jxTreeFolder");if(D&&C.length>1){A=D.findChild(C.slice(1,C.length))}else{A=F}}else{A=F}}return A});return A},setSelection:function(A){if(this.selection&&this.ownsSelection){this.selection.removeEvents(this.bound);this.selection.destroy();this.ownsSelection=false}this.selection=A;this.list.setSelection(A);this.list.each(function(B){B.retrieve("jxTreeItem").setSelection(A)});return this}});Jx.TreeItem=new Class({Family:"Jx.TreeItem",Extends:Jx.Widget,selection:null,domObj:null,owner:null,options:{label:"",contextMenu:nul
 l,enabled:true,selectable:true,image:null,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>',busyMask:{message:null}},classes:new Hash({domObj:"jxTreeContainer",domA:"jxTreeItem",domImg:"jxTreeImage",domIcon:"jxTreeIcon",domLabel:"jxTreeLabel"}),render:function(){this.parent();this.domObj=this.elements.get("jxTreeContainer");this.domObj.store("jxTreeItem",this);this.domA.store("jxTreeItem",this);if(this.options.contextMenu){this.domA.store("jxContextMenu",this.options.contextMenu)}this.domObj.store("jxListTarget",this.domA);if(!this.options.selectable){this.domObj.addClass("jxUnselectable")}if(this.options.id){this.domObj.id=this.options.id}if(!this.options.enabled){this.domObj.addClass("jxDisabled")}if(this.option
 s.image&&this.domIcon){this.domIcon.setStyle("backgroundImage","url("+this.options.image+")");if(this.options.imageClass){this.domIcon.addClass(this.options.imageClass)}}if(this.options.label&&this.domLabel){this.setLabel(this.options.label)}if(this.domA){this.domA.addEvents({click:this.click.bind(this),dblclick:this.dblclick.bind(this),drag:function(A){A.stop()}});if(typeof Drag!="undefined"){new Drag(this.domA,{onStart:function(){this.stop()}})}}if($defined(this.options.enabled)){this.enable(this.options.enabled,true)}},setDirty:function(A){if(A&&this.owner&&this.owner.setDirty){this.owner.setDirty(A)}},finalize:function(){this.destroy()},cleanup:function(){this.domObj.eliminate("jxTreeItem");this.domA.eliminate("jxTreeItem");this.domA.eliminate("jxContextMenu");this.domObj.eliminate("jxListTarget");this.domObj.eliminate("jxListTargetItem");this.domA.removeEvents();this.owner=null;this.selection=null;this.parent()},update:function(A){if(A){this.domObj.addClass(this.options
 .lastLeafClass)}else{this.domObj.removeClass(this.options.lastLeafClass)}},click:function(){if(this.options.enabled){this.fireEvent("click",this)}},dblclick:function(){if(this.options.enabled){this.fireEvent("dblclick",this)}},select:function(){if(this.selection&&this.options.enabled){this.selection.select(this.domA)}},getLabel:function(){return this.options.label},setLabel:function(A){this.options.label=A;if(this.domLabel){this.domLabel.set("html",this.getText(A));this.setDirty(true)}},setImage:function(A,B){if(this.domIcon&&$defined(A)){this.options.image=A;this.domIcon.setStyle("backgroundImage","url("+this.options.image+")");this.setDirty(true)}if(this.domIcon&&$defined(B)){this.domIcon.removeClass(this.options.imageClass);this.options.imageClass=B;this.domIcon.addClass(B);this.setDirty(true)}},enable:function(B,A){if(this.options.enabled!=B||A){this.options.enabled=B;if(this.options.enabled){this.domObj.removeClass("jxDisabled");this.fireEvent("enabled",this)}else{this.
 domObj.addClass("jxDisabled");this.fireEvent("disabled",this);if(this.selection){this.selection.unselect(document.id(this))}}}},propertyChanged:function(A){this.options.enabled=A.isEnabled();if(this.options.enabled){this.domObj.removeClass("jxDisabled")}else{this.domObj.addClass("jxDisabled")}},setSelection:function(A){this.selection=A},setBusy:function(A){if(this.busy==A){return }this.busy=A;this.fireEvent("busy",this.busy);if(this.busy){this.domImg.addClass(this.options.busyClass)}else{if(this.options.busyClass){this.domImg.removeClass(this.options.busyClass)}}},changeText:function(A){this.parent();this.setLabel(this.options.label)}});Jx.TreeFolder=new Class({Family:"Jx.TreeFolder",Extends:Jx.TreeItem,tree:null,options:{open:false,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=""><spa
 n class="jxTreeLabel"></span></a><ul class="jxTree"></ul></li>'},classes:new Hash({domObj:"jxTreeContainer",domA:"jxTreeItem",domImg:"jxTreeImage",domIcon:"jxTreeIcon",domLabel:"jxTreeLabel",domTree:"jxTree"}),render:function(){this.parent();this.domObj.store("jxTreeFolder",this);this.bound.toggle=this.toggle.bind(this);this.addEvents({click:this.bound.toggle,dblclick:this.bound.toggle});if(this.domImg){this.domImg.addEvent("click",this.bound.toggle)}this.tree=new Jx.Tree({template:this.options.template,onAdd:function(A){this.update();this.fireEvent("add",A)}.bind(this),onRemove:function(A){this.update();this.fireEvent("remove",A)}.bind(this)},this.domTree);if(this.options.open){this.expand()}else{this.collapse()}},cleanup:function(){this.domObj.eliminate("jxTreeFolder");this.removeEvents({click:this.bound.toggle,dblclick:this.bound.toggle});if(this.domImg){this.domImg.removeEvent("click",this.bound.toggle)}this.bound.toggle=null;this.tree.destroy();this.tree=null;this.paren
 t()},add:function(B,A){this.tree.add(B,A);return this},remove:function(A){this.tree.remove(A);return this},replace:function(B,A){this.tree.replace(B,A);return this},items:function(){return this.tree.items()},empty:function(){this.tree.empty()},update:function(B,A){if(!this.domObj.parentNode){return }if(this.tree.dirty||(this.owner&&this.owner.dirty)){if(!$defined(A)){A=this.domObj.hasClass("jxTreeBranchLastOpen")||this.domObj.hasClass("jxTreeBranchLastClosed")}["jxTreeBranchOpen","jxTreeBranchLastOpen","jxTreeBranchClosed","jxTreeBranchLastClosed"].each(function(D){this.removeClass(D)},this.domObj);var C="jxTreeBranch";C+=A?"Last":"";C+=this.options.open?"Open":"Closed";this.domObj.addClass(C)}this.tree.update(B,A)},toggle:function(){if(this.options.enabled){if(this.options.open){this.collapse()}else{this.expand()}}return this},expand:function(){this.options.open=true;document.id(this.tree).setStyle("display","block");this.setDirty(true);this.update(true);this.fireEvent("dis
 closed",this);return this},collapse:function(){this.options.open=false;document.id(this.tree).setStyle("display","none");this.setDirty(true);this.update(true);this.fireEvent("disclosed",this);return this},findChild:function(A){if(A.length==0){return this}else{return this.tree.findChild(A)}},setSelection:function(A){this.tree.setSelection(A);return this},setDirty:function(A){this.parent(A);if(this.tree){this.tree.setDirty(true)}}});Jx.Slider=new Class({Family:"Jx.Slider",Extends:Jx.Widget,options:{template:'<div class="jxSliderContainer"><div class="jxSliderKnob"></div></div>',max:100,min:0,step:1,mode:"horizontal",wheel:true,snap:true,startAt:0,offset:0,onChange:$empty,onComplete:$empty},classes:new Hash({domObj:"jxSliderContainer",knob:"jxSliderKnob"}),slider:null,knob:null,sliderOpts:null,render:function(){this.parent();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-t
 his.options.min)/this.options.step,offset:this.options.offset,onChange:this.change.bind(this),onComplete:this.complete.bind(this)}},change:function(A){this.fireEvent("change",[A,this])},complete:function(A){this.fireEvent("complete",[A,this])},start:function(){if(!$defined(this.slider)){this.slider=new Slider(this.domObj,this.knob,this.sliderOpts)}this.slider.set(this.options.startAt)},set:function(A){this.slider.set(A)}});Jx.Notice=new Class({Family:"Jx.Notice",Extends:Jx.ListItem,options:{fx:"fade",chrome:false,enabled:true,template:'<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="'+MooTools.lang.get("Jx","notice").closeTip+'"></a></div></li>',klass:""},classes:new Hash({domObj:"jxNoticeItemContainer",domItem:"jxNoticeItem",domContent:"jxNotice",domClose:"jxNoticeClose"}),render:function(){this.parent();if(this.options.klass){this.domObj.addClass(this.options.klass)}if(this
 .domClose){this.domClose.addEvent("click",this.close.bind(this))}},close:function(){this.fireEvent("close",this)},show:function(A,B){if(this.options.chrome){this.showChrome()}if(this.options.fx){document.id(A).adopt(this);if(B){B()}}else{document.id(A).adopt(this);if(B){B()}}},hide:function(A){if(this.options.chrome){this.hideChrome()}if(this.options.fx){document.id(this).dispose();if(A){A()}}else{document.id(this).dispose();if(A){A()}}},changeText:function(A){this.parent()}});Jx.Notice.Information=new Class({Extends:Jx.Notice,options:{template:'<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="'+MooTools.lang.get("Jx","notice").closeTip+'"></a></div></li>',klass:"jxNoticeInformation"}});Jx.Notice.Success=new Class({Extends:Jx.Notice,options:{template:'<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><
 img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="'+MooTools.lang.get("Jx","notice").closeTip+'"></a></div></li>',klass:"jxNoticeSuccess"}});Jx.Notice.Warning=new Class({Extends:Jx.Notice,options:{template:'<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="'+MooTools.lang.get("Jx","notice").closeTip+'"></a></div></li>',klass:"jxNoticeWarning"}});Jx.Notice.Error=new Class({Extends:Jx.Notice,options:{template:'<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="'+MooTools.lang.get("Jx","notice").closeTip+'"></a></div></li>',klass:"jxNoticeError"}});Jx.Not
 ifier=new Class({Family:"Jx.Notifier",Extends:Jx.ListView,options:{parent:null,template:'<div class="jxNoticeListContainer"><ul class="jxNoticeList"></ul></div>',listOptions:{}},classes:new Hash({domObj:"jxNoticeListContainer",listObj:"jxNoticeList"}),render:function(){this.parent();if(!$defined(this.options.parent)){this.options.parent=document.body}document.id(this.options.parent).adopt(this.domObj);this.addEvent("postRender",function(){if(Jx.type(this.options.items)=="array"){this.options.items.each(function(A){this.add(A)},this)}}.bind(this))},add:function(A){if(!(A instanceof Jx.Notice)){A=new Jx.Notice({content:A})}A.addEvent("close",this.remove.bind(this));A.show(this.listObj)},remove:function(A){if(this.domObj.hasChild(A)){A.removeEvents("close");A.hide()}}});Jx.Notifier.Float=new Class({Family:"Jx.Notifier.Float",Extends:Jx.Notifier,options:{chrome:true,fx:null,width:250,position:{horizontal:"center center",vertical:"top top"}},render:function(){this.parent();this.d
 omObj.setStyle("position","absolute");if($defined(this.options.width)){this.domObj.setStyle("width",this.options.width)}this.position(this.domObj,this.options.parent,this.options.position)},add:function(A){if(!(A instanceof Jx.Notice)){A=new Jx.Notice({content:A})}A.options.chrome=this.options.chrome;this.parent(A)}});Jx.Scrollbar=new Class({Family:"Jx.Scrollbar",Extends:Jx.Widget,Binds:["scrollIt"],options:{direction:"vertical",useMouseWheel:true,useScrollers:true,scrollerInterval:50,template:'<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'},classes:new Hash({domObj:"jxScrollbarContainer",scrollLeft:"jxScrollLeft",scrollRight:"jxScrollRight",sliderHolder:"jxSlider"}),el:null,parameters:["element","options"],render:function(){this.parent();this.el=document.id(this.options.element);if(this.el){this.el.addClass("jxHas"+this.options.direction.capitalize()+"Scrollbar");var D=this.el.getChildre
 n();this.wrapper=new Element("div",{"class":"jxScrollbarChildWrapper"});this.wrapper.setStyles({width:this.el.getStyle("width"),height:this.el.getStyle("height")});D.inject(this.wrapper);this.wrapper.inject(this.el);this.domObj.inject(this.el);var E=this.wrapper.getScrollSize();var C=this.wrapper.getContentBoxSize();this.steps=this.options.direction==="horizontal"?E.x-C.width:E.y-C.height;this.slider=new Jx.Slider({snap:false,min:0,max:this.steps,step:1,mode:this.options.direction,onChange:this.scrollIt});if(!this.options.useScrollers){this.scrollLeft.dispose();this.scrollRight.dispose();if(this.options.direction==="horizontal"){this.sliderHolder.setStyle("width","100%")}else{this.sliderHolder.setStyle("height","100%")}}else{this.scrollLeft.addEvents({mousedown:function(){this.slider.slider.set(this.slider.slider.step-this.options.scrollerInterval);this.pid=function(){this.slider.slider.set(this.slider.slider.step-this.options.scrollerInterval)}.periodical(1000,this)}.bind(t
 his),mouseup:function(){$clear(this.pid)}.bind(this)});this.scrollRight.addEvents({mousedown:function(){this.slider.slider.set(this.slider.slider.step+this.options.scrollerInterval);this.pid=function(){this.slider.slider.set(this.slider.slider.step+this.options.scrollerInterval)}.periodical(1000,this)}.bind(this),mouseup:function(){$clear(this.pid)}.bind(this)});var A,F,B;if(this.options.direction==="horizontal"){F=this.scrollRight.getMarginBoxSize().width;B=this.scrollLeft.getMarginBoxSize().width;A=C.width-F-B;this.sliderHolder.setStyle("width",A+"px")}else{F=this.scrollRight.getMarginBoxSize().height;B=this.scrollLeft.getMarginBoxSize().height;A=C.height-F-B;this.sliderHolder.setStyle("height",A+"px")}}document.id(this.slider).inject(this.sliderHolder);if(this.options.useMouseWheel){$$(this.el,this.domObj).addEvent("mousewheel",function(H){H=new Event(H).stop();var G=this.slider.slider.step-H.wheel*30;this.slider.slider.set(G)}.bind(this))}document.id(document.body).addEv
 ent("mouseleave",function(){this.slider.slider.drag.stop()}.bind(this));this.slider.start()}},scrollIt:function(B){var A=this.options.direction==="horizontal"?B:0;var C=this.options.direction==="horizontal"?0:B;this.wrapper.scrollTo(A,C)}});Jx.Formatter=new Class({Family:"Jx.Formatter",Extends:Jx.Object,format:$empty});Jx.Formatter.Number=new Class({Extends:Jx.Formatter,options:{precision:2,useParens:true,useThousands:true},format:function(K){if(Jx.type(K)==="string"){var A=K.split(",");K=A.join("");K=K.toFloat()}K=K.toFixed(this.options.precision);var H=K.split(".");var C=true;if(H.length===1){C=false}var D=false;var F;var J="";if(H[0].contains("-")){D=true;F=H[0].substring(1,H[0].length)}else{F=H[0]}if(this.options.useThousands){var E=F.length;var B=E%3;var G=0;for(var I=0;I<E;I++){J=J+F.charAt(I);if(I===B-1&&I!==E-1){J=J+this.getText({set:"Jx",key:"formatter.number",value:"thousandsSeparator"})}else{if(I>=B){G++;if(G===3&&I!==E-1){J=J+this.getText({set:"Jx",key:"formatter
 .number",value:"thousandsSeparator"});G=0}}}}}else{J=H[0]}if(C){J=J+this.getText({set:"Jx",key:"formatter.number",value:"decimalSeparator"})+H[1]}if(D&&this.options.useParens){J="("+J+")"}else{if(D&&!this.options.useParens){J="-"+J}}return J},changeText:function(A){this.parent()}});Jx.Formatter.Currency=new Class({Extends:Jx.Formatter.Number,options:{},format:function(B){this.options.precision=2;B=this.parent(B);var C=false;if(B.contains("(")||B.contains("-")){C=true}var A;if(C&&!this.options.useParens){A="-"+this.getText({set:"Jx",key:"formatter.currency",value:"sign"})+B.substring(1,B.length)}else{A=this.getText({set:"Jx",key:"formatter.currency",value:"sign"})+B}return A},changeText:function(A){this.parent()}});Jx.Formatter.Date=new Class({Extends:Jx.Formatter,options:{format:"%B %d, %Y"},format:function(A){var B=Date.parse(A);return B.format(this.options.format)}});Jx.Formatter.Uri=new Class({Extends:Jx.Formatter,options:{format:'<a href="{string}" target="_blank">{host}
 </a>'},format:function(G){var E=new URI(G),C={},F=new Array(),B=this.options.format.match(/\\?\{([^{}]+)\}/g);B.each(function(H){F.push(H.slice(1,H.length-1))});for(var D=0,A=F.length;D<A;D++){switch(F[D]){case"string":C[F[D]]=E.toString();break;default:C[F[D]]=E.get(F[D]);break}}return this.options.format.substitute(C)}});Jx.Formatter.Boolean=new Class({Extends:Jx.Formatter,options:{},format:function(C){var A=false;var B=Jx.type(C);switch(B){case"string":if(C==="true"){A=true}break;case"number":if(C!==0){A=true}break;case"boolean":A=C;break;default:A=true}return A?this.getText({set:"Jx",key:"formatter.boolean",value:"true"}):this.getText({set:"Jx",key:"formatter.boolean",value:"false"})},changeText:function(A){this.parent()}});Jx.Formatter.Phone=new Class({Extends:Jx.Formatter,options:{useParens:true,separator:"-"},format:function(D){var C=this.options.separator;var A=""+D;A=A.replace(/[^0-9]/g,"");var B="";if(A.length===11){B=A.charAt(0);A=A.substring(1)}if(A.length===10){
 if(this.options.useParens){B=B+"("+A.substring(0,3)+")"}else{B=B+C+A.substring(0,3)+C}A=A.substring(3)}B=B+A.substring(0,3)+C+A.substring(3);return B}});Jx.Formatter.Text=new Class({Extends:Jx.Formatter,options:{length:null,ellipsis:"..."},format:function(C){var D=""+C,A=this.options.length,B=this.options.ellipsis;if(A&&D.length>A){D=D.substr(0,A-B.length)+B}return D}});Jx.Field.Checkbox=new Class({Extends:Jx.Field,options:{template:'<span class="jxInputContainer"><input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',checked:false,labelSeparator:""},type:"Check",render:function(){this.parent();if($defined(this.options.checked)&&this.options.checked){if(Browser.Engine.trident){var B=this.field.getParent();var A;if(B){A=this.field.getPrevious()}this.field.setStyle("visibility","hidden");this.field.inject(document.id(document.body));this.field.checked=true;this.field.defaultChecked=true;this.field
 .dispose();this.field.setStyle("visibility","visible");if(A){this.field.inject(A,"after")}else{if(B){this.field.inject(B,"top")}}}else{this.field.set("checked","checked");this.field.set("defaultChecked","checked")}}if(this.label){this.label.addEvent("click",function(C){this.setValue(this.getValue()!=null?false:true)}.bind(this))}},setValue:function(A){if(!this.options.readonly){if(A==="checked"||A==="true"||A===true){this.field.set("checked","checked")}else{this.field.erase("checked")}}},getValue:function(){if(this.field.get("checked")){return this.field.get("value")}else{return null}},reset:function(){if(this.options.checked){this.field.set("checked","checked")}else{this.field.erase("checked")}},getChecked:function(){return this.field.get("checked")}});Jx.Field.Radio=new Class({Extends:Jx.Field,options:{template:'<span class="jxInputContainer"><input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>'
 ,checked:false,labelSeparator:""},type:"Radio",render:function(){this.parent();if($defined(this.options.checked)&&this.options.checked){if(Browser.Engine.trident){var B=this.field.getParent();var A;if(B){A=this.field.getPrevious()}this.field.setStyle("visibility","hidden");this.field.inject(document.id(document.body));this.field.checked=true;this.field.defaultChecked=true;this.field.dispose();this.field.setStyle("visibility","visible");if(A){this.field.inject(A,"after")}else{if(B){this.field.inject(B,"top")}}}else{this.field.set("checked","checked");this.field.set("defaultChecked","checked")}}this.label.addEvent("click",function(C){this.field.checked?this.setValue(false):this.setValue(true)}.bind(this))},setValue:function(A){if(!this.options.readonly){if(A==="checked"||A==="true"||A===true){this.field.set("checked","checked")}else{this.field.erase("checked")}}},getValue:function(){if(this.field.get("checked")){return this.field.get("value")}else{return null}},reset:function(
 ){if(this.options.checked){this.field.set("checked","checked")}else{this.field.erase("checked")}}});Jx.Field.Select=new Class({Extends:Jx.Field,options:{mulitple:false,size:1,comboOpts:null,optGroups:null,template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span></span>'},type:"Select",render:function(){this.parent();this.field.addEvent("change",function(){this.fireEvent("change",this)}.bind(this));if($defined(this.options.multiple)){this.field.set("multiple",this.options.multiple)}if($defined(this.options.size)){this.field.set("size",this.options.size)}if($defined(this.options.optGroups)){this.options.optGroups.each(function(B){var A=new Element("optGroup");A.set("label",B.name);B.options.each(function(D){var C=new Element("option",{value:D.value,html:this.getText(D.text)});if($defined(D.selected)&&D.selected){C.set("selected","selected")}A.grab(C)},this);this.field.grab(
 A)},this)}else{if($defined(this.options.comboOpts)){this.options.comboOpts.each(function(A){this.addOption(A)},this)}}},addOption:function(D,A){var C=new Element("option",{value:D.value,html:this.getText(D.text)});if($defined(D.selected)&&D.selected){C.set("selected","selected")}var B="bottom";var E=this.field;if($defined(A)){if(Jx.type(A)=="integer"&&(A>=0&&A<E.options.length)){E=this.field.options[A];B="before"}else{if(A=="top"){B="top"}}}C.inject(E,B)},removeOption:function(A){},setValue:function(A){if(!this.options.readonly){$$(this.field.options).each(function(B){if(B.get("value")===A){document.id(B).set("selected",true)}},this)}},getValue:function(){var B=this.field.selectedIndex;if(B>-1){var A=this.field.options[B].get("value");if(!$defined(A)){A=this.field.options[B].get("text")}return A}},empty:function(){if($defined(this.field.options)){$A(this.field.options).each(function(A){this.field.remove(A)},this)}}});Jx.Field.Textarea=new Class({Extends:Jx.Field,options:{row
 s:null,columns:null,template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span></span>'},type:"Textarea",errorClass:"jxFormErrorTextarea",render:function(){this.parent();if($defined(this.options.rows)){this.field.set("rows",this.options.rows)}if($defined(this.options.columns)){this.field.set("cols",this.options.columns)}}});Jx.Field.Button=new Class({Extends:Jx.Field,options:{buttonClass:Jx.Button,buttonOptions:{},template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span></span>'},button:null,type:"Button",processTemplate:function(D,B,A){var C=this.parent(D,B,A);this.button=new this.options.buttonClass(this.options.buttonOptions);this.button.addEvent("click",function(){this.fireEvent("click")}.bind(this));var E=C.get("jxInputButton");if(E){this.button.domObj.replaces(E)}this.button.set
 Enabled(!this.options.disabled);return C},click:function(){this.button.clicked()},enable:function(){this.parent();this.button.setEnabled(true)},disable:function(){this.parent();this.button.setEnabled(false)}});Jx.Field.Combo=new Class({Family:"Jx.Field.Combo",Extends:Jx.Field,pluginNamespace:"Combo",options:{buttonTemplate:'<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'},type:"Combo",render:function(){this.classes.combine({wrapper:"jxInputWrapper",revealer:"jxInputRevealer",icon:"jxInputIcon"});this.parent();var A=new Jx.Button({template:this.options.buttonTemplate,imageClass:"jxInputRevealerIcon"}).addTo(this.re
 vealer);this.menu=new Jx.Menu();this.menu.button=A;this.buttonSet=new Jx.ButtonSet();this.buttonSet=new Jx.ButtonSet({onChange:(function(F){var E=F.activeButton;var C=E.options.label;if(C=="&nbsp;"){C=""}this.setLabel(C);var D=E.options.image;if(D.indexOf("a_pixel")!=-1){D=""}this.setImage(D,E.options.imageClass);this.fireEvent("change",this)}).bind(this)});if(this.options.items){this.add(this.options.items)}var B=this;A.addEvent("click",function(C){if(this.list.count()===0){return }if(!A.options.enabled){return }this.contentContainer.setStyle("visibility","hidden");this.contentContainer.setStyle("display","block");document.id(document.body).adopt(this.contentContainer);this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());this.showChrome(this.contentContainer);this.position(this.contentContainer,B.field,{horizontal:["left left","right right"],vertical:["bottom top","top bottom"],offsets:this.chromeOffsets});this.contentContainer.setStyle("visibility",""
 );document.addEvent("mousedown",this.bound.hide);document.addEvent("keyup",this.bound.keypress);this.fireEvent("show",this)}.bindWithEvent(this.menu));this.menu.addEvents({show:(function(){}).bind(this),hide:(function(){}).bind(this)})},setLabel:function(A){if($defined(this.field)){this.field.value=this.getText(A)}},setImage:function(A,B){if($defined(this.icon)){this.icon.setStyle("background-image","url("+A+")");this.icon.setStyle("background-repeat","no-repeat");if(this.options.imageClass){this.icon.removeClass(this.options.imageClass)}if(B){this.options.imageClass=B;this.icon.addClass(B);this.icon.setStyle("background-position","")}else{this.options.imageClass=null;this.icon.setStyle("background-position","center center")}}if(!A){this.wrapper.addClass("jxInputIconHidden")}else{this.wrapper.removeClass("jxInputIconHidden")}},valueChanged:function(){this.fireEvent("change",this)},setValue:function(A){this.field.set("value",A);this.buttonSet.buttons.each(function(B){B.setAct
 ive(B.options.label===A)},this)},onKeyPress:function(A){if(A.key=="enter"){this.valueChanged()}},add:function(){$A(arguments).flatten().each(function(B){var A=new Jx.Menu.Item($merge(B,{toggle:true}));this.menu.add(A);this.buttonSet.add(A);if(B.selected){this.buttonSet.setActiveButton(A)}},this)},remove:function(A){var B;if($type(A)=="number"&&A<this.buttonSet.buttons.length){B=this.buttonSet.buttons[A]}else{if($type(A)=="string"){this.buttonSet.buttons.some(function(C){if(C.options.label===A){B=C;return true}return false},this)}}if(B){this.buttonSet.remove(B);this.menu.remove(B)}},empty:function(){this.menu.empty();this.buttonSet.empty();this.setLabel("");this.setImage(Jx.aPixel.src)},enable:function(){this.parent();this.menu.setEnabled(true)},disable:function(){this.parent();this.menu.setEnabled(false)}});Jx.Field.Password=new Class({Extends:Jx.Field,options:{template:'<span class="jxInputContainer"><label class="jxInputLabel" ></label><input class="jxInputPassword" type="
 password" name="{name}"/><span class="jxInputTag"></span></span>'},type:"Password"});Jx.Field.Color=new Class({Extends:Jx.Field,Binds:["changed","hide","keyup","changeText"],type:"Color",options:{buttonTemplate:'<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',template:'<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',showOnHover:false,showDelay:250,errorMsg:"Invalid Web-Color",color:"#000000"},button:null,validator:null,render:function(){this.classes.combine({wrapper:"jxInputWrapper",revealer:"jxInputRevealer",icon:"jxInputIcon"});this.parent();var A=this;if(!Jx.Field.Color.ColorPalette){Jx.Field.Color.ColorPalette=new Jx.ColorPalette(this.options)}this.button=new Jx.Butt
 on.Flyout({template:this.options.buttonTemplate,imageClass:"jxInputRevealerIcon",positionElement:this.field,onBeforeOpen:function(){if(Jx.Field.Color.ColorPalette.currentButton){Jx.Field.Color.ColorPalette.currentButton.hide()}Jx.Field.Color.ColorPalette.currentButton=this;Jx.Field.Color.ColorPalette.addEvent("change",A.changed);Jx.Field.Color.ColorPalette.addEvent("click",A.hide);this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);Jx.Field.Color.ColorPalette.domObj.setStyle("display","block")},onOpen:function(){Jx.Field.Color.ColorPalette.options.color=A.options.color;Jx.Field.Color.ColorPalette.updateSelected()}}).addTo(this.revealer);this.validator=new Jx.Plugin.Field.Validator({validators:[{validatorClass:"colorHex",validator:{name:"colorValidator",options:{validateOnChange:false,errorMsg:A.options.errorMsg,test:function(E,C){try{var F=E.get("value").hexToRgb(true);if(F==null){return false}for(var B=0;B<3;B++){if(F[B].toString()=="NaN"){return false}}}catch(D){r
 eturn false}F=F.rgbToHex().toUpperCase();A.setColor(F);return true}}}}],validateOnBlur:true,validateOnChange:true});this.validator.attach(this);this.field.addEvent("keyup",this.onKeyUp.bind(this));if(this.options.showOnHover){this.field.addEvent("mouseenter",function(B){A.button.clicked.delay(A.options.showDelay,A.button)})}this.setValue(this.options.color);this.icon.setStyle("background-color",this.options.color)},onKeyUp:function(B){var A=this.getValue();if(A.substring(0,1)=="#"){A=A.substring(1)}if(A.toLowerCase().match(/^[0-9a-f]{6}$/)){this.options.color="#"+A.toUpperCase();this.setColor(this.options.color)}},setColor:function(A){this.options.color=A;this.setValue(A);this.icon.setStyle("background-color",A)},changed:function(){var A=Jx.Field.Color.ColorPalette.options.color;this.setColor(A)},hide:function(){this.button.setActive(false);Jx.Field.Color.ColorPalette.removeEvent("change",this.changed);Jx.Field.Color.ColorPalette.removeEvent("click",this.hide);this.button.hi
 de();Jx.Field.Color.ColorPalette.currentButton=null},changeText:function(A){this.parent()}});
\ No newline at end of file

Added: trunk/lib/jxLib/jxlib.uncompressed.09Feb.js
===================================================================
--- trunk/lib/jxLib/jxlib.uncompressed.09Feb.js	                        (rev 0)
+++ trunk/lib/jxLib/jxlib.uncompressed.09Feb.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,42681 @@
+/******************************************************************************
+ * 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, 3.0alpha
+ * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+/*
+---
+
+script: Core.js
+
+description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [MooTools, Native, Hash.base, Array.each, $util]
+
+...
+*/
+
+var MooTools = {
+	'version': '1.2.5dev',
+	'build': '%build%'
+};
+
+var Native = function(options){
+	options = options || {};
+	var name = options.name;
+	var legacy = options.legacy;
+	var protect = options.protect;
+	var methods = options.implement;
+	var generics = options.generics;
+	var initialize = options.initialize;
+	var afterImplement = options.afterImplement || function(){};
+	var object = initialize || legacy;
+	generics = generics !== false;
+
+	object.constructor = Native;
+	object.$family = {name: 'native'};
+	if (legacy && initialize) object.prototype = legacy.prototype;
+	object.prototype.constructor = object;
+
+	if (name){
+		var family = name.toLowerCase();
+		object.prototype.$family = {name: family};
+		Native.typize(object, family);
+	}
+
+	var add = function(obj, name, method, force){
+		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
+		if (generics) Native.genericize(obj, name, protect);
+		afterImplement.call(obj, name, method);
+		return obj;
+	};
+
+	object.alias = function(a1, a2, a3){
+		if (typeof a1 == 'string'){
+			var pa1 = this.prototype[a1];
+			if ((a1 = pa1)) return add(this, a2, a1, a3);
+		}
+		for (var a in a1) this.alias(a, a1[a], a2);
+		return this;
+	};
+
+	object.implement = function(a1, a2, a3){
+		if (typeof a1 == 'string') return add(this, a1, a2, a3);
+		for (var p in a1) add(this, p, a1[p], a2);
+		return this;
+	};
+
+	if (methods) object.implement(methods);
+
+	return object;
+};
+
+Native.genericize = function(object, property, check){
+	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
+		var args = Array.prototype.slice.call(arguments);
+		return object.prototype[property].apply(args.shift(), args);
+	};
+};
+
+Native.implement = function(objects, properties){
+	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
+};
+
+Native.typize = function(object, family){
+	if (!object.type) object.type = function(item){
+		return ($type(item) === family);
+	};
+};
+
+(function(){
+	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
+	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
+
+	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
+	for (var t in types) Native.typize(types[t], t);
+
+	var generics = {
+		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
+		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
+	};
+	for (var g in generics){
+		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
+	}
+})();
+
+var Hash = new Native({
+
+	name: 'Hash',
+
+	initialize: function(object){
+		if ($type(object) == 'hash') object = $unlink(object.getClean());
+		for (var key in object) this[key] = object[key];
+		return this;
+	}
+
+});
+
+Hash.implement({
+
+	forEach: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
+		}
+	},
+
+	getClean: function(){
+		var clean = {};
+		for (var key in this){
+			if (this.hasOwnProperty(key)) clean[key] = this[key];
+		}
+		return clean;
+	},
+
+	getLength: function(){
+		var length = 0;
+		for (var key in this){
+			if (this.hasOwnProperty(key)) length++;
+		}
+		return length;
+	}
+
+});
+
+Hash.alias('forEach', 'each');
+
+Array.implement({
+
+	forEach: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
+	}
+
+});
+
+Array.alias('forEach', 'each');
+
+function $A(iterable){
+	if (iterable.item){
+		var l = iterable.length, array = new Array(l);
+		while (l--) array[l] = iterable[l];
+		return array;
+	}
+	return Array.prototype.slice.call(iterable);
+};
+
+function $arguments(i){
+	return function(){
+		return arguments[i];
+	};
+};
+
+function $chk(obj){
+	return !!(obj || obj === 0);
+};
+
+function $clear(timer){
+	clearTimeout(timer);
+	clearInterval(timer);
+	return null;
+};
+
+function $defined(obj){
+	return (obj != undefined);
+};
+
+function $each(iterable, fn, bind){
+	var type = $type(iterable);
+	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
+};
+
+function $empty(){};
+
+function $extend(original, extended){
+	for (var key in (extended || {})) original[key] = extended[key];
+	return original;
+};
+
+function $H(object){
+	return new Hash(object);
+};
+
+function $lambda(value){
+	return ($type(value) == 'function') ? value : function(){
+		return value;
+	};
+};
+
+function $merge(){
+	var args = Array.slice(arguments);
+	args.unshift({});
+	return $mixin.apply(null, args);
+};
+
+function $mixin(mix){
+	for (var i = 1, l = arguments.length; i < l; i++){
+		var object = arguments[i];
+		if ($type(object) != 'object') continue;
+		for (var key in object){
+			var op = object[key], mp = mix[key];
+			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
+		}
+	}
+	return mix;
+};
+
+function $pick(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		if (arguments[i] != undefined) return arguments[i];
+	}
+	return null;
+};
+
+function $random(min, max){
+	return Math.floor(Math.random() * (max - min + 1) + min);
+};
+
+function $splat(obj){
+	var type = $type(obj);
+	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
+};
+
+var $time = Date.now || function(){
+	return +new Date;
+};
+
+function $try(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		try {
+			return arguments[i]();
+		} catch(e){}
+	}
+	return null;
+};
+
+function $type(obj){
+	if (obj == undefined) return false;
+	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
+	if (obj.nodeName){
+		switch (obj.nodeType){
+			case 1: return 'element';
+			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
+		}
+	} else if (typeof obj.length == 'number'){
+		if (obj.callee) return 'arguments';
+		else if (obj.item) return 'collection';
+	}
+	return typeof obj;
+};
+
+function $unlink(object){
+	var unlinked;
+	switch ($type(object)){
+		case 'object':
+			unlinked = {};
+			for (var p in object) unlinked[p] = $unlink(object[p]);
+		break;
+		case 'hash':
+			unlinked = new Hash(object);
+		break;
+		case 'array':
+			unlinked = [];
+			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+		break;
+		default: return object;
+	}
+	return unlinked;
+};
+/*
+---
+
+script: Browser.js
+
+description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: 
+- /Native
+- /$util
+
+provides: [Browser, Window, Document, $exec]
+
+...
+*/
+
+var Browser = $merge({
+
+	Engine: {name: 'unknown', version: 0},
+
+	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
+
+	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
+
+	Plugins: {},
+
+	Engines: {
+
+		presto: function(){
+			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
+		},
+
+		trident: function(){
+			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
+		},
+
+		webkit: function(){
+			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
+		},
+
+		gecko: function(){
+			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
+		}
+
+	}
+
+}, Browser || {});
+
+Browser.Platform[Browser.Platform.name] = true;
+
+Browser.detect = function(){
+
+	for (var engine in this.Engines){
+		var version = this.Engines[engine]();
+		if (version){
+			this.Engine = {name: engine, version: version};
+			this.Engine[engine] = this.Engine[engine + version] = true;
+			break;
+		}
+	}
+
+	return {name: engine, version: version};
+
+};
+
+Browser.detect();
+
+Browser.Request = function(){
+	return $try(function(){
+		return new XMLHttpRequest();
+	}, function(){
+		return new ActiveXObject('MSXML2.XMLHTTP');
+	}, function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
+	});
+};
+
+Browser.Features.xhr = !!(Browser.Request());
+
+Browser.Plugins.Flash = (function(){
+	var version = ($try(function(){
+		return navigator.plugins['Shockwave Flash'].description;
+	}, function(){
+		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+	}) || '0 r0').match(/\d+/g);
+	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+})();
+
+function $exec(text){
+	if (!text) return text;
+	if (window.execScript){
+		window.execScript(text);
+	} else {
+		var script = document.createElement('script');
+		script.setAttribute('type', 'text/javascript');
+		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
+		document.head.appendChild(script);
+		document.head.removeChild(script);
+	}
+	return text;
+};
+
+Native.UID = 1;
+
+var $uid = (Browser.Engine.trident) ? function(item){
+	return (item.uid || (item.uid = [Native.UID++]))[0];
+} : function(item){
+	return item.uid || (item.uid = Native.UID++);
+};
+
+var Window = new Native({
+
+	name: 'Window',
+
+	legacy: (Browser.Engine.trident) ? null: window.Window,
+
+	initialize: function(win){
+		$uid(win);
+		if (!win.Element){
+			win.Element = $empty;
+			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
+			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
+		}
+		win.document.window = win;
+		return $extend(win, Window.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		window[property] = Window.Prototype[property] = value;
+	}
+
+});
+
+Window.Prototype = {$family: {name: 'window'}};
+
+new Window(window);
+
+var Document = new Native({
+
+	name: 'Document',
+
+	legacy: (Browser.Engine.trident) ? null: window.Document,
+
+	initialize: function(doc){
+		$uid(doc);
+		doc.head = doc.getElementsByTagName('head')[0];
+		doc.html = doc.getElementsByTagName('html')[0];
+		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
+			doc.execCommand("BackgroundImageCache", false, true);
+		});
+		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
+			doc.window.detachEvent('onunload', arguments.callee);
+			doc.head = doc.html = doc.window = null;
+		});
+		return $extend(doc, Document.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		document[property] = Document.Prototype[property] = value;
+	}
+
+});
+
+Document.Prototype = {$family: {name: 'document'}};
+
+new Document(document);
+/*
+---
+
+script: Array.js
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Array.each
+
+provides: [Array]
+
+...
+*/
+
+Array.implement({
+
+	every: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (!fn.call(bind, this[i], i, this)) return false;
+		}
+		return true;
+	},
+
+	filter: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
+		}
+		return results;
+	},
+
+	clean: function(){
+		return this.filter($defined);
+	},
+
+	indexOf: function(item, from){
+		var len = this.length;
+		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+			if (this[i] === item) return i;
+		}
+		return -1;
+	},
+
+	map: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
+		return results;
+	},
+
+	some: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) return true;
+		}
+		return false;
+	},
+
+	associate: function(keys){
+		var obj = {}, length = Math.min(this.length, keys.length);
+		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+		return obj;
+	},
+
+	link: function(object){
+		var result = {};
+		for (var i = 0, l = this.length; i < l; i++){
+			for (var key in object){
+				if (object[key](this[i])){
+					result[key] = this[i];
+					delete object[key];
+					break;
+				}
+			}
+		}
+		return result;
+	},
+
+	contains: function(item, from){
+		return this.indexOf(item, from) != -1;
+	},
+
+	extend: function(array){
+		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
+		return this;
+	},
+	
+	getLast: function(){
+		return (this.length) ? this[this.length - 1] : null;
+	},
+
+	getRandom: function(){
+		return (this.length) ? this[$random(0, this.length - 1)] : null;
+	},
+
+	include: function(item){
+		if (!this.contains(item)) this.push(item);
+		return this;
+	},
+
+	combine: function(array){
+		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+		return this;
+	},
+
+	erase: function(item){
+		for (var i = this.length; i--; i){
+			if (this[i] === item) this.splice(i, 1);
+		}
+		return this;
+	},
+
+	empty: function(){
+		this.length = 0;
+		return this;
+	},
+
+	flatten: function(){
+		var array = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			var type = $type(this[i]);
+			if (!type) continue;
+			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
+		}
+		return array;
+	},
+
+	hexToRgb: function(array){
+		if (this.length != 3) return null;
+		var rgb = this.map(function(value){
+			if (value.length == 1) value += value;
+			return value.toInt(16);
+		});
+		return (array) ? rgb : 'rgb(' + rgb + ')';
+	},
+
+	rgbToHex: function(array){
+		if (this.length < 3) return null;
+		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+		var hex = [];
+		for (var i = 0; i < 3; i++){
+			var bit = (this[i] - 0).toString(16);
+			hex.push((bit.length == 1) ? '0' + bit : bit);
+		}
+		return (array) ? hex : '#' + hex.join('');
+	}
+
+});
+/*
+---
+
+script: Function.js
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Function]
+
+...
+*/
+
+Function.implement({
+
+	extend: function(properties){
+		for (var property in properties) this[property] = properties[property];
+		return this;
+	},
+
+	create: function(options){
+		var self = this;
+		options = options || {};
+		return function(event){
+			var args = options.arguments;
+			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+			if (options.event) args = [event || window.event].extend(args);
+			var returns = function(){
+				return self.apply(options.bind || null, args);
+			};
+			if (options.delay) return setTimeout(returns, options.delay);
+			if (options.periodical) return setInterval(returns, options.periodical);
+			if (options.attempt) return $try(returns);
+			return returns();
+		};
+	},
+
+	run: function(args, bind){
+		return this.apply(bind, $splat(args));
+	},
+
+	pass: function(args, bind){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bind: function(bind, args){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bindWithEvent: function(bind, args){
+		return this.create({bind: bind, arguments: args, event: true});
+	},
+
+	attempt: function(args, bind){
+		return this.create({bind: bind, arguments: args, attempt: true})();
+	},
+
+	delay: function(delay, bind, args){
+		return this.create({bind: bind, arguments: args, delay: delay})();
+	},
+
+	periodical: function(periodical, bind, args){
+		return this.create({bind: bind, arguments: args, periodical: periodical})();
+	}
+
+});
+/*
+---
+
+script: Number.js
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Number]
+
+...
+*/
+
+Number.implement({
+
+	limit: function(min, max){
+		return Math.min(max, Math.max(min, this));
+	},
+
+	round: function(precision){
+		precision = Math.pow(10, precision || 0);
+		return Math.round(this * precision) / precision;
+	},
+
+	times: function(fn, bind){
+		for (var i = 0; i < this; i++) fn.call(bind, i, this);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	}
+
+});
+
+Number.alias('times', 'each');
+
+(function(math){
+	var methods = {};
+	math.each(function(name){
+		if (!Number[name]) methods[name] = function(){
+			return Math[name].apply(null, [this].concat($A(arguments)));
+		};
+	});
+	Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+/*
+---
+
+script: String.js
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires:
+- /Native
+
+provides: [String]
+
+...
+*/
+
+String.implement({
+
+	test: function(regex, params){
+		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
+	},
+
+	contains: function(string, separator){
+		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+	},
+
+	trim: function(){
+		return this.replace(/^\s+|\s+$/g, '');
+	},
+
+	clean: function(){
+		return this.replace(/\s+/g, ' ').trim();
+	},
+
+	camelCase: function(){
+		return this.replace(/-\D/g, function(match){
+			return match.charAt(1).toUpperCase();
+		});
+	},
+
+	hyphenate: function(){
+		return this.replace(/[A-Z]/g, function(match){
+			return ('-' + match.charAt(0).toLowerCase());
+		});
+	},
+
+	capitalize: function(){
+		return this.replace(/\b[a-z]/g, function(match){
+			return match.toUpperCase();
+		});
+	},
+
+	escapeRegExp: function(){
+		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	hexToRgb: function(array){
+		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+		return (hex) ? hex.slice(1).hexToRgb(array) : null;
+	},
+
+	rgbToHex: function(array){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHex(array) : null;
+	},
+
+	stripScripts: function(option){
+		var scripts = '';
+		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
+			scripts += arguments[1] + '\n';
+			return '';
+		});
+		if (option === true) $exec(scripts);
+		else if ($type(option) == 'function') option(scripts, text);
+		return text;
+	},
+
+	substitute: function(object, regexp){
+		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+			if (match.charAt(0) == '\\') return match.slice(1);
+			return (object[name] != undefined) ? object[name] : '';
+		});
+	}
+
+});
+/*
+---
+
+script: Hash.js
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+- /Hash.base
+
+provides: [Hash]
+
+...
+*/
+
+Hash.implement({
+
+	has: Object.prototype.hasOwnProperty,
+
+	keyOf: function(value){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && this[key] === value) return key;
+		}
+		return null;
+	},
+
+	hasValue: function(value){
+		return (Hash.keyOf(this, value) !== null);
+	},
+
+	extend: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.set(this, key, value);
+		}, this);
+		return this;
+	},
+
+	combine: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.include(this, key, value);
+		}, this);
+		return this;
+	},
+
+	erase: function(key){
+		if (this.hasOwnProperty(key)) delete this[key];
+		return this;
+	},
+
+	get: function(key){
+		return (this.hasOwnProperty(key)) ? this[key] : null;
+	},
+
+	set: function(key, value){
+		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+		return this;
+	},
+
+	empty: function(){
+		Hash.each(this, function(value, key){
+			delete this[key];
+		}, this);
+		return this;
+	},
+
+	include: function(key, value){
+		if (this[key] == undefined) this[key] = value;
+		return this;
+	},
+
+	map: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			results.set(key, fn.call(bind, value, key, this));
+		}, this);
+		return results;
+	},
+
+	filter: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			if (fn.call(bind, value, key, this)) results.set(key, value);
+		}, this);
+		return results;
+	},
+
+	every: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
+		}
+		return true;
+	},
+
+	some: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
+		}
+		return false;
+	},
+
+	getKeys: function(){
+		var keys = [];
+		Hash.each(this, function(value, key){
+			keys.push(key);
+		});
+		return keys;
+	},
+
+	getValues: function(){
+		var values = [];
+		Hash.each(this, function(value){
+			values.push(value);
+		});
+		return values;
+	},
+
+	toQueryString: function(base){
+		var queryString = [];
+		Hash.each(this, function(value, key){
+			if (base) key = base + '[' + key + ']';
+			var result;
+			switch ($type(value)){
+				case 'object': result = Hash.toQueryString(value, key); break;
+				case 'array':
+					var qs = {};
+					value.each(function(val, i){
+						qs[i] = val;
+					});
+					result = Hash.toQueryString(qs, key);
+				break;
+				default: result = key + '=' + encodeURIComponent(value);
+			}
+			if (value != undefined) queryString.push(result);
+		});
+
+		return queryString.join('&');
+	}
+
+});
+
+Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
+/*
+---
+
+script: Event.js
+
+description: Contains the Event Class, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Hash
+- /Array
+- /Function
+- /String
+
+provides: [Event]
+
+...
+*/
+
+var Event = new Native({
+
+	name: 'Event',
+
+	initialize: function(event, win){
+		win = win || window;
+		var doc = win.document;
+		event = event || win.event;
+		if (event.$extended) return event;
+		this.$extended = true;
+		var type = event.type;
+		var target = event.target || event.srcElement;
+		while (target && target.nodeType == 3) target = target.parentNode;
+
+		if (type.test(/key/)){
+			var code = event.which || event.keyCode;
+			var key = Event.Keys.keyOf(code);
+			if (type == 'keydown'){
+				var fKey = code - 111;
+				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
+			}
+			key = key || String.fromCharCode(code).toLowerCase();
+		} else if (type.match(/(click|mouse|menu)/i)){
+			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+			var page = {
+				x: event.pageX || event.clientX + doc.scrollLeft,
+				y: event.pageY || event.clientY + doc.scrollTop
+			};
+			var client = {
+				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
+				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
+			};
+			if (type.match(/DOMMouseScroll|mousewheel/)){
+				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+			}
+			var rightClick = (event.which == 3) || (event.button == 2);
+			var related = null;
+			if (type.match(/over|out/)){
+				switch (type){
+					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
+					case 'mouseout': related = event.relatedTarget || event.toElement;
+				}
+				if (!(function(){
+					while (related && related.nodeType == 3) related = related.parentNode;
+					return true;
+				}).create({attempt: Browser.Engine.gecko})()) related = false;
+			}
+		}
+
+		return $extend(this, {
+			event: event,
+			type: type,
+
+			page: page,
+			client: client,
+			rightClick: rightClick,
+
+			wheel: wheel,
+
+			relatedTarget: related,
+			target: target,
+
+			code: code,
+			key: key,
+
+			shift: event.shiftKey,
+			control: event.ctrlKey,
+			alt: event.altKey,
+			meta: event.metaKey
+		});
+	}
+
+});
+
+Event.Keys = new Hash({
+	'enter': 13,
+	'up': 38,
+	'down': 40,
+	'left': 37,
+	'right': 39,
+	'esc': 27,
+	'space': 32,
+	'backspace': 8,
+	'tab': 9,
+	'delete': 46
+});
+
+Event.implement({
+
+	stop: function(){
+		return this.stopPropagation().preventDefault();
+	},
+
+	stopPropagation: function(){
+		if (this.event.stopPropagation) this.event.stopPropagation();
+		else this.event.cancelBubble = true;
+		return this;
+	},
+
+	preventDefault: function(){
+		if (this.event.preventDefault) this.event.preventDefault();
+		else this.event.returnValue = false;
+		return this;
+	}
+
+});
+/*
+---
+
+script: Class.js
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Native
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Class]
+
+...
+*/
+
+function Class(params){
+	
+	if (params instanceof Function) params = {initialize: params};
+	
+	var newClass = function(){
+		Object.reset(this);
+		if (newClass._prototyping) return this;
+		this._current = $empty;
+		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+		delete this._current; delete this.caller;
+		return value;
+	}.extend(this);
+	
+	newClass.implement(params);
+	
+	newClass.constructor = Class;
+	newClass.prototype.constructor = newClass;
+
+	return newClass;
+
+};
+
+Function.prototype.protect = function(){
+	this._protected = true;
+	return this;
+};
+
+Object.reset = function(object, key){
+		
+	if (key == null){
+		for (var p in object) Object.reset(object, p);
+		return object;
+	}
+	
+	delete object[key];
+	
+	switch ($type(object[key])){
+		case 'object':
+			var F = function(){};
+			F.prototype = object[key];
+			var i = new F;
+			object[key] = Object.reset(i);
+		break;
+		case 'array': object[key] = $unlink(object[key]); break;
+	}
+	
+	return object;
+	
+};
+
+new Native({name: 'Class', initialize: Class}).extend({
+
+	instantiate: function(F){
+		F._prototyping = true;
+		var proto = new F;
+		delete F._prototyping;
+		return proto;
+	},
+	
+	wrap: function(self, key, method){
+		if (method._origin) method = method._origin;
+		
+		return function(){
+			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
+			var caller = this.caller, current = this._current;
+			this.caller = current; this._current = arguments.callee;
+			var result = method.apply(this, arguments);
+			this._current = current; this.caller = caller;
+			return result;
+		}.extend({_owner: self, _origin: method, _name: key});
+
+	}
+	
+});
+
+Class.implement({
+	
+	implement: function(key, value){
+		
+		if ($type(key) == 'object'){
+			for (var p in key) this.implement(p, key[p]);
+			return this;
+		}
+		
+		var mutator = Class.Mutators[key];
+		
+		if (mutator){
+			value = mutator.call(this, value);
+			if (value == null) return this;
+		}
+		
+		var proto = this.prototype;
+
+		switch ($type(value)){
+			
+			case 'function':
+				if (value._hidden) return this;
+				proto[key] = Class.wrap(this, key, value);
+			break;
+			
+			case 'object':
+				var previous = proto[key];
+				if ($type(previous) == 'object') $mixin(previous, value);
+				else proto[key] = $unlink(value);
+			break;
+			
+			case 'array':
+				proto[key] = $unlink(value);
+			break;
+			
+			default: proto[key] = value;
+
+		}
+		
+		return this;
+
+	}
+	
+});
+
+Class.Mutators = {
+	
+	Extends: function(parent){
+
+		this.parent = parent;
+		this.prototype = Class.instantiate(parent);
+
+		this.implement('parent', function(){
+			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
+			if (!previous) throw new Error('The method "' + name + '" has no parent.');
+			return previous.apply(this, arguments);
+		}.protect());
+
+	},
+
+	Implements: function(items){
+		$splat(items).each(function(item){
+			if (item instanceof Function) item = Class.instantiate(item);
+			this.implement(item);
+		}, this);
+
+	}
+	
+};
+/*
+---
+
+script: Class.Extras.js
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires:
+- /Class
+
+provides: [Chain, Events, Options]
+
+...
+*/
+
+var Chain = new Class({
+
+	$chain: [],
+
+	chain: function(){
+		this.$chain.extend(Array.flatten(arguments));
+		return this;
+	},
+
+	callChain: function(){
+		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+	},
+
+	clearChain: function(){
+		this.$chain.empty();
+		return this;
+	}
+
+});
+
+var Events = new Class({
+
+	$events: {},
+
+	addEvent: function(type, fn, internal){
+		type = Events.removeOn(type);
+		if (fn != $empty){
+			this.$events[type] = this.$events[type] || [];
+			this.$events[type].include(fn);
+			if (internal) fn.internal = true;
+		}
+		return this;
+	},
+
+	addEvents: function(events){
+		for (var type in events) this.addEvent(type, events[type]);
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		type = Events.removeOn(type);
+		if (!this.$events || !this.$events[type]) return this;
+		this.$events[type].each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		type = Events.removeOn(type);
+		if (!this.$events[type]) return this;
+		if (!fn.internal) this.$events[type].erase(fn);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		if (events) events = Events.removeOn(events);
+		for (type in this.$events){
+			if (events && events != type) continue;
+			var fns = this.$events[type];
+			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
+		}
+		return this;
+	}
+
+});
+
+Events.removeOn = function(string){
+	return string.replace(/^on([A-Z])/, function(full, first){
+		return first.toLowerCase();
+	});
+};
+
+var Options = new Class({
+
+	setOptions: function(){
+		this.options = $merge.run([this.options].extend(arguments));
+		if (!this.addEvent) return this;
+		for (var option in this.options){
+			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+			this.addEvent(option, this.options[option]);
+			delete this.options[option];
+		}
+		return this;
+	}
+
+});
+/*
+---
+
+script: Element.js
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Element, Elements, $, $$, Iframe]
+
+...
+*/
+
+var Element = new Native({
+
+	name: 'Element',
+
+	legacy: window.Element,
+
+	initialize: function(tag, props){
+		var konstructor = Element.Constructors.get(tag);
+		if (konstructor) return konstructor(props);
+		if (typeof tag == 'string') return document.newElement(tag, props);
+		return document.id(tag).set(props);
+	},
+
+	afterImplement: function(key, value){
+		Element.Prototype[key] = value;
+		if (Array[key]) return;
+		Elements.implement(key, function(){
+			var items = [], elements = true;
+			for (var i = 0, j = this.length; i < j; i++){
+				var returns = this[i][key].apply(this[i], arguments);
+				items.push(returns);
+				if (elements) elements = ($type(returns) == 'element');
+			}
+			return (elements) ? new Elements(items) : items;
+		});
+	}
+
+});
+
+Element.Prototype = {$family: {name: 'element'}};
+
+Element.Constructors = new Hash;
+
+var IFrame = new Native({
+
+	name: 'IFrame',
+
+	generics: false,
+
+	initialize: function(){
+		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
+		var props = params.properties || {};
+		var iframe = document.id(params.iframe);
+		var onload = props.onload || $empty;
+		delete props.onload;
+		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
+		iframe = new Element(iframe || 'iframe', props);
+		var onFrameLoad = function(){
+			var host = $try(function(){
+				return iframe.contentWindow.location.host;
+			});
+			if (!host || host == window.location.host){
+				var win = new Window(iframe.contentWindow);
+				new Document(iframe.contentWindow.document);
+				$extend(win.Element.prototype, Element.Prototype);
+			}
+			onload.call(iframe.contentWindow, iframe.contentWindow.document);
+		};
+		var contentWindow = $try(function(){
+			return iframe.contentWindow;
+		});
+		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
+		return iframe;
+	}
+
+});
+
+var Elements = new Native({
+
+	initialize: function(elements, options){
+		options = $extend({ddup: true, cash: true}, options);
+		elements = elements || [];
+		if (options.ddup || options.cash){
+			var uniques = {}, returned = [];
+			for (var i = 0, l = elements.length; i < l; i++){
+				var el = document.id(elements[i], !options.cash);
+				if (options.ddup){
+					if (uniques[el.uid]) continue;
+					uniques[el.uid] = true;
+				}
+				if (el) returned.push(el);
+			}
+			elements = returned;
+		}
+		return (options.cash) ? $extend(elements, this) : elements;
+	}
+
+});
+
+Elements.implement({
+
+	filter: function(filter, bind){
+		if (!filter) return this;
+		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
+			return item.match(filter);
+		} : filter, bind));
+	}
+
+});
+
+Document.implement({
+
+	newElement: function(tag, props){
+		if (Browser.Engine.trident && props){
+			['name', 'type', 'checked'].each(function(attribute){
+				if (!props[attribute]) return;
+				tag += ' ' + attribute + '="' + props[attribute] + '"';
+				if (attribute != 'checked') delete props[attribute];
+			});
+			tag = '<' + tag + '>';
+		}
+		return document.id(this.createElement(tag)).set(props);
+	},
+
+	newTextNode: function(text){
+		return this.createTextNode(text);
+	},
+
+	getDocument: function(){
+		return this;
+	},
+
+	getWindow: function(){
+		return this.window;
+	},
+	
+	id: (function(){
+		
+		var types = {
+
+			string: function(id, nocash, doc){
+				id = doc.getElementById(id);
+				return (id) ? types.element(id, nocash) : null;
+			},
+			
+			element: function(el, nocash){
+				$uid(el);
+				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
+					var proto = Element.Prototype;
+					for (var p in proto) el[p] = proto[p];
+				};
+				return el;
+			},
+			
+			object: function(obj, nocash, doc){
+				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+				return null;
+			}
+			
+		};
+
+		types.textnode = types.whitespace = types.window = types.document = $arguments(0);
+		
+		return function(el, nocash, doc){
+			if (el && el.$family && el.uid) return el;
+			var type = $type(el);
+			return (types[type]) ? types[type](el, nocash, doc || document) : null;
+		};
+
+	})()
+
+});
+
+if (window.$ == null) Window.implement({
+	$: function(el, nc){
+		return document.id(el, nc, this.document);
+	}
+});
+
+Window.implement({
+
+	$$: function(selector){
+		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
+		var elements = [];
+		var args = Array.flatten(arguments);
+		for (var i = 0, l = args.length; i < l; i++){
+			var item = args[i];
+			switch ($type(item)){
+				case 'element': elements.push(item); break;
+				case 'string': elements.extend(this.document.getElements(item, true));
+			}
+		}
+		return new Elements(elements);
+	},
+
+	getDocument: function(){
+		return this.document;
+	},
+
+	getWindow: function(){
+		return this;
+	}
+
+});
+
+Native.implement([Element, Document], {
+
+	getElement: function(selector, nocash){
+		return document.id(this.getElements(selector, true)[0] || null, nocash);
+	},
+
+	getElements: function(tags, nocash){
+		tags = tags.split(',');
+		var elements = [];
+		var ddup = (tags.length > 1);
+		tags.each(function(tag){
+			var partial = this.getElementsByTagName(tag.trim());
+			(ddup) ? elements.extend(partial) : elements = partial;
+		}, this);
+		return new Elements(elements, {ddup: ddup, cash: !nocash});
+	}
+
+});
+
+(function(){
+
+var collected = {}, storage = {};
+var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
+
+var get = function(uid){
+	return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item, retain){
+	if (!item) return;
+	var uid = item.uid;
+	if (retain !== true) retain = false;
+	if (Browser.Engine.trident){
+		if (item.clearAttributes){
+			var clone = retain && item.cloneNode(false);
+			item.clearAttributes();
+			if (clone) item.mergeAttributes(clone);
+		} else if (item.removeEvents){
+			item.removeEvents();
+		}
+		if ((/object/i).test(item.tagName)){
+			for (var p in item){
+				if (typeof item[p] == 'function') item[p] = $empty;
+			}
+			Element.dispose(item);
+		}
+	}	
+	if (!uid) return;
+	collected[uid] = storage[uid] = null;
+};
+
+var purge = function(){
+	Hash.each(collected, clean);
+	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
+	if (window.CollectGarbage) CollectGarbage();
+	collected = storage = null;
+};
+
+var walk = function(element, walk, start, match, all, nocash){
+	var el = element[start || walk];
+	var elements = [];
+	while (el){
+		if (el.nodeType == 1 && (!match || Element.match(el, match))){
+			if (!all) return document.id(el, nocash);
+			elements.push(el);
+		}
+		el = el[walk];
+	}
+	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
+};
+
+var attributes = {
+	'html': 'innerHTML',
+	'class': 'className',
+	'for': 'htmlFor',
+	'defaultValue': 'defaultValue',
+	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
+};
+var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
+var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
+
+bools = bools.associate(bools);
+
+Hash.extend(attributes, bools);
+Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
+
+var inserters = {
+
+	before: function(context, element){
+		if (element.parentNode) element.parentNode.insertBefore(context, element);
+	},
+
+	after: function(context, element){
+		if (!element.parentNode) return;
+		var next = element.nextSibling;
+		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
+	},
+
+	bottom: function(context, element){
+		element.appendChild(context);
+	},
+
+	top: function(context, element){
+		var first = element.firstChild;
+		(first) ? element.insertBefore(context, first) : element.appendChild(context);
+	}
+
+};
+
+inserters.inside = inserters.bottom;
+
+Hash.each(inserters, function(inserter, where){
+
+	where = where.capitalize();
+
+	Element.implement('inject' + where, function(el){
+		inserter(this, document.id(el, true));
+		return this;
+	});
+
+	Element.implement('grab' + where, function(el){
+		inserter(document.id(el, true), this);
+		return this;
+	});
+
+});
+
+Element.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				var property = Element.Properties.get(prop);
+				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		var property = Element.Properties.get(prop);
+		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
+	},
+
+	erase: function(prop){
+		var property = Element.Properties.get(prop);
+		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+		return this;
+	},
+
+	setProperty: function(attribute, value){
+		var key = attributes[attribute];
+		if (value == undefined) return this.removeProperty(attribute);
+		if (key && bools[attribute]) value = !!value;
+		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
+		return this;
+	},
+
+	setProperties: function(attributes){
+		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+		return this;
+	},
+
+	getProperty: function(attribute){
+		var key = attributes[attribute];
+		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
+		return (bools[attribute]) ? !!value : (key) ? value : value || null;
+	},
+
+	getProperties: function(){
+		var args = $A(arguments);
+		return args.map(this.getProperty, this).associate(args);
+	},
+
+	removeProperty: function(attribute){
+		var key = attributes[attribute];
+		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
+		return this;
+	},
+
+	removeProperties: function(){
+		Array.each(arguments, this.removeProperty, this);
+		return this;
+	},
+
+	hasClass: function(className){
+		return this.className.contains(className, ' ');
+	},
+
+	addClass: function(className){
+		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+		return this;
+	},
+
+	removeClass: function(className){
+		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+		return this;
+	},
+
+	toggleClass: function(className){
+		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+	},
+
+	adopt: function(){
+		Array.flatten(arguments).each(function(element){
+			element = document.id(element, true);
+			if (element) this.appendChild(element);
+		}, this);
+		return this;
+	},
+
+	appendText: function(text, where){
+		return this.grab(this.getDocument().newTextNode(text), where);
+	},
+
+	grab: function(el, where){
+		inserters[where || 'bottom'](document.id(el, true), this);
+		return this;
+	},
+
+	inject: function(el, where){
+		inserters[where || 'bottom'](this, document.id(el, true));
+		return this;
+	},
+
+	replaces: function(el){
+		el = document.id(el, true);
+		el.parentNode.replaceChild(this, el);
+		return this;
+	},
+
+	wraps: function(el, where){
+		el = document.id(el, true);
+		return this.replaces(el).grab(el, where);
+	},
+
+	getPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, false, nocash);
+	},
+
+	getAllPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, true, nocash);
+	},
+
+	getNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, false, nocash);
+	},
+
+	getAllNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, true, nocash);
+	},
+
+	getFirst: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
+	},
+
+	getLast: function(match, nocash){
+		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
+	},
+
+	getParent: function(match, nocash){
+		return walk(this, 'parentNode', null, match, false, nocash);
+	},
+
+	getParents: function(match, nocash){
+		return walk(this, 'parentNode', null, match, true, nocash);
+	},
+	
+	getSiblings: function(match, nocash){
+		return this.getParent().getChildren(match, nocash).erase(this);
+	},
+
+	getChildren: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
+	},
+
+	getWindow: function(){
+		return this.ownerDocument.window;
+	},
+
+	getDocument: function(){
+		return this.ownerDocument;
+	},
+
+	getElementById: function(id, nocash){
+		var el = this.ownerDocument.getElementById(id);
+		if (!el) return null;
+		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
+			if (!parent) return null;
+		}
+		return document.id(el, nocash);
+	},
+
+	getSelected: function(){
+		return new Elements($A(this.options).filter(function(option){
+			return option.selected;
+		}));
+	},
+
+	getComputedStyle: function(property){
+		if (this.currentStyle) return this.currentStyle[property.camelCase()];
+		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
+		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
+	},
+
+	toQueryString: function(){
+		var queryString = [];
+		this.getElements('input, select, textarea', true).each(function(el){
+			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
+			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
+				return opt.value;
+			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
+			$splat(value).each(function(val){
+				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
+			});
+		});
+		return queryString.join('&');
+	},
+
+	clone: function(contents, keepid){
+		contents = contents !== false;
+		var clone = this.cloneNode(contents);
+		var clean = function(node, element){
+			if (!keepid) node.removeAttribute('id');
+			if (Browser.Engine.trident){
+				node.clearAttributes();
+				node.mergeAttributes(element);
+				node.removeAttribute('uid');
+				if (node.options){
+					var no = node.options, eo = element.options;
+					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+				}
+			}
+			var prop = props[element.tagName.toLowerCase()];
+			if (prop && element[prop]) node[prop] = element[prop];
+		};
+
+		if (contents){
+			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
+			for (var i = ce.length; i--;) clean(ce[i], te[i]);
+		}
+
+		clean(clone, this);
+		return document.id(clone);
+	},
+
+	destroy: function(){
+		Element.empty(this);
+		Element.dispose(this);
+		clean(this, true);
+		return null;
+	},
+
+	empty: function(){
+		$A(this.childNodes).each(function(node){
+			Element.destroy(node);
+		});
+		return this;
+	},
+
+	dispose: function(){
+		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+	},
+
+	hasChild: function(el){
+		el = document.id(el, true);
+		if (!el) return false;
+		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
+		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
+	},
+
+	match: function(tag){
+		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
+	}
+
+});
+
+Native.implement([Element, Window, Document], {
+
+	addListener: function(type, fn){
+		if (type == 'unload'){
+			var old = fn, self = this;
+			fn = function(){
+				self.removeListener('unload', fn);
+				old();
+			};
+		} else {
+			collected[this.uid] = this;
+		}
+		if (this.addEventListener) this.addEventListener(type, fn, false);
+		else this.attachEvent('on' + type, fn);
+		return this;
+	},
+
+	removeListener: function(type, fn){
+		if (this.removeEventListener) this.removeEventListener(type, fn, false);
+		else this.detachEvent('on' + type, fn);
+		return this;
+	},
+
+	retrieve: function(property, dflt){
+		var storage = get(this.uid), prop = storage[property];
+		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
+		return $pick(prop);
+	},
+
+	store: function(property, value){
+		var storage = get(this.uid);
+		storage[property] = value;
+		return this;
+	},
+
+	eliminate: function(property){
+		var storage = get(this.uid);
+		delete storage[property];
+		return this;
+	}
+
+});
+
+window.addListener('unload', purge);
+
+})();
+
+Element.Properties = new Hash;
+
+Element.Properties.style = {
+
+	set: function(style){
+		this.style.cssText = style;
+	},
+
+	get: function(){
+		return this.style.cssText;
+	},
+
+	erase: function(){
+		this.style.cssText = '';
+	}
+
+};
+
+Element.Properties.tag = {
+
+	get: function(){
+		return this.tagName.toLowerCase();
+	}
+
+};
+
+Element.Properties.html = (function(){
+	var wrapper = document.createElement('div');
+
+	var translations = {
+		table: [1, '<table>', '</table>'],
+		select: [1, '<select>', '</select>'],
+		tbody: [2, '<table><tbody>', '</tbody></table>'],
+		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+	};
+	translations.thead = translations.tfoot = translations.tbody;
+
+	var html = {
+		set: function(){
+			var html = Array.flatten(arguments).join('');
+			var wrap = Browser.Engine.trident && translations[this.get('tag')];
+			if (wrap){
+				var first = wrapper;
+				first.innerHTML = wrap[1] + html + wrap[2];
+				for (var i = wrap[0]; i--;) first = first.firstChild;
+				this.empty().adopt(first.childNodes);
+			} else {
+				this.innerHTML = html;
+			}
+		}
+	};
+
+	html.erase = html.set;
+
+	return html;
+})();
+
+if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
+	get: function(){
+		if (this.innerText) return this.innerText;
+		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
+		var text = temp.innerText;
+		temp.destroy();
+		return text;
+	}
+};
+/*
+---
+
+script: Element.Event.js
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: 
+- /Element
+- /Event
+
+provides: [Element.Event]
+
+...
+*/
+
+Element.Properties.events = {set: function(events){
+	this.addEvents(events);
+}};
+
+Native.implement([Element, Window, Document], {
+
+	addEvent: function(type, fn){
+		var events = this.retrieve('events', {});
+		events[type] = events[type] || {'keys': [], 'values': []};
+		if (events[type].keys.contains(fn)) return this;
+		events[type].keys.push(fn);
+		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
+		if (custom){
+			if (custom.onAdd) custom.onAdd.call(this, fn);
+			if (custom.condition){
+				condition = function(event){
+					if (custom.condition.call(this, event)) return fn.call(this, event);
+					return true;
+				};
+			}
+			realType = custom.base || realType;
+		}
+		var defn = function(){
+			return fn.call(self);
+		};
+		var nativeEvent = Element.NativeEvents[realType];
+		if (nativeEvent){
+			if (nativeEvent == 2){
+				defn = function(event){
+					event = new Event(event, self.getWindow());
+					if (condition.call(self, event) === false) event.stop();
+				};
+			}
+			this.addListener(realType, defn);
+		}
+		events[type].values.push(defn);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		var pos = events[type].keys.indexOf(fn);
+		if (pos == -1) return this;
+		events[type].keys.splice(pos, 1);
+		var value = events[type].values.splice(pos, 1)[0];
+		var custom = Element.Events.get(type);
+		if (custom){
+			if (custom.onRemove) custom.onRemove.call(this, fn);
+			type = custom.base || type;
+		}
+		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
+	},
+
+	addEvents: function(events){
+		for (var event in events) this.addEvent(event, events[event]);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		var attached = this.retrieve('events');
+		if (!attached) return this;
+		if (!events){
+			for (type in attached) this.removeEvents(type);
+			this.eliminate('events');
+		} else if (attached[events]){
+			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
+			attached[events] = null;
+		}
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		events[type].keys.each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	cloneEvents: function(from, type){
+		from = document.id(from);
+		var fevents = from.retrieve('events');
+		if (!fevents) return this;
+		if (!type){
+			for (var evType in fevents) this.cloneEvents(from, evType);
+		} else if (fevents[type]){
+			fevents[type].keys.each(function(fn){
+				this.addEvent(type, fn);
+			}, this);
+		}
+		return this;
+	}
+
+});
+
+Element.NativeEvents = {
+	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+	keydown: 2, keypress: 2, keyup: 2, //keyboard
+	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
+	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+	error: 1, abort: 1, scroll: 1 //misc
+};
+
+(function(){
+
+var $check = function(event){
+	var related = event.relatedTarget;
+	if (related == undefined) return true;
+	if (related === false) return false;
+	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
+};
+
+Element.Events = new Hash({
+
+	mouseenter: {
+		base: 'mouseover',
+		condition: $check
+	},
+
+	mouseleave: {
+		base: 'mouseout',
+		condition: $check
+	},
+
+	mousewheel: {
+		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
+	}
+
+});
+
+})();
+/*
+---
+
+script: Element.Style.js
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Element.Style]
+
+...
+*/
+
+Element.Properties.styles = {set: function(styles){
+	this.setStyles(styles);
+}};
+
+Element.Properties.opacity = {
+
+	set: function(opacity, novisibility){
+		if (!novisibility){
+			if (opacity == 0){
+				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
+			} else {
+				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
+			}
+		}
+		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
+		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
+		this.style.opacity = opacity;
+		this.store('opacity', opacity);
+	},
+
+	get: function(){
+		return this.retrieve('opacity', 1);
+	}
+
+};
+
+Element.implement({
+
+	setOpacity: function(value){
+		return this.set('opacity', value, true);
+	},
+
+	getOpacity: function(){
+		return this.get('opacity');
+	},
+
+	setStyle: function(property, value){
+		switch (property){
+			case 'opacity': return this.set('opacity', parseFloat(value));
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		if ($type(value) != 'string'){
+			var map = (Element.Styles.get(property) || '@').split(' ');
+			value = $splat(value).map(function(val, i){
+				if (!map[i]) return '';
+				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+			}).join(' ');
+		} else if (value == String(Number(value))){
+			value = Math.round(value);
+		}
+		this.style[property] = value;
+		return this;
+	},
+
+	getStyle: function(property){
+		switch (property){
+			case 'opacity': return this.get('opacity');
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		var result = this.style[property];
+		if (!$chk(result)){
+			result = [];
+			for (var style in Element.ShortStyles){
+				if (property != style) continue;
+				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+				return result.join(' ');
+			}
+			result = this.getComputedStyle(property);
+		}
+		if (result){
+			result = String(result);
+			var color = result.match(/rgba?\([\d\s,]+\)/);
+			if (color) result = result.replace(color[0], color[0].rgbToHex());
+		}
+		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
+			if (property.test(/^(height|width)$/)){
+				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+				values.each(function(value){
+					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+				}, this);
+				return this['offset' + property.capitalize()] - size + 'px';
+			}
+			if ((Browser.Engine.presto) && String(result).test('px')) return result;
+			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
+		}
+		return result;
+	},
+
+	setStyles: function(styles){
+		for (var style in styles) this.setStyle(style, styles[style]);
+		return this;
+	},
+
+	getStyles: function(){
+		var result = {};
+		Array.flatten(arguments).each(function(key){
+			result[key] = this.getStyle(key);
+		}, this);
+		return result;
+	}
+
+});
+
+Element.Styles = new Hash({
+	left: '@px', top: '@px', bottom: '@px', right: '@px',
+	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+});
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+	var Short = Element.ShortStyles;
+	var All = Element.Styles;
+	['margin', 'padding'].each(function(style){
+		var sd = style + direction;
+		Short[style][sd] = All[sd] = '@px';
+	});
+	var bd = 'border' + direction;
+	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+	Short[bd] = {};
+	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+/*
+---
+
+script: Element.Dimensions.js
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires:
+- /Element
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+Element.implement({
+
+	scrollTo: function(x, y){
+		if (isBody(this)){
+			this.getWindow().scrollTo(x, y);
+		} else {
+			this.scrollLeft = x;
+			this.scrollTop = y;
+		}
+		return this;
+	},
+
+	getSize: function(){
+		if (isBody(this)) return this.getWindow().getSize();
+		return {x: this.offsetWidth, y: this.offsetHeight};
+	},
+
+	getScrollSize: function(){
+		if (isBody(this)) return this.getWindow().getScrollSize();
+		return {x: this.scrollWidth, y: this.scrollHeight};
+	},
+
+	getScroll: function(){
+		if (isBody(this)) return this.getWindow().getScroll();
+		return {x: this.scrollLeft, y: this.scrollTop};
+	},
+
+	getScrolls: function(){
+		var element = this, position = {x: 0, y: 0};
+		while (element && !isBody(element)){
+			position.x += element.scrollLeft;
+			position.y += element.scrollTop;
+			element = element.parentNode;
+		}
+		return position;
+	},
+
+	getOffsetParent: function(){
+		var element = this;
+		if (isBody(element)) return null;
+		if (!Browser.Engine.trident) return element.offsetParent;
+		while ((element = element.parentNode) && !isBody(element)){
+			if (styleString(element, 'position') != 'static') return element;
+		}
+		return null;
+	},
+
+	getOffsets: function(){
+		if (this.getBoundingClientRect){
+			var bound = this.getBoundingClientRect(),
+				html = document.id(this.getDocument().documentElement),
+				htmlScroll = html.getScroll(),
+				elemScrolls = this.getScrolls(),
+				elemScroll = this.getScroll(),
+				isFixed = (styleString(this, 'position') == 'fixed');
+
+			return {
+				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+			};
+		}
+
+		var element = this, position = {x: 0, y: 0};
+		if (isBody(this)) return position;
+
+		while (element && !isBody(element)){
+			position.x += element.offsetLeft;
+			position.y += element.offsetTop;
+
+			if (Browser.Engine.gecko){
+				if (!borderBox(element)){
+					position.x += leftBorder(element);
+					position.y += topBorder(element);
+				}
+				var parent = element.parentNode;
+				if (parent && styleString(parent, 'overflow') != 'visible'){
+					position.x += leftBorder(parent);
+					position.y += topBorder(parent);
+				}
+			} else if (element != this && Browser.Engine.webkit){
+				position.x += leftBorder(element);
+				position.y += topBorder(element);
+			}
+
+			element = element.offsetParent;
+		}
+		if (Browser.Engine.gecko && !borderBox(this)){
+			position.x -= leftBorder(this);
+			position.y -= topBorder(this);
+		}
+		return position;
+	},
+
+	getPosition: function(relative){
+		if (isBody(this)) return {x: 0, y: 0};
+		var offset = this.getOffsets(),
+				scroll = this.getScrolls();
+		var position = {
+			x: offset.x - scroll.x,
+			y: offset.y - scroll.y
+		};
+		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
+		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
+	},
+
+	getCoordinates: function(element){
+		if (isBody(this)) return this.getWindow().getCoordinates();
+		var position = this.getPosition(element),
+				size = this.getSize();
+		var obj = {
+			left: position.x,
+			top: position.y,
+			width: size.x,
+			height: size.y
+		};
+		obj.right = obj.left + obj.width;
+		obj.bottom = obj.top + obj.height;
+		return obj;
+	},
+
+	computePosition: function(obj){
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
+	},
+
+	setPosition: function(obj){
+		return this.setStyles(this.computePosition(obj));
+	}
+
+});
+
+
+Native.implement([Document, Window], {
+
+	getSize: function(){
+		if (Browser.Engine.presto || Browser.Engine.webkit){
+			var win = this.getWindow();
+			return {x: win.innerWidth, y: win.innerHeight};
+		}
+		var doc = getCompatElement(this);
+		return {x: doc.clientWidth, y: doc.clientHeight};
+	},
+
+	getScroll: function(){
+		var win = this.getWindow(), doc = getCompatElement(this);
+		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+	},
+
+	getScrollSize: function(){
+		var doc = getCompatElement(this), min = this.getSize();
+		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
+	},
+
+	getPosition: function(){
+		return {x: 0, y: 0};
+	},
+
+	getCoordinates: function(){
+		var size = this.getSize();
+		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+	}
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+	return styleString(element, style).toInt() || 0;
+};
+
+function borderBox(element){
+	return styleString(element, '-moz-box-sizing') == 'border-box';
+};
+
+function topBorder(element){
+	return styleNumber(element, 'border-top-width');
+};
+
+function leftBorder(element){
+	return styleNumber(element, 'border-left-width');
+};
+
+function isBody(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+};
+
+function getCompatElement(element){
+	var doc = element.getDocument();
+	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+};
+
+})();
+
+//aliases
+Element.alias('setPosition', 'position'); //compatability
+
+Native.implement([Window, Document, Element], {
+
+	getHeight: function(){
+		return this.getSize().y;
+	},
+
+	getWidth: function(){
+		return this.getSize().x;
+	},
+
+	getScrollTop: function(){
+		return this.getScroll().y;
+	},
+
+	getScrollLeft: function(){
+		return this.getScroll().x;
+	},
+
+	getScrollHeight: function(){
+		return this.getScrollSize().y;
+	},
+
+	getScrollWidth: function(){
+		return this.getScrollSize().x;
+	},
+
+	getTop: function(){
+		return this.getPosition().y;
+	},
+
+	getLeft: function(){
+		return this.getPosition().x;
+	}
+
+});
+/*
+---
+
+script: Selectors.js
+
+description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Selectors]
+
+...
+*/
+
+Native.implement([Document, Element], {
+
+	getElements: function(expression, nocash){
+		expression = expression.split(',');
+		var items, local = {};
+		for (var i = 0, l = expression.length; i < l; i++){
+			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
+			if (i != 0 && elements.item) elements = $A(elements);
+			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
+		}
+		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
+	}
+
+});
+
+Element.implement({
+
+	match: function(selector){
+		if (!selector || (selector == this)) return true;
+		var tagid = Selectors.Utils.parseTagAndID(selector);
+		var tag = tagid[0], id = tagid[1];
+		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
+		var parsed = Selectors.Utils.parseSelector(selector);
+		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
+	}
+
+});
+
+var Selectors = {Cache: {nth: {}, parsed: {}}};
+
+Selectors.RegExps = {
+	id: (/#([\w-]+)/),
+	tag: (/^(\w+|\*)/),
+	quick: (/^(\w+|\*)$/),
+	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
+	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
+};
+
+Selectors.Utils = {
+
+	chk: function(item, uniques){
+		if (!uniques) return true;
+		var uid = $uid(item);
+		if (!uniques[uid]) return uniques[uid] = true;
+		return false;
+	},
+
+	parseNthArgument: function(argument){
+		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
+		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
+		if (!parsed) return false;
+		var inta = parseInt(parsed[1], 10);
+		var a = (inta || inta === 0) ? inta : 1;
+		var special = parsed[2] || false;
+		var b = parseInt(parsed[3], 10) || 0;
+		if (a != 0){
+			b--;
+			while (b < 1) b += a;
+			while (b >= a) b -= a;
+		} else {
+			a = b;
+			special = 'index';
+		}
+		switch (special){
+			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
+			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
+			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
+			case 'first': parsed = {a: 0, special: 'index'}; break;
+			case 'last': parsed = {special: 'last-child'}; break;
+			case 'only': parsed = {special: 'only-child'}; break;
+			default: parsed = {a: (a - 1), special: 'index'};
+		}
+
+		return Selectors.Cache.nth[argument] = parsed;
+	},
+
+	parseSelector: function(selector){
+		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
+		var m, parsed = {classes: [], pseudos: [], attributes: []};
+		while ((m = Selectors.RegExps.combined.exec(selector))){
+			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
+			if (cn){
+				parsed.classes.push(cn);
+			} else if (pn){
+				var parser = Selectors.Pseudo.get(pn);
+				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
+				else parsed.attributes.push({name: pn, operator: '=', value: pa});
+			} else if (an){
+				parsed.attributes.push({name: an, operator: ao, value: av});
+			}
+		}
+		if (!parsed.classes.length) delete parsed.classes;
+		if (!parsed.attributes.length) delete parsed.attributes;
+		if (!parsed.pseudos.length) delete parsed.pseudos;
+		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
+		return Selectors.Cache.parsed[selector] = parsed;
+	},
+
+	parseTagAndID: function(selector){
+		var tag = selector.match(Selectors.RegExps.tag);
+		var id = selector.match(Selectors.RegExps.id);
+		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
+	},
+
+	filter: function(item, parsed, local){
+		var i;
+		if (parsed.classes){
+			for (i = parsed.classes.length; i--; i){
+				var cn = parsed.classes[i];
+				if (!Selectors.Filters.byClass(item, cn)) return false;
+			}
+		}
+		if (parsed.attributes){
+			for (i = parsed.attributes.length; i--; i){
+				var att = parsed.attributes[i];
+				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
+			}
+		}
+		if (parsed.pseudos){
+			for (i = parsed.pseudos.length; i--; i){
+				var psd = parsed.pseudos[i];
+				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
+			}
+		}
+		return true;
+	},
+
+	getByTagAndID: function(ctx, tag, id){
+		if (id){
+			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
+			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
+		} else {
+			return ctx.getElementsByTagName(tag);
+		}
+	},
+
+	search: function(self, expression, local){
+		var splitters = [];
+
+		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
+			splitters.push(m1);
+			return ':)' + m2;
+		}).split(':)');
+
+		var items, filtered, item;
+
+		for (var i = 0, l = selectors.length; i < l; i++){
+
+			var selector = selectors[i];
+
+			if (i == 0 && Selectors.RegExps.quick.test(selector)){
+				items = self.getElementsByTagName(selector);
+				continue;
+			}
+
+			var splitter = splitters[i - 1];
+
+			var tagid = Selectors.Utils.parseTagAndID(selector);
+			var tag = tagid[0], id = tagid[1];
+
+			if (i == 0){
+				items = Selectors.Utils.getByTagAndID(self, tag, id);
+			} else {
+				var uniques = {}, found = [];
+				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
+				items = found;
+			}
+
+			var parsed = Selectors.Utils.parseSelector(selector);
+
+			if (parsed){
+				filtered = [];
+				for (var m = 0, n = items.length; m < n; m++){
+					item = items[m];
+					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
+				}
+				items = filtered;
+			}
+
+		}
+
+		return items;
+
+	}
+
+};
+
+Selectors.Getters = {
+
+	' ': function(found, self, tag, id, uniques){
+		var items = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = items.length; i < l; i++){
+			var item = items[i];
+			if (Selectors.Utils.chk(item, uniques)) found.push(item);
+		}
+		return found;
+	},
+
+	'>': function(found, self, tag, id, uniques){
+		var children = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = children.length; i < l; i++){
+			var child = children[i];
+			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
+		}
+		return found;
+	},
+
+	'+': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+				break;
+			}
+		}
+		return found;
+	},
+
+	'~': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (!Selectors.Utils.chk(self, uniques)) break;
+				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+			}
+		}
+		return found;
+	}
+
+};
+
+Selectors.Filters = {
+
+	byTag: function(self, tag){
+		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
+	},
+
+	byID: function(self, id){
+		return (!id || (self.id && self.id == id));
+	},
+
+	byClass: function(self, klass){
+		return (self.className && self.className.contains && self.className.contains(klass, ' '));
+	},
+
+	byPseudo: function(self, parser, argument, local){
+		return parser.call(self, argument, local);
+	},
+
+	byAttribute: function(self, name, operator, value){
+		var result = Element.prototype.getProperty.call(self, name);
+		if (!result) return (operator == '!=');
+		if (!operator || value == undefined) return true;
+		switch (operator){
+			case '=': return (result == value);
+			case '*=': return (result.contains(value));
+			case '^=': return (result.substr(0, value.length) == value);
+			case '$=': return (result.substr(result.length - value.length) == value);
+			case '!=': return (result != value);
+			case '~=': return result.contains(value, ' ');
+			case '|=': return result.contains(value, '-');
+		}
+		return false;
+	}
+
+};
+
+Selectors.Pseudo = new Hash({
+
+	// w3c pseudo selectors
+
+	checked: function(){
+		return this.checked;
+	},
+	
+	empty: function(){
+		return !(this.innerText || this.textContent || '').length;
+	},
+
+	not: function(selector){
+		return !Element.match(this, selector);
+	},
+
+	contains: function(text){
+		return (this.innerText || this.textContent || '').contains(text);
+	},
+
+	'first-child': function(){
+		return Selectors.Pseudo.index.call(this, 0);
+	},
+
+	'last-child': function(){
+		var element = this;
+		while ((element = element.nextSibling)){
+			if (element.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'only-child': function(){
+		var prev = this;
+		while ((prev = prev.previousSibling)){
+			if (prev.nodeType == 1) return false;
+		}
+		var next = this;
+		while ((next = next.nextSibling)){
+			if (next.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'nth-child': function(argument, local){
+		argument = (argument == undefined) ? 'n' : argument;
+		var parsed = Selectors.Utils.parseNthArgument(argument);
+		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
+		var count = 0;
+		local.positions = local.positions || {};
+		var uid = $uid(this);
+		if (!local.positions[uid]){
+			var self = this;
+			while ((self = self.previousSibling)){
+				if (self.nodeType != 1) continue;
+				count ++;
+				var position = local.positions[$uid(self)];
+				if (position != undefined){
+					count = position + count;
+					break;
+				}
+			}
+			local.positions[uid] = count;
+		}
+		return (local.positions[uid] % parsed.a == parsed.b);
+	},
+
+	// custom pseudo selectors
+
+	index: function(index){
+		var element = this, count = 0;
+		while ((element = element.previousSibling)){
+			if (element.nodeType == 1 && ++count > index) return false;
+		}
+		return (count == index);
+	},
+
+	even: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
+	},
+
+	odd: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
+	},
+	
+	selected: function(){
+		return this.selected;
+	},
+	
+	enabled: function(){
+		return (this.disabled === false);
+	}
+
+});
+/*
+---
+
+script: DomReady.js
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires:
+- /Element.Event
+
+provides: [DomReady]
+
+...
+*/
+
+Element.Events.domready = {
+
+	onAdd: function(fn){
+		if (Browser.loaded) fn.call(this);
+	}
+
+};
+
+(function(){
+
+	var domready = function(){
+		if (Browser.loaded) return;
+		Browser.loaded = true;
+		window.fireEvent('domready');
+		document.fireEvent('domready');
+	};
+	
+	window.addEvent('load', domready);
+
+	if (Browser.Engine.trident){
+		var temp = document.createElement('div');
+		(function(){
+			($try(function(){
+				temp.doScroll(); // Technique by Diego Perini
+				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
+			})) ? domready() : arguments.callee.delay(50);
+		})();
+	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
+		(function(){
+			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
+		})();
+	} else {
+		document.addEvent('DOMContentLoaded', domready);
+	}
+
+})();
+/*
+---
+
+script: JSON.js
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+See Also: <http://www.json.org/>
+
+requires:
+- /Array
+- /String
+- /Number
+- /Function
+- /Hash
+
+provides: [JSON]
+
+...
+*/
+
+var JSON = new Hash(this.JSON && {
+	stringify: JSON.stringify,
+	parse: JSON.parse
+}).extend({
+	
+	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},
+
+	$replaceChars: function(chr){
+		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
+	},
+
+	encode: function(obj){
+		switch ($type(obj)){
+			case 'string':
+				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
+			case 'array':
+				return '[' + String(obj.map(JSON.encode).clean()) + ']';
+			case 'object': case 'hash':
+				var string = [];
+				Hash.each(obj, function(value, key){
+					var json = JSON.encode(value);
+					if (json) string.push(JSON.encode(key) + ':' + json);
+				});
+				return '{' + string + '}';
+			case 'number': case 'boolean': return String(obj);
+			case false: return 'null';
+		}
+		return null;
+	},
+
+	decode: function(string, secure){
+		if ($type(string) != 'string' || !string.length) return null;
+		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
+		return eval('(' + string + ')');
+	}
+
+});
+
+Native.implement([Hash, Array, String, Number], {
+
+	toJSON: function(){
+		return JSON.encode(this);
+	}
+
+});
+/*
+---
+
+script: Cookie.js
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+- Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires:
+- /Options
+
+provides: [Cookie]
+
+...
+*/
+
+var Cookie = new Class({
+
+	Implements: Options,
+
+	options: {
+		path: false,
+		domain: false,
+		duration: false,
+		secure: false,
+		document: document
+	},
+
+	initialize: function(key, options){
+		this.key = key;
+		this.setOptions(options);
+	},
+
+	write: function(value){
+		value = encodeURIComponent(value);
+		if (this.options.domain) value += '; domain=' + this.options.domain;
+		if (this.options.path) value += '; path=' + this.options.path;
+		if (this.options.duration){
+			var date = new Date();
+			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+			value += '; expires=' + date.toGMTString();
+		}
+		if (this.options.secure) value += '; secure';
+		this.options.document.cookie = this.key + '=' + value;
+		return this;
+	},
+
+	read: function(){
+		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+		return (value) ? decodeURIComponent(value[1]) : null;
+	},
+
+	dispose: function(){
+		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
+		return this;
+	}
+
+});
+
+Cookie.write = function(key, value, options){
+	return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+	return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+	return new Cookie(key, options).dispose();
+};
+/*
+---
+
+script: Swiff.js
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits: 
+- Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires:
+- /Options
+- /$util
+
+provides: [Swiff]
+
+...
+*/
+
+var Swiff = new Class({
+
+	Implements: [Options],
+
+	options: {
+		id: null,
+		height: 1,
+		width: 1,
+		container: null,
+		properties: {},
+		params: {
+			quality: 'high',
+			allowScriptAccess: 'always',
+			wMode: 'transparent',
+			swLiveConnect: true
+		},
+		callBacks: {},
+		vars: {}
+	},
+
+	toElement: function(){
+		return this.object;
+	},
+
+	initialize: function(path, options){
+		this.instance = 'Swiff_' + $time();
+
+		this.setOptions(options);
+		options = this.options;
+		var id = this.id = options.id || this.instance;
+		var container = document.id(options.container);
+
+		Swiff.CallBacks[this.instance] = {};
+
+		var params = options.params, vars = options.vars, callBacks = options.callBacks;
+		var properties = $extend({height: options.height, width: options.width}, options.properties);
+
+		var self = this;
+
+		for (var callBack in callBacks){
+			Swiff.CallBacks[this.instance][callBack] = (function(option){
+				return function(){
+					return option.apply(self.object, arguments);
+				};
+			})(callBacks[callBack]);
+			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+		}
+
+		params.flashVars = Hash.toQueryString(vars);
+		if (Browser.Engine.trident){
+			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			params.movie = path;
+		} else {
+			properties.type = 'application/x-shockwave-flash';
+			properties.data = path;
+		}
+		var build = '<object id="' + id + '"';
+		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+		build += '>';
+		for (var param in params){
+			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+		}
+		build += '</object>';
+		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+	},
+
+	replaces: function(element){
+		element = document.id(element, true);
+		element.parentNode.replaceChild(this.toElement(), element);
+		return this;
+	},
+
+	inject: function(element){
+		document.id(element, true).appendChild(this.toElement());
+		return this;
+	},
+
+	remote: function(){
+		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
+	}
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+	return eval(rs);
+};
+/*
+---
+
+script: Fx.js
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires:
+- /Chain
+- /Events
+- /Options
+
+provides: [Fx]
+
+...
+*/
+
+var Fx = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {
+		/*
+		onStart: $empty,
+		onCancel: $empty,
+		onComplete: $empty,
+		*/
+		fps: 50,
+		unit: false,
+		duration: 500,
+		link: 'ignore'
+	},
+
+	initialize: function(options){
+		this.subject = this.subject || this;
+		this.setOptions(options);
+		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
+		var wait = this.options.wait;
+		if (wait === false) this.options.link = 'cancel';
+	},
+
+	getTransition: function(){
+		return function(p){
+			return -(Math.cos(Math.PI * p) - 1) / 2;
+		};
+	},
+
+	step: function(){
+		var time = $time();
+		if (time < this.time + this.options.duration){
+			var delta = this.transition((time - this.time) / this.options.duration);
+			this.set(this.compute(this.from, this.to, delta));
+		} else {
+			this.set(this.compute(this.from, this.to, 1));
+			this.complete();
+		}
+	},
+
+	set: function(now){
+		return now;
+	},
+
+	compute: function(from, to, delta){
+		return Fx.compute(from, to, delta);
+	},
+
+	check: function(){
+		if (!this.timer) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	start: function(from, to){
+		if (!this.check(from, to)) return this;
+		this.from = from;
+		this.to = to;
+		this.time = 0;
+		this.transition = this.getTransition();
+		this.startTimer();
+		this.onStart();
+		return this;
+	},
+
+	complete: function(){
+		if (this.stopTimer()) this.onComplete();
+		return this;
+	},
+
+	cancel: function(){
+		if (this.stopTimer()) this.onCancel();
+		return this;
+	},
+
+	onStart: function(){
+		this.fireEvent('start', this.subject);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', this.subject);
+		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+	},
+
+	onCancel: function(){
+		this.fireEvent('cancel', this.subject).clearChain();
+	},
+
+	pause: function(){
+		this.stopTimer();
+		return this;
+	},
+
+	resume: function(){
+		this.startTimer();
+		return this;
+	},
+
+	stopTimer: function(){
+		if (!this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = $clear(this.timer);
+		return true;
+	},
+
+	startTimer: function(){
+		if (this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
+		return true;
+	}
+
+});
+
+Fx.compute = function(from, to, delta){
+	return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+/*
+---
+
+script: Fx.CSS.js
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires:
+- /Fx
+- /Element.Style
+
+provides: [Fx.CSS]
+
+...
+*/
+
+Fx.CSS = new Class({
+
+	Extends: Fx,
+
+	//prepares the base from/to object
+
+	prepare: function(element, property, values){
+		values = $splat(values);
+		var values1 = values[1];
+		if (!$chk(values1)){
+			values[1] = values[0];
+			values[0] = element.getStyle(property);
+		}
+		var parsed = values.map(this.parse);
+		return {from: parsed[0], to: parsed[1]};
+	},
+
+	//parses a value into an array
+
+	parse: function(value){
+		value = $lambda(value)();
+		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
+		return value.map(function(val){
+			val = String(val);
+			var found = false;
+			Fx.CSS.Parsers.each(function(parser, key){
+				if (found) return;
+				var parsed = parser.parse(val);
+				if ($chk(parsed)) found = {value: parsed, parser: parser};
+			});
+			found = found || {value: val, parser: Fx.CSS.Parsers.String};
+			return found;
+		});
+	},
+
+	//computes by a from and to prepared objects, using their parsers.
+
+	compute: function(from, to, delta){
+		var computed = [];
+		(Math.min(from.length, to.length)).times(function(i){
+			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+		});
+		computed.$family = {name: 'fx:css:value'};
+		return computed;
+	},
+
+	//serves the value as settable
+
+	serve: function(value, unit){
+		if ($type(value) != 'fx:css:value') value = this.parse(value);
+		var returned = [];
+		value.each(function(bit){
+			returned = returned.concat(bit.parser.serve(bit.value, unit));
+		});
+		return returned;
+	},
+
+	//renders the change to an element
+
+	render: function(element, property, value, unit){
+		element.setStyle(property, this.serve(value, unit));
+	},
+
+	//searches inside the page css to find the values for a selector
+
+	search: function(selector){
+		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+		var to = {};
+		Array.each(document.styleSheets, function(sheet, j){
+			var href = sheet.href;
+			if (href && href.contains('://') && !href.contains(document.domain)) return;
+			var rules = sheet.rules || sheet.cssRules;
+			Array.each(rules, function(rule, i){
+				if (!rule.style) return;
+				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+					return m.toLowerCase();
+				}) : null;
+				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
+				Element.Styles.each(function(value, style){
+					if (!rule.style[style] || Element.ShortStyles[style]) return;
+					value = String(rule.style[style]);
+					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
+				});
+			});
+		});
+		return Fx.CSS.Cache[selector] = to;
+	}
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = new Hash({
+
+	Color: {
+		parse: function(value){
+			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+		},
+		compute: function(from, to, delta){
+			return from.map(function(value, i){
+				return Math.round(Fx.compute(from[i], to[i], delta));
+			});
+		},
+		serve: function(value){
+			return value.map(Number);
+		}
+	},
+
+	Number: {
+		parse: parseFloat,
+		compute: Fx.compute,
+		serve: function(value, unit){
+			return (unit) ? value + unit : value;
+		}
+	},
+
+	String: {
+		parse: $lambda(false),
+		compute: $arguments(1),
+		serve: $arguments(0)
+	}
+
+});
+/*
+---
+
+script: Fx.Tween.js
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: 
+- /Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(property, now){
+		if (arguments.length == 1){
+			now = property;
+			property = this.property || this.options.property;
+		}
+		this.render(this.element, property, now, this.options.unit);
+		return this;
+	},
+
+	start: function(property, from, to){
+		if (!this.check(property, from, to)) return this;
+		var args = Array.flatten(arguments);
+		this.property = this.options.property || args.shift();
+		var parsed = this.prepare(this.element, this.property, args);
+		return this.parent(parsed.from, parsed.to);
+	}
+
+});
+
+Element.Properties.tween = {
+
+	set: function(options){
+		var tween = this.retrieve('tween');
+		if (tween) tween.cancel();
+		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('tween')){
+			if (options || !this.retrieve('tween:options')) this.set('tween', options);
+			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
+		}
+		return this.retrieve('tween');
+	}
+
+};
+
+Element.implement({
+
+	tween: function(property, from, to){
+		this.get('tween').start(arguments);
+		return this;
+	},
+
+	fade: function(how){
+		var fade = this.get('tween'), o = 'opacity', toggle;
+		how = $pick(how, 'toggle');
+		switch (how){
+			case 'in': fade.start(o, 1); break;
+			case 'out': fade.start(o, 0); break;
+			case 'show': fade.set(o, 1); break;
+			case 'hide': fade.set(o, 0); break;
+			case 'toggle':
+				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
+				fade.start(o, (flag) ? 0 : 1);
+				this.store('fade:flag', !flag);
+				toggle = true;
+			break;
+			default: fade.start(o, arguments);
+		}
+		if (!toggle) this.eliminate('fade:flag');
+		return this;
+	},
+
+	highlight: function(start, end){
+		if (!end){
+			end = this.retrieve('highlight:original', this.getStyle('background-color'));
+			end = (end == 'transparent') ? '#fff' : end;
+		}
+		var tween = this.get('tween');
+		tween.start('background-color', start || '#ffff88', end).chain(function(){
+			this.setStyle('background-color', this.retrieve('highlight:original'));
+			tween.callChain();
+		}.bind(this));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Morph.js
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires:
+- /Fx.CSS
+
+provides: [Fx.Morph]
+
+...
+*/
+
+Fx.Morph = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(now){
+		if (typeof now == 'string') now = this.search(now);
+		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+		return now;
+	},
+
+	start: function(properties){
+		if (!this.check(properties)) return this;
+		if (typeof properties == 'string') properties = this.search(properties);
+		var from = {}, to = {};
+		for (var p in properties){
+			var parsed = this.prepare(this.element, p, properties[p]);
+			from[p] = parsed.from;
+			to[p] = parsed.to;
+		}
+		return this.parent(from, to);
+	}
+
+});
+
+Element.Properties.morph = {
+
+	set: function(options){
+		var morph = this.retrieve('morph');
+		if (morph) morph.cancel();
+		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('morph')){
+			if (options || !this.retrieve('morph:options')) this.set('morph', options);
+			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
+		}
+		return this.retrieve('morph');
+	}
+
+};
+
+Element.implement({
+
+	morph: function(props){
+		this.get('morph').start(props);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Transitions.js
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires:
+- /Fx
+
+provides: [Fx.Transitions]
+
+...
+*/
+
+Fx.implement({
+
+	getTransition: function(){
+		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+		if (typeof trans == 'string'){
+			var data = trans.split(':');
+			trans = Fx.Transitions;
+			trans = trans[data[0]] || trans[data[0].capitalize()];
+			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+		}
+		return trans;
+	}
+
+});
+
+Fx.Transition = function(transition, params){
+	params = $splat(params);
+	return $extend(transition, {
+		easeIn: function(pos){
+			return transition(pos, params);
+		},
+		easeOut: function(pos){
+			return 1 - transition(1 - pos, params);
+		},
+		easeInOut: function(pos){
+			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
+		}
+	});
+};
+
+Fx.Transitions = new Hash({
+
+	linear: $arguments(0)
+
+});
+
+Fx.Transitions.extend = function(transitions){
+	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+	Pow: function(p, x){
+		return Math.pow(p, x[0] || 6);
+	},
+
+	Expo: function(p){
+		return Math.pow(2, 8 * (p - 1));
+	},
+
+	Circ: function(p){
+		return 1 - Math.sin(Math.acos(p));
+	},
+
+	Sine: function(p){
+		return 1 - Math.sin((1 - p) * Math.PI / 2);
+	},
+
+	Back: function(p, x){
+		x = x[0] || 1.618;
+		return Math.pow(p, 2) * ((x + 1) * p - x);
+	},
+
+	Bounce: function(p){
+		var value;
+		for (var a = 0, b = 1; 1; a += b, b /= 2){
+			if (p >= (7 - 4 * a) / 11){
+				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+				break;
+			}
+		}
+		return value;
+	},
+
+	Elastic: function(p, x){
+		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+	}
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+	Fx.Transitions[transition] = new Fx.Transition(function(p){
+		return Math.pow(p, [i + 2]);
+	});
+});
+/*
+---
+
+script: Request.js
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires:
+- /Element
+- /Chain
+- /Events
+- /Options
+- /Browser
+
+provides: [Request]
+
+...
+*/
+
+var Request = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {/*
+		onRequest: $empty,
+		onComplete: $empty,
+		onCancel: $empty,
+		onSuccess: $empty,
+		onFailure: $empty,
+		onException: $empty,*/
+		url: '',
+		data: '',
+		headers: {
+			'X-Requested-With': 'XMLHttpRequest',
+			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+		},
+		async: true,
+		format: false,
+		method: 'post',
+		link: 'ignore',
+		isSuccess: null,
+		emulation: true,
+		urlEncoded: true,
+		encoding: 'utf-8',
+		evalScripts: false,
+		evalResponse: false,
+		noCache: false
+	},
+
+	initialize: function(options){
+		this.xhr = new Browser.Request();
+		this.setOptions(options);
+		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+		this.headers = new Hash(this.options.headers);
+	},
+
+	onStateChange: function(){
+		if (this.xhr.readyState != 4 || !this.running) return;
+		this.running = false;
+		this.status = 0;
+		$try(function(){
+			this.status = this.xhr.status;
+		}.bind(this));
+		this.xhr.onreadystatechange = $empty;
+		if (this.options.isSuccess.call(this, this.status)){
+			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
+			this.success(this.response.text, this.response.xml);
+		} else {
+			this.response = {text: null, xml: null};
+			this.failure();
+		}
+	},
+
+	isSuccess: function(){
+		return ((this.status >= 200) && (this.status < 300));
+	},
+
+	processScripts: function(text){
+		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
+		return text.stripScripts(this.options.evalScripts);
+	},
+
+	success: function(text, xml){
+		this.onSuccess(this.processScripts(text), xml);
+	},
+
+	onSuccess: function(){
+		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+	},
+
+	failure: function(){
+		this.onFailure();
+	},
+
+	onFailure: function(){
+		this.fireEvent('complete').fireEvent('failure', this.xhr);
+	},
+
+	setHeader: function(name, value){
+		this.headers.set(name, value);
+		return this;
+	},
+
+	getHeader: function(name){
+		return $try(function(){
+			return this.xhr.getResponseHeader(name);
+		}.bind(this));
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!this.check(options)) return this;
+		this.running = true;
+
+		var type = $type(options);
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		var old = this.options;
+		options = $extend({data: old.data, url: old.url, method: old.method}, options);
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+		switch ($type(data)){
+			case 'element': data = document.id(data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(data);
+		}
+
+		if (this.options.format){
+			var format = 'format=' + this.options.format;
+			data = (data) ? format + '&' + data : format;
+		}
+
+		if (this.options.emulation && !['get', 'post'].contains(method)){
+			var _method = '_method=' + method;
+			data = (data) ? _method + '&' + data : _method;
+			method = 'post';
+		}
+
+		if (this.options.urlEncoded && method == 'post'){
+			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
+		}
+
+		if (this.options.noCache){
+			var noCache = 'noCache=' + new Date().getTime();
+			data = (data) ? noCache + '&' + data : noCache;
+		}
+
+		var trimPosition = url.lastIndexOf('/');
+		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+		if (data && method == 'get'){
+			url = url + (url.contains('?') ? '&' : '?') + data;
+			data = null;
+		}
+
+		this.xhr.open(method.toUpperCase(), url, this.options.async);
+
+		this.xhr.onreadystatechange = this.onStateChange.bind(this);
+
+		this.headers.each(function(value, key){
+			try {
+				this.xhr.setRequestHeader(key, value);
+			} catch (e){
+				this.fireEvent('exception', [key, value]);
+			}
+		}, this);
+
+		this.fireEvent('request');
+		this.xhr.send(data);
+		if (!this.options.async) this.onStateChange();
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.xhr.abort();
+		this.xhr.onreadystatechange = $empty;
+		this.xhr = new Browser.Request();
+		this.fireEvent('cancel');
+		return this;
+	}
+
+});
+
+(function(){
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+	methods[method] = function(){
+		var params = Array.link(arguments, {url: String.type, data: $defined});
+		return this.send($extend(params, {method: method}));
+	};
+});
+
+Request.implement(methods);
+
+})();
+
+Element.Properties.send = {
+
+	set: function(options){
+		var send = this.retrieve('send');
+		if (send) send.cancel();
+		return this.eliminate('send').store('send:options', $extend({
+			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+		}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('send')){
+			if (options || !this.retrieve('send:options')) this.set('send', options);
+			this.store('send', new Request(this.retrieve('send:options')));
+		}
+		return this.retrieve('send');
+	}
+
+};
+
+Element.implement({
+
+	send: function(url){
+		var sender = this.get('send');
+		sender.send({data: this, url: url || sender.options.url});
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.HTML.js
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires:
+- /Request
+- /Element
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.HTML = new Class({
+
+	Extends: Request,
+
+	options: {
+		update: false,
+		append: false,
+		evalScripts: true,
+		filter: false
+	},
+
+	processHTML: function(text){
+		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+		text = (match) ? match[1] : text;
+
+		var container = new Element('div');
+
+		return $try(function(){
+			var root = '<root>' + text + '</root>', doc;
+			if (Browser.Engine.trident){
+				doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = false;
+				doc.loadXML(root);
+			} else {
+				doc = new DOMParser().parseFromString(root, 'text/xml');
+			}
+			root = doc.getElementsByTagName('root')[0];
+			if (!root) return null;
+			for (var i = 0, k = root.childNodes.length; i < k; i++){
+				var child = Element.clone(root.childNodes[i], true, true);
+				if (child) container.grab(child);
+			}
+			return container;
+		}) || container.set('html', text);
+	},
+
+	success: function(text){
+		var options = this.options, response = this.response;
+
+		response.html = text.stripScripts(function(script){
+			response.javascript = script;
+		});
+
+		var temp = this.processHTML(response.html);
+
+		response.tree = temp.childNodes;
+		response.elements = temp.getElements('*');
+
+		if (options.filter) response.tree = response.elements.filter(options.filter);
+		if (options.update) document.id(options.update).empty().set('html', response.html);
+		else if (options.append) document.id(options.append).adopt(temp.getChildren());
+		if (options.evalScripts) $exec(response.javascript);
+
+		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+	}
+
+});
+
+Element.Properties.load = {
+
+	set: function(options){
+		var load = this.retrieve('load');
+		if (load) load.cancel();
+		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
+	},
+
+	get: function(options){
+		if (options || ! this.retrieve('load')){
+			if (options || !this.retrieve('load:options')) this.set('load', options);
+			this.store('load', new Request.HTML(this.retrieve('load:options')));
+		}
+		return this.retrieve('load');
+	}
+
+};
+
+Element.implement({
+
+	load: function(){
+		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.JSON.js
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires:
+- /Request JSON
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.JSON = new Class({
+
+	Extends: Request,
+
+	options: {
+		secure: true
+	},
+
+	initialize: function(options){
+		this.parent(options);
+		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
+	},
+
+	success: function(text){
+		this.response.json = JSON.decode(text, this.options.secure);
+		this.onSuccess(this.response.json, text);
+	}
+
+});
+/*
+---
+
+script: More.js
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+	'version': '1.2.4.4',
+	'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
+};/*
+---
+
+script: MooTools.Lang.js
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Events
+ - /MooTools.More
+
+provides: [MooTools.Lang]
+
+...
+*/
+
+(function(){
+
+	var data = {
+		language: 'en-US',
+		languages: {
+			'en-US': {}
+		},
+		cascades: ['en-US']
+	};
+	
+	var cascaded;
+
+	MooTools.lang = new Events();
+
+	$extend(MooTools.lang, {
+
+		setLanguage: function(lang){
+			if (!data.languages[lang]) return this;
+			data.language = lang;
+			this.load();
+			this.fireEvent('langChange', lang);
+			return this;
+		},
+
+		load: function() {
+			var langs = this.cascade(this.getCurrentLanguage());
+			cascaded = {};
+			$each(langs, function(set, setName){
+				cascaded[setName] = this.lambda(set);
+			}, this);
+		},
+
+		getCurrentLanguage: function(){
+			return data.language;
+		},
+
+		addLanguage: function(lang){
+			data.languages[lang] = data.languages[lang] || {};
+			return this;
+		},
+
+		cascade: function(lang){
+			var cascades = (data.languages[lang] || {}).cascades || [];
+			cascades.combine(data.cascades);
+			cascades.erase(lang).push(lang);
+			var langs = cascades.map(function(lng){
+				return data.languages[lng];
+			}, this);
+			return $merge.apply(this, langs);
+		},
+
+		lambda: function(set) {
+			(set || {}).get = function(key, args){
+				return $lambda(set[key]).apply(this, $splat(args));
+			};
+			return set;
+		},
+
+		get: function(set, key, args){
+			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
+		},
+
+		set: function(lang, set, members){
+			this.addLanguage(lang);
+			langData = data.languages[lang];
+			if (!langData[set]) langData[set] = {};
+			$extend(langData[set], members);
+			if (lang == this.getCurrentLanguage()){
+				this.load();
+				this.fireEvent('langChange', lang);
+			}
+			return this;
+		},
+
+		list: function(){
+			return Hash.getKeys(data.languages);
+		}
+
+	});
+
+})();/*
+---
+
+script: Log.js
+
+description: Provides basic logging functionality for plugins to implement.
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Log]
+
+...
+*/
+
+(function(){
+
+var global = this;
+
+var log = function(){
+	if (global.console && console.log){
+		try {
+			console.log.apply(console, arguments);
+		} catch(e) {
+			console.log(Array.slice(arguments));
+		}
+	} else {
+		Log.logged.push(arguments);
+	}
+	return this;
+};
+
+var disabled = function(){
+	this.logged.push(arguments);
+	return this;
+};
+
+this.Log = new Class({
+	
+	logged: [],
+	
+	log: disabled,
+	
+	resetLog: function(){
+		this.logged.empty();
+		return this;
+	},
+
+	enableLog: function(){
+		this.log = log;
+		this.logged.each(function(args){
+			this.log.apply(this, args);
+		}, this);
+		return this.resetLog();
+	},
+
+	disableLog: function(){
+		this.log = disabled;
+		return this;
+	}
+	
+});
+
+Log.extend(new Log).enableLog();
+
+// legacy
+Log.logger = function(){
+	return this.log.apply(this, arguments);
+};
+
+})();/*
+---
+
+script: Depender.js
+
+description: A stand alone dependency loader for the MooTools library.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Events
+ - core:1.2.4/Request.JSON
+ - /MooTools.More
+ - /Log
+
+provides: Depender
+
+...
+*/
+
+var Depender = {
+
+	options: {
+		/* 
+		onRequire: $empty(options),
+		onRequirementLoaded: $empty([scripts, options]),
+		onScriptLoaded: $empty({
+			script: script, 
+			totalLoaded: percentOfTotalLoaded, 
+			loaded: scriptsState
+		}),
+		serial: false,
+		target: null,
+		noCache: false,
+		log: false,*/
+		loadedSources: [],
+		loadedScripts: ['Core', 'Browser', 'Array', 'String', 'Function', 'Number', 'Hash', 'Element', 'Event', 'Element.Event', 'Class', 'DomReady', 'Class.Extras', 'Request', 'JSON', 'Request.JSON', 'More', 'Depender', 'Log'],
+		useScriptInjection: true
+	},
+
+	loaded: [],
+
+	sources: {},
+
+	libs: {},
+
+	include: function(libs){
+		this.log('include: ', libs);
+		this.mapLoaded = false;
+		var loader = function(data){
+			this.libs = $merge(this.libs, data);
+			$each(this.libs, function(data, lib){
+				if (data.scripts) this.loadSource(lib, data.scripts);
+			}, this);
+		}.bind(this);
+		if ($type(libs) == 'string'){
+			this.log('fetching libs ', libs);
+			this.request(libs, loader);
+		} else {
+			loader(libs);
+		}
+		return this;
+	},
+
+	required: [],
+
+	require: function(options){
+		var loaded = function(){
+			var scripts = this.calculateDependencies(options.scripts);
+			if (options.sources){
+				options.sources.each(function(source){
+					scripts.combine(this.libs[source].files);
+				}, this);
+			}
+			if (options.serial) scripts.combine(this.getLoadedScripts());
+			options.scripts = scripts;
+			this.required.push(options);
+			this.fireEvent('require', options);
+			this.loadScripts(options.scripts);
+		};
+		if (this.mapLoaded) loaded.call(this);
+		else this.addEvent('mapLoaded', loaded.bind(this));
+		return this;
+	},
+
+	cleanDoubleSlash: function(str){
+		if (!str) return str;
+		var prefix = '';
+		if (str.test(/^http:\/\//)){
+			prefix = 'http://';
+			str = str.substring(7, str.length);
+		}
+		str = str.replace(/\/\//g, '/');
+		return prefix + str;
+	},
+
+	request: function(url, callback){
+		new Request.JSON({
+			url: url,
+			secure: false,
+			onSuccess: callback
+		}).send();
+	},
+
+	loadSource: function(lib, source){
+		if (this.libs[lib].files){
+			this.dataLoaded();
+			return;
+		}
+		this.log('loading source: ', source);
+		this.request(this.cleanDoubleSlash(source + '/scripts.json'), function(result){
+			this.log('loaded source: ', source);
+			this.libs[lib].files = result;
+			this.dataLoaded();
+		}.bind(this));
+	},
+
+	dataLoaded: function(){
+		var loaded = true;
+		$each(this.libs, function(v, k){
+			if (!this.libs[k].files) loaded = false;
+		}, this);
+		if (loaded){
+			this.mapTree();
+			this.mapLoaded = true;
+			this.calculateLoaded();
+			this.lastLoaded = this.getLoadedScripts().getLength();
+			this.fireEvent('mapLoaded');
+			this.removeEvents('mapLoaded');
+		}
+	},
+
+	calculateLoaded: function(){
+		var set = function(script){
+			this.scriptsState[script] = true;
+		}.bind(this);
+		if (this.options.loadedScripts) this.options.loadedScripts.each(set);
+		if (this.options.loadedSources){
+			this.options.loadedSources.each(function(lib){
+				$each(this.libs[lib].files, function(dir){
+					$each(dir, function(data, file){
+						set(file);
+					}, this);
+				}, this);
+			}, this);
+		}
+	},
+
+	deps: {},
+
+	pathMap: {},
+
+	mapTree: function(){
+		$each(this.libs, function(data, source){
+			$each(data.files, function(scripts, folder){
+				$each(scripts, function(details, script){
+					var path = source + ':' + folder + ':' + script;
+					if (this.deps[path]) return;
+					this.deps[path] = details.deps;
+					this.pathMap[script] = path;
+				}, this);
+			}, this);
+		}, this);
+	},
+
+	getDepsForScript: function(script){
+		return this.deps[this.pathMap[script]] || [];
+	},
+
+	calculateDependencies: function(scripts){
+		var reqs = [];
+		$splat(scripts).each(function(script){
+			if (script == 'None' || !script) return;
+			var deps = this.getDepsForScript(script);
+			if (!deps){
+				if (window.console && console.warn) console.warn('dependencies not mapped: script: %o, map: %o, :deps: %o', script, this.pathMap, this.deps);
+			} else {
+				deps.each(function(scr){
+					if (scr == script || scr == 'None' || !scr) return;
+					if (!reqs.contains(scr)) reqs.combine(this.calculateDependencies(scr));
+					reqs.include(scr);
+				}, this);
+			}
+			reqs.include(script);
+		}, this);
+		return reqs;
+	},
+
+	getPath: function(script){
+		try {
+			var chunks = this.pathMap[script].split(':');
+			var lib = this.libs[chunks[0]];
+			var dir = (lib.path || lib.scripts) + '/';
+			chunks.shift();
+			return this.cleanDoubleSlash(dir + chunks.join('/') + '.js');
+		} catch(e){
+			return script;
+		}
+	},
+
+	loadScripts: function(scripts){
+		scripts = scripts.filter(function(s){
+			if (!this.scriptsState[s] && s != 'None'){
+				this.scriptsState[s] = false;
+				return true;
+			}
+		}, this);
+		if (scripts.length){
+			scripts.each(function(scr){
+				this.loadScript(scr);
+			}, this);
+		} else {
+			this.check();
+		}
+	},
+
+	toLoad: [],
+
+	loadScript: function(script){
+		if (this.scriptsState[script] && this.toLoad.length){
+			this.loadScript(this.toLoad.shift());
+			return;
+		} else if (this.loading){
+			this.toLoad.push(script);
+			return;
+		}
+		var finish = function(){
+			this.loading = false;
+			this.scriptLoaded(script);
+			if (this.toLoad.length) this.loadScript(this.toLoad.shift());
+		}.bind(this);
+		var error = function(){
+			this.log('could not load: ', scriptPath);
+		}.bind(this);
+		this.loading = true;
+		var scriptPath = this.getPath(script);
+		if (this.options.useScriptInjection){
+			this.log('injecting script: ', scriptPath);
+			var loaded = function(){
+				this.log('loaded script: ', scriptPath);
+				finish();
+			}.bind(this);
+			new Element('script', {
+				src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
+				events: {
+					load: loaded,
+					readystatechange: function(){
+						if (['loaded', 'complete'].contains(this.readyState)) loaded();
+					},
+					error: error
+				}
+			}).inject(this.options.target || document.head);
+		} else {
+			this.log('requesting script: ', scriptPath);
+			new Request({
+				url: scriptPath,
+				noCache: this.options.noCache,
+				onComplete: function(js){
+					this.log('loaded script: ', scriptPath);
+					$exec(js);
+					finish();
+				}.bind(this),
+				onFailure: error,
+				onException: error
+			}).send();
+		}
+	},
+
+	scriptsState: $H(),
+	
+	getLoadedScripts: function(){
+		return this.scriptsState.filter(function(state){
+			return state;
+		});
+	},
+
+	scriptLoaded: function(script){
+		this.log('loaded script: ', script);
+		this.scriptsState[script] = true;
+		this.check();
+		var loaded = this.getLoadedScripts();
+		var loadedLength = loaded.getLength();
+		var toLoad = this.scriptsState.getLength();
+		this.fireEvent('scriptLoaded', {
+			script: script,
+			totalLoaded: (loadedLength / toLoad * 100).round(),
+			currentLoaded: ((loadedLength - this.lastLoaded) / (toLoad - this.lastLoaded) * 100).round(),
+			loaded: loaded
+		});
+		if (loadedLength == toLoad) this.lastLoaded = loadedLength;
+	},
+
+	lastLoaded: 0,
+
+	check: function(){
+		var incomplete = [];
+		this.required.each(function(required){
+			var loaded = [];
+			required.scripts.each(function(script){
+				if (this.scriptsState[script]) loaded.push(script);
+			}, this);
+			if (required.onStep){
+				required.onStep({
+					percent: loaded.length / required.scripts.length * 100,
+					scripts: loaded
+				});
+			};
+			if (required.scripts.length != loaded.length) return;
+			required.callback();
+			this.required.erase(required);
+			this.fireEvent('requirementLoaded', [loaded, required]);
+		}, this);
+	}
+
+};
+
+$extend(Depender, new Events);
+$extend(Depender, new Options);
+$extend(Depender, new Log);
+
+Depender._setOptions = Depender.setOptions;
+Depender.setOptions = function(){
+	Depender._setOptions.apply(Depender, arguments);
+	if (this.options.log) Depender.enableLog();
+	return this;
+};
+/*
+---
+
+script: Class.Refactor.js
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Class.refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+	$each(refactors, function(item, name){
+		var origin = original.prototype[name];
+		if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
+			var old = this.previous;
+			this.previous = origin;
+			var value = item.apply(this, arguments);
+			this.previous = old;
+			return value;
+		}); else original.implement(name, item);
+	});
+
+	return original;
+
+};/*
+---
+
+script: Class.Binds.js
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+    return binds;
+};
+
+Class.Mutators.initialize = function(initialize){
+	return function(){
+		$splat(this.Binds).each(function(name){
+			var original = this[name];
+			if (original) this[name] = original.bind(this);
+		}, this);
+		return initialize.apply(this, arguments);
+	};
+};
+/*
+---
+
+script: Class.Occlude.js
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires: 
+ - core/1.2.4/Class
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+	occlude: function(property, element){
+		element = document.id(element || this.element);
+		var instance = element.retrieve(property || this.property);
+		if (instance && !$defined(this.occluded))
+			return this.occluded = instance;
+
+		this.occluded = false;
+		element.store(property || this.property, this);
+		return this.occluded;
+	}
+
+});/*
+---
+
+script: Chain.Wait.js
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires: 
+ - core:1.2.4/Chain
+ - core:1.2.4/Element
+ - core:1.2.4/Fx
+ - /MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+	var wait = {
+		wait: function(duration){
+			return this.chain(function(){
+				this.callChain.delay($pick(duration, 500), this);
+			}.bind(this));
+		}
+	};
+
+	Chain.implement(wait);
+
+	if (window.Fx){
+		Fx.implement(wait);
+		['Css', 'Tween', 'Elements'].each(function(cls){
+			if (Fx[cls]) Fx[cls].implement(wait);
+		});
+	}
+
+	Element.implement({
+		chains: function(effects){
+			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
+				effect = this.get(effect);
+				if (!effect) return;
+				effect.setOptions({
+					link:'chain'
+				});
+			}, this);
+			return this;
+		},
+		pauseFx: function(duration, effect){
+			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
+			return this;
+		}
+	});
+
+})();/*
+---
+
+script: Array.Extras.js
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Array
+
+provides: [Array.Extras]
+
+...
+*/
+Array.implement({
+
+	min: function(){
+		return Math.min.apply(null, this);
+	},
+
+	max: function(){
+		return Math.max.apply(null, this);
+	},
+
+	average: function(){
+		return this.length ? this.sum() / this.length : 0;
+	},
+
+	sum: function(){
+		var result = 0, l = this.length;
+		if (l){
+			do {
+				result += this[--l];
+			} while (l);
+		}
+		return result;
+	},
+
+	unique: function(){
+		return [].combine(this);
+	},
+
+	shuffle: function(){
+		for (var i = this.length; i && --i;){
+			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+			this[i] = this[r];
+			this[r] = temp;
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Date.English.US.js
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.English.US]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Date', {
+
+	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['month', 'date', 'year'],
+	shortDate: '%m/%d/%Y',
+	shortTime: '%I:%M%p',
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1st, 2nd, 3rd, etc.
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'less than a minute ago',
+	minuteAgo: 'about a minute ago',
+	minutesAgo: '{delta} minutes ago',
+	hourAgo: 'about an hour ago',
+	hoursAgo: 'about {delta} hours ago',
+	dayAgo: '1 day ago',
+	daysAgo: '{delta} days ago',
+	weekAgo: '1 week ago',
+	weeksAgo: '{delta} weeks ago',
+	monthAgo: '1 month ago',
+	monthsAgo: '{delta} months ago',
+	yearAgo: '1 year ago',
+	yearsAgo: '{delta} years ago',
+	lessThanMinuteUntil: 'less than a minute from now',
+	minuteUntil: 'about a minute from now',
+	minutesUntil: '{delta} minutes from now',
+	hourUntil: 'about an hour from now',
+	hoursUntil: 'about {delta} hours from now',
+	dayUntil: '1 day from now',
+	daysUntil: '{delta} days from now',
+	weekUntil: '1 week from now',
+	weeksUntil: '{delta} weeks from now',
+	monthUntil: '1 month from now',
+	monthsUntil: '{delta} months from now',
+	yearUntil: '1 year from now',
+	yearsUntil: '{delta} years from now'
+
+});
+/*
+---
+
+script: Date.js
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - core:1.2.4/Number
+ - core:1.2.4/Lang
+ - core:1.2.4/Date.English.US
+ - /MooTools.More
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+if (!Date.now) Date.now = $time;
+
+Date.Methods = {
+	ms: 'Milliseconds',
+	year: 'FullYear',
+	min: 'Minutes',
+	mo: 'Month',
+	sec: 'Seconds',
+	hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
+	Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(what, length){
+	return new Array(length - String(what).length + 1).join('0') + what;
+};
+
+Date.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				prop = prop.toLowerCase();
+				var m = Date.Methods;
+				if (m[prop]) this['set' + m[prop]](value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		prop = prop.toLowerCase();
+		var m = Date.Methods;
+		if (m[prop]) return this['get' + m[prop]]();
+		return null;
+	},
+
+	clone: function(){
+		return new Date(this.get('time'));
+	},
+
+	increment: function(interval, times){
+		interval = interval || 'day';
+		times = $pick(times, 1);
+
+		switch (interval){
+			case 'year':
+				return this.increment('month', times * 12);
+			case 'month':
+				var d = this.get('date');
+				this.set('date', 1).set('mo', this.get('mo') + times);
+				return this.set('date', d.min(this.get('lastdayofmonth')));
+			case 'week':
+				return this.increment('day', times * 7);
+			case 'day':
+				return this.set('date', this.get('date') + times);
+		}
+
+		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+		return this.set('time', this.get('time') + times * Date.units[interval]());
+	},
+
+	decrement: function(interval, times){
+		return this.increment(interval, -1 * $pick(times, 1));
+	},
+
+	isLeapYear: function(){
+		return Date.isLeapYear(this.get('year'));
+	},
+
+	clearTime: function(){
+		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+	},
+
+	diff: function(date, resolution){
+		if ($type(date) == 'string') date = Date.parse(date);
+		
+		return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
+	},
+
+	getLastDayOfMonth: function(){
+		return Date.daysInMonth(this.get('mo'), this.get('year'));
+	},
+
+	getDayOfYear: function(){
+		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) 
+			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+	},
+
+	getWeek: function(){
+		return (this.get('dayofyear') / 7).ceil();
+	},
+	
+	getOrdinal: function(day){
+		return Date.getMsg('ordinal', day || this.get('date'));
+	},
+
+	getTimezone: function(){
+		return this.toString()
+			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+	},
+
+	getGMTOffset: function(){
+		var off = this.get('timezoneOffset');
+		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+	},
+
+	setAMPM: function(ampm){
+		ampm = ampm.toUpperCase();
+		var hr = this.get('hr');
+		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+		return this;
+	},
+
+	getAMPM: function(){
+		return (this.get('hr') < 12) ? 'AM' : 'PM';
+	},
+
+	parse: function(str){
+		this.set('time', Date.parse(str));
+		return this;
+	},
+
+	isValid: function(date) {
+		return !!(date || this).valueOf();
+	},
+
+	format: function(f){
+		if (!this.isValid()) return 'invalid date';
+		f = f || '%x %X';
+		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
+		var d = this;
+		return f.replace(/%([a-z%])/gi,
+			function($0, $1){
+				switch ($1){
+					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
+					case 'A': return Date.getMsg('days')[d.get('day')];
+					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
+					case 'B': return Date.getMsg('months')[d.get('month')];
+					case 'c': return d.toString();
+					case 'd': return pad(d.get('date'), 2);
+					case 'H': return pad(d.get('hr'), 2);
+					case 'I': return ((d.get('hr') % 12) || 12);
+					case 'j': return pad(d.get('dayofyear'), 3);
+					case 'm': return pad((d.get('mo') + 1), 2);
+					case 'M': return pad(d.get('min'), 2);
+					case 'o': return d.get('ordinal');
+					case 'p': return Date.getMsg(d.get('ampm'));
+					case 'S': return pad(d.get('seconds'), 2);
+					case 'U': return pad(d.get('week'), 2);
+					case 'w': return d.get('day');
+					case 'x': return d.format(Date.getMsg('shortDate'));
+					case 'X': return d.format(Date.getMsg('shortTime'));
+					case 'y': return d.get('year').toString().substr(2);
+					case 'Y': return d.get('year');
+					case 'T': return d.get('GMTOffset');
+					case 'Z': return d.get('Timezone');
+				}
+				return $1;
+			}
+		);
+	},
+
+	toISOString: function(){
+		return this.format('iso8601');
+	}
+
+});
+
+Date.alias('toISOString', 'toJSON');
+Date.alias('diff', 'compare');
+Date.alias('format', 'strftime');
+
+var formats = {
+	db: '%Y-%m-%d %H:%M:%S',
+	compact: '%Y%m%dT%H%M%S',
+	iso8601: '%Y-%m-%dT%H:%M:%S%T',
+	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
+	'short': '%d %b %H:%M',
+	'long': '%B %d, %Y %H:%M'
+};
+
+var parsePatterns = [];
+var nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+	var ret = -1;
+	var translated = Date.getMsg(type + 's');
+
+	switch ($type(word)){
+		case 'object':
+			ret = translated[word.get(type)];
+			break;
+		case 'number':
+			ret = translated[month - 1];
+			if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
+			break;
+		case 'string':
+			var match = translated.filter(function(name){
+				return this.test(name);
+			}, new RegExp('^' + word, 'i'));
+			if (!match.length)    throw new Error('Invalid ' + type + ' string');
+			if (match.length > 1) throw new Error('Ambiguous ' + type);
+			ret = match[0];
+	}
+
+	return (num) ? translated.indexOf(ret) : ret;
+};
+
+Date.extend({
+
+	getMsg: function(key, args) {
+		return MooTools.lang.get('Date', key, args);
+	},
+
+	units: {
+		ms: $lambda(1),
+		second: $lambda(1000),
+		minute: $lambda(60000),
+		hour: $lambda(3600000),
+		day: $lambda(86400000),
+		week: $lambda(608400000),
+		month: function(month, year){
+			var d = new Date;
+			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
+		},
+		year: function(year){
+			year = year || new Date().get('year');
+			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+		}
+	},
+
+	daysInMonth: function(month, year){
+		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+	},
+
+	isLeapYear: function(year){
+		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+	},
+
+	parse: function(from){
+		var t = $type(from);
+		if (t == 'number') return new Date(from);
+		if (t != 'string') return from;
+		from = from.clean();
+		if (!from.length) return null;
+
+		var parsed;
+		parsePatterns.some(function(pattern){
+			var bits = pattern.re.exec(from);
+			return (bits) ? (parsed = pattern.handler(bits)) : false;
+		});
+
+		return parsed || new Date(nativeParse(from));
+	},
+
+	parseDay: function(day, num){
+		return parseWord('day', day, num);
+	},
+
+	parseMonth: function(month, num){
+		return parseWord('month', month, num);
+	},
+
+	parseUTC: function(value){
+		var localDate = new Date(value);
+		var utcSeconds = Date.UTC(
+			localDate.get('year'),
+			localDate.get('mo'),
+			localDate.get('date'),
+			localDate.get('hr'),
+			localDate.get('min'),
+			localDate.get('sec')
+		);
+		return new Date(utcSeconds);
+	},
+
+	orderIndex: function(unit){
+		return Date.getMsg('dateOrder').indexOf(unit) + 1;
+	},
+
+	defineFormat: function(name, format){
+		formats[name] = format;
+	},
+
+	defineFormats: function(formats){
+		for (var name in formats) Date.defineFormat(name, formats[name]);
+	},
+
+	parsePatterns: parsePatterns, // this is deprecated
+	
+	defineParser: function(pattern){
+		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+	},
+	
+	defineParsers: function(){
+		Array.flatten(arguments).each(Date.defineParser);
+	},
+	
+	define2DigitYearStart: function(year){
+		startYear = year % 100;
+		startCentury = year - startYear;
+	}
+
+});
+
+var startCentury = 1900;
+var startYear = 70;
+
+var regexOf = function(type){
+	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+		return name.substr(0, 3);
+	}).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+	switch(key){
+		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+			return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
+		case 'X':
+			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
+	}
+	return null;
+};
+
+var keys = {
+	d: /[0-2]?[0-9]|3[01]/,
+	H: /[01]?[0-9]|2[0-3]/,
+	I: /0?[1-9]|1[0-2]/,
+	M: /[0-5]?\d/,
+	s: /\d+/,
+	o: /[a-z]*/,
+	p: /[ap]\.?m\.?/,
+	y: /\d{2}|\d{4}/,
+	Y: /\d{4}/,
+	T: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+	currentLanguage = language;
+	
+	keys.a = keys.A = regexOf('days');
+	keys.b = keys.B = regexOf('months');
+	
+	parsePatterns.each(function(pattern, i){
+		if (pattern.format) parsePatterns[i] = build(pattern.format);
+	});
+};
+
+var build = function(format){
+	if (!currentLanguage) return {format: format};
+	
+	var parsed = [];
+	var re = (format.source || format) // allow format to be regex
+	 .replace(/%([a-z])/gi,
+		function($0, $1){
+			return replacers($1) || $0;
+		}
+	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+	 .replace(/%([a-z%])/gi,
+		function($0, $1){
+			var p = keys[$1];
+			if (!p) return $1;
+			parsed.push($1);
+			return '(' + p.source + ')';
+		}
+	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words
+
+	return {
+		format: format,
+		re: new RegExp('^' + re + '$', 'i'),
+		handler: function(bits){
+			bits = bits.slice(1).associate(parsed);
+			var date = new Date().clearTime();
+			if ('d' in bits) handle.call(date, 'd', 1);
+			if ('m' in bits || 'b' in bits || 'B' in bits) handle.call(date, 'm', 1);
+			for (var key in bits) handle.call(date, key, bits[key]);
+			return date;
+		}
+	};
+};
+
+var handle = function(key, value){
+	if (!value) return this;
+
+	switch(key){
+		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+		case 'd': return this.set('date', value);
+		case 'H': case 'I': return this.set('hr', value);
+		case 'm': return this.set('mo', value - 1);
+		case 'M': return this.set('min', value);
+		case 'p': return this.set('ampm', value.replace(/\./g, ''));
+		case 'S': return this.set('sec', value);
+		case 's': return this.set('ms', ('0.' + value) * 1000);
+		case 'w': return this.set('day', value);
+		case 'Y': return this.set('year', value);
+		case 'y':
+			value = +value;
+			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+			return this.set('year', value);
+		case 'T':
+			if (value == 'Z') value = '+00';
+			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+			return this.set('time', this - offset * 60000);
+	}
+
+	return this;
+};
+
+Date.defineParsers(
+	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+	'%o %b %d %X %T %Y' // "Thu Oct 22 08:11:23 +0000 2009"
+);
+
+MooTools.lang.addEvent('langChange', function(language){
+	if (MooTools.lang.get('Date')) recompile(language);
+}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
+
+})();/*
+---
+
+script: Date.Extras.js
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - /Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+	timeDiffInWords: function(relative_to){
+		return Date.distanceOfTimeInWords(this, relative_to || new Date);
+	},
+
+	timeDiff: function(to, joiner){
+		if (to == null) to = new Date;
+		var delta = ((to - this) / 1000).toInt();
+		if (!delta) return '0s';
+		
+		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
+		var duration, vals = [];
+		
+		for (var step in durations){
+			if (!delta) break;
+			if ((duration = durations[step])){
+				vals.unshift((delta % duration) + step);
+				delta = (delta / duration).toInt();
+			} else {
+				vals.unshift(delta + step);
+			}
+		}
+		
+		return vals.join(joiner || ':');
+	}
+
+});
+
+Date.alias('timeDiffInWords', 'timeAgoInWords');
+
+Date.extend({
+
+	distanceOfTimeInWords: function(from, to){
+		return Date.getTimePhrase(((to - from) / 1000).toInt());
+	},
+
+	getTimePhrase: function(delta){
+		var suffix = (delta < 0) ? 'Until' : 'Ago';
+		if (delta < 0) delta *= -1;
+		
+		var units = {
+			minute: 60,
+			hour: 60,
+			day: 24,
+			week: 7,
+			month: 52 / 12,
+			year: 12,
+			eon: Infinity
+		};
+		
+		var msg = 'lessThanMinute';
+		
+		for (var unit in units){
+			var interval = units[unit];
+			if (delta < 1.5 * interval){
+				if (delta > 0.75 * interval) msg = unit;
+				break;
+			}
+			delta /= interval;
+			msg = unit + 's';
+		}
+		
+		return Date.getMsg(msg + suffix).substitute({delta: delta.round()});
+	}
+
+});
+
+
+Date.defineParsers(
+
+	{
+		// "today", "tomorrow", "yesterday"
+		re: /^(?:tod|tom|yes)/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			switch(bits[0]){
+				case 'tom': return d.increment();
+				case 'yes': return d.decrement();
+				default: 	return d;
+			}
+		}
+	},
+
+	{
+		// "next Wednesday", "last Thursday"
+		re: /^(next|last) ([a-z]+)$/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			var day = d.getDay();
+			var newDay = Date.parseDay(bits[2], true);
+			var addDays = newDay - day;
+			if (newDay <= day) addDays += 7;
+			if (bits[1] == 'last') addDays -= 7;
+			return d.set('date', d.getDate() + addDays);
+		}
+	}
+
+);
+/*
+---
+
+script: Hash.Extras.js
+
+description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Hash.base
+ - /MooTools.More
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+	getFromPath: function(notation){
+		var source = this.getClean();
+		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
+			if (!source) return null;
+			var prop = arguments[2] || arguments[1] || arguments[0];
+			source = (prop in source) ? source[prop] : null;
+			return match;
+		});
+		return source;
+	},
+
+	cleanValues: function(method){
+		method = method || $defined;
+		this.each(function(v, k){
+			if (!method(v)) this.erase(k);
+		}, this);
+		return this;
+	},
+
+	run: function(){
+		var args = arguments;
+		this.each(function(v, k){
+			if ($type(v) == 'function') v.run(args);
+		});
+	}
+
+});/*
+---
+
+script: String.Extras.js
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+
+requires:
+ - core:1.2.4/String
+ - core:1.2.4/$util
+ - core:1.2.4/Array
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+  
+var special = ['À','à','�?','á','Â','â','Ã','ã','Ä','ä','Å','å','Ă','ă','Ą','ą','Ć','ć','Č','�?','Ç','ç', 'Ď','�?','�?','đ', 'È','è','É','é','Ê','ê','Ë','ë','Ě','ě','Ę','ę', 'Ğ','ğ','Ì','ì','�?','í','Î','î','�?','ï', 'Ĺ','ĺ','Ľ','ľ','�?','ł', 'Ñ','ñ','Ň','ň','Ń','ń','Ò','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','ő','Ř','ř','Ŕ','ŕ','Š','š','Ş','ş','Ś','ś', 'Ť','ť','Ť','ť','Ţ','ţ','Ù','ù','Ú','ú','Û','û','Ü','ü','Ů','ů', 'Ÿ','ÿ','ý','�?','Ž','ž','Ź','ź','Ż','ż', 'Þ','þ','�?','ð','ß','Œ','œ','Æ','æ','µ'];
+
+var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];
+
+var tidymap = {
+	"[\xa0\u2002\u2003\u2009]": " ",
+	"\xb7": "*",
+	"[\u2018\u2019]": "'",
+	"[\u201c\u201d]": '"',
+	"\u2026": "...",
+	"\u2013": "-",
+	"\u2014": "--",
+	"\uFFFD": "&raquo;"
+};
+
+var getRegForTag = function(tag, contents) {
+	tag = tag || '';
+	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
+	reg = new RegExp(regstr, "gi");
+	return reg;
+};
+
+String.implement({
+
+	standardize: function(){
+		var text = this;
+		special.each(function(ch, i){
+			text = text.replace(new RegExp(ch, 'g'), standard[i]);
+		});
+		return text;
+	},
+
+	repeat: function(times){
+		return new Array(times + 1).join(this);
+	},
+
+	pad: function(length, str, dir){
+		if (this.length >= length) return this;
+		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
+		if (!dir || dir == 'right') return this + pad;
+		if (dir == 'left') return pad + this;
+		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+	},
+
+	getTags: function(tag, contents){
+		return this.match(getRegForTag(tag, contents)) || [];
+	},
+
+	stripTags: function(tag, contents){
+		return this.replace(getRegForTag(tag, contents), '');
+	},
+
+	tidy: function(){
+		var txt = this.toString();
+		$each(tidymap, function(value, key){
+			txt = txt.replace(new RegExp(key, 'g'), value);
+		});
+		return txt;
+	}
+
+});
+
+})();/*
+---
+
+script: String.QueryString.js
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - /MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+	parseQueryString: function(){
+		var vars = this.split(/[&;]/), res = {};
+		if (vars.length) vars.each(function(val){
+			var index = val.indexOf('='),
+				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
+				value = decodeURIComponent(val.substr(index + 1)),
+				obj = res;
+			keys.each(function(key, i){
+				var current = obj[key];
+				if(i < keys.length - 1)
+					obj = obj[key] = current || {};
+				else if($type(current) == 'array')
+					current.push(value);
+				else
+					obj[key] = $defined(current) ? [current, value] : value;
+			});
+		});
+		return res;
+	},
+
+	cleanQueryString: function(method){
+		return this.split('&').filter(function(val){
+			var index = val.indexOf('='),
+			key = index < 0 ? '' : val.substr(0, index),
+			value = val.substr(index + 1);
+			return method ? method.run([key, value]) : $chk(value);
+		}).join('&');
+	}
+
+});/*
+---
+
+script: URI.js
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markb�ge
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Selectors
+ - /String.QueryString
+
+provides: URI
+
+...
+*/
+
+var URI = new Class({
+
+	Implements: Options,
+
+	options: {
+		/*base: false*/
+	},
+
+	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+	initialize: function(uri, options){
+		this.setOptions(options);
+		var base = this.options.base || URI.base;
+		if(!uri) uri = base;
+		
+		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
+		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+	},
+
+	parse: function(value, base){
+		var bits = value.match(this.regex);
+		if (!bits) return false;
+		bits.shift();
+		return this.merge(bits.associate(this.parts), base);
+	},
+
+	merge: function(bits, base){
+		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+		if (base){
+			this.parts.every(function(part){
+				if (bits[part]) return false;
+				bits[part] = base[part] || '';
+				return true;
+			});
+		}
+		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+		return bits;
+	},
+
+	parseDirectory: function(directory, baseDirectory) {
+		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+		if (!directory.test(URI.regs.directoryDot)) return directory;
+		var result = [];
+		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+			if (dir == '..' && result.length > 0) result.pop();
+			else if (dir != '.') result.push(dir);
+		});
+		return result.join('/') + '/';
+	},
+
+	combine: function(bits){
+		return bits.value || bits.scheme + '://' +
+			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+			(bits.directory || '/') + (bits.file || '') +
+			(bits.query ? '?' + bits.query : '') +
+			(bits.fragment ? '#' + bits.fragment : '');
+	},
+
+	set: function(part, value, base){
+		if (part == 'value'){
+			var scheme = value.match(URI.regs.scheme);
+			if (scheme) scheme = scheme[1];
+			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
+			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+		} else if (part == 'data') {
+			this.setData(value);
+		} else {
+			this.parsed[part] = value;
+		}
+		return this;
+	},
+
+	get: function(part, base){
+		switch(part){
+			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+			case 'data' : return this.getData();
+		}
+		return this.parsed[part] || '';
+	},
+
+	go: function(){
+		document.location.href = this.toString();
+	},
+
+	toURI: function(){
+		return this;
+	},
+
+	getData: function(key, part){
+		var qs = this.get(part || 'query');
+		if (!$chk(qs)) return key ? null : {};
+		var obj = qs.parseQueryString();
+		return key ? obj[key] : obj;
+	},
+
+	setData: function(values, merge, part){
+		if (typeof values == 'string'){
+			data = this.getData();
+			data[arguments[0]] = arguments[1];
+			values = data;
+		} else if (merge) {
+			values = $merge(this.getData(), values);
+		}
+		return this.set(part || 'query', Hash.toQueryString(values));
+	},
+
+	clearData: function(part){
+		return this.set(part || 'query', '');
+	}
+
+});
+
+URI.prototype.toString = URI.prototype.valueOf = function(){
+	return this.get('value');
+};
+
+URI.regs = {
+	endSlash: /\/$/,
+	scheme: /^(\w+):/,
+	directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
+
+String.implement({
+
+	toURI: function(options){
+		return new URI(this, options);
+	}
+
+});/*
+---
+
+script: URI.Relative.js
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - /Class.refactor
+ - /URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+	combine: function(bits, base){
+		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+			return this.previous.apply(this, arguments);
+		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+		var baseDir = base.directory.split('/'),
+			relDir = bits.directory.split('/'),
+			path = '',
+			offset;
+
+		var i = 0;
+		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+		return (path || (bits.file ? '' : './')) + end;
+	},
+
+	toAbsolute: function(base){
+		base = new URI(base);
+		if (base) base.set('directory', '').set('file', '');
+		return this.toRelative(base);
+	},
+
+	toRelative: function(base){
+		return this.get('value', new URI(base));
+	}
+
+});/*
+---
+
+script: Element.Forms.js
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+	tidy: function(){
+		this.set('value', this.get('value').tidy());
+	},
+
+	getTextInRange: function(start, end){
+		return this.get('value').substring(start, end);
+	},
+
+	getSelectedText: function(){
+		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		return document.selection.createRange().text;
+	},
+
+	getSelectedRange: function() {
+		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
+		var pos = {start: 0, end: 0};
+		var range = this.getDocument().selection.createRange();
+		if (!range || range.parentElement() != this) return pos;
+		var dup = range.duplicate();
+		if (this.type == 'text') {
+			pos.start = 0 - dup.moveStart('character', -100000);
+			pos.end = pos.start + range.text.length;
+		} else {
+			var value = this.get('value');
+			var offset = value.length;
+			dup.moveToElementText(this);
+			dup.setEndPoint('StartToEnd', range);
+			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+			pos.end = offset - dup.text.length;
+			dup.setEndPoint('StartToStart', range);
+			pos.start = offset - dup.text.length;
+		}
+		return pos;
+	},
+
+	getSelectionStart: function(){
+		return this.getSelectedRange().start;
+	},
+
+	getSelectionEnd: function(){
+		return this.getSelectedRange().end;
+	},
+
+	setCaretPosition: function(pos){
+		if (pos == 'end') pos = this.get('value').length;
+		this.selectRange(pos, pos);
+		return this;
+	},
+
+	getCaretPosition: function(){
+		return this.getSelectedRange().start;
+	},
+
+	selectRange: function(start, end){
+		if (this.setSelectionRange) {
+			this.focus();
+			this.setSelectionRange(start, end);
+		} else {
+			var value = this.get('value');
+			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+			start = value.substr(0, start).replace(/\r/g, '').length;
+			var range = this.createTextRange();
+			range.collapse(true);
+			range.moveEnd('character', start + diff);
+			range.moveStart('character', start);
+			range.select();
+		}
+		return this;
+	},
+
+	insertAtCursor: function(value, select){
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
+		else this.setCaretPosition(pos.start + value.length);
+		return this;
+	},
+
+	insertAroundCursor: function(options, select){
+		options = $extend({
+			before: '',
+			defaultMiddle: '',
+			after: ''
+		}, options);
+		var value = this.getSelectedText() || options.defaultMiddle;
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		if (pos.start == pos.end){
+			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+		} else {
+			var current = text.substring(pos.start, pos.end);
+			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+			var selStart = pos.start + options.before.length;
+			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
+			else this.setCaretPosition(selStart + text.length);
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Elements.From.js
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Elements.from]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+	if ($pick(excludeScripts, true)) text = text.stripScripts();
+
+	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
+
+	if (match){
+		container = new Element('table');
+		var tag = match[1].toLowerCase();
+		if (['td', 'th', 'tr'].contains(tag)){
+			container = new Element('tbody').inject(container);
+			if (tag != 'tr') container = new Element('tr').inject(container);
+		}
+	}
+
+	return (container || new Element('div')).set('html', text).getChildren();
+};/*
+---
+
+script: Element.Delegation.js
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+credits:
+ - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Daniel Steigerwald
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Selectors
+ - /MooTools.More
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(addEvent, removeEvent){
+	
+	var match = /(.*?):relay\(([^)]+)\)$/,
+		combinators = /[+>~\s]/,
+		splitType = function(type){
+			var bits = type.match(match);
+			return !bits ? {event: type} : {
+				event: bits[1],
+				selector: bits[2]
+			};
+		},
+		check = function(e, selector){
+			var t = e.target;
+			if (combinators.test(selector = selector.trim())){
+				var els = this.getElements(selector);
+				for (var i = els.length; i--; ){
+					var el = els[i];
+					if (t == el || el.hasChild(t)) return el;
+				}
+			} else {
+				for ( ; t && t != this; t = t.parentNode){
+					if (Element.match(t, selector)) return document.id(t);
+				}
+			}
+			return null;
+		};
+
+	Element.implement({
+
+		addEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var monitors = this.retrieve('$moo:delegateMonitors', {});
+				if (!monitors[type]){
+					var monitor = function(e){
+						var el = check.call(this, e, splitted.selector);
+						if (el) this.fireEvent(type, [e, el], 0, el);
+					}.bind(this);
+					monitors[type] = monitor;
+					addEvent.call(this, splitted.event, monitor);
+				}
+			}
+			return addEvent.apply(this, arguments);
+		},
+
+		removeEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var events = this.retrieve('events');
+				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
+
+				if (fn) removeEvent.apply(this, [type, fn]);
+				else removeEvent.apply(this, type);
+
+				events = this.retrieve('events');
+				if (events && events[type] && events[type].keys.length == 0){
+					var monitors = this.retrieve('$moo:delegateMonitors', {});
+					removeEvent.apply(this, [splitted.event, monitors[type]]);
+					delete monitors[type];
+				}
+				return this;
+			}
+			return removeEvent.apply(this, arguments);
+		},
+
+		fireEvent: function(type, args, delay, bind){
+			var events = this.retrieve('events');
+			if (!events || !events[type]) return this;
+			events[type].keys.each(function(fn){
+				fn.create({bind: bind || this, delay: delay, arguments: args})();
+			}, this);
+			return this;
+		}
+
+	});
+
+})(Element.prototype.addEvent, Element.prototype.removeEvent);/*
+---
+
+script: Element.Measure.js
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+Element.implement({
+
+	measure: function(fn){
+		var vis = function(el) {
+			return !!(!el || el.offsetHeight || el.offsetWidth);
+		};
+		if (vis(this)) return fn.apply(this);
+		var parent = this.getParent(),
+			restorers = [],
+			toMeasure = []; 
+		while (!vis(parent) && parent != document.body) {
+			toMeasure.push(parent.expose());
+			parent = parent.getParent();
+		}
+		var restore = this.expose();
+		var result = fn.apply(this);
+		restore();
+		toMeasure.each(function(restore){
+			restore();
+		});
+		return result;
+	},
+
+	expose: function(){
+		if (this.getStyle('display') != 'none') return $empty;
+		var before = this.style.cssText;
+		this.setStyles({
+			display: 'block',
+			position: 'absolute',
+			visibility: 'hidden'
+		});
+		return function(){
+			this.style.cssText = before;
+		}.bind(this);
+	},
+
+	getDimensions: function(options){
+		options = $merge({computeSize: false},options);
+		var dim = {};
+		var getSize = function(el, options){
+			return (options.computeSize)?el.getComputedSize(options):el.getSize();
+		};
+		var parent = this.getParent('body');
+		if (parent && this.getStyle('display') == 'none'){
+			dim = this.measure(function(){
+				return getSize(this, options);
+			});
+		} else if (parent){
+			try { //safari sometimes crashes here, so catch it
+				dim = getSize(this, options);
+			}catch(e){}
+		} else {
+			dim = {x: 0, y: 0};
+		}
+		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
+	},
+
+	getComputedSize: function(options){
+		options = $merge({
+			styles: ['padding','border'],
+			plains: {
+				height: ['top','bottom'],
+				width: ['left','right']
+			},
+			mode: 'both'
+		}, options);
+		var size = {width: 0,height: 0};
+		switch (options.mode){
+			case 'vertical':
+				delete size.width;
+				delete options.plains.width;
+				break;
+			case 'horizontal':
+				delete size.height;
+				delete options.plains.height;
+				break;
+		}
+		var getStyles = [];
+		//this function might be useful in other places; perhaps it should be outside this function?
+		$each(options.plains, function(plain, key){
+			plain.each(function(edge){
+				options.styles.each(function(style){
+					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
+				});
+			});
+		});
+		var styles = {};
+		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
+		var subtracted = [];
+		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
+			var capitalized = key.capitalize();
+			size['total' + capitalized] = size['computed' + capitalized] = 0;
+			plain.each(function(edge){ //top, left, right, bottom
+				size['computed' + edge.capitalize()] = 0;
+				getStyles.each(function(style, i){ //padding, border, etc.
+					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
+					if (style.test(edge)){
+						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
+						size['total' + capitalized] = size['total' + capitalized] + styles[style];
+						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
+					}
+					//if width != width (so, padding-left, for instance), then subtract that from the total
+					if (style.test(edge) && key != style &&
+						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
+						subtracted.push(style);
+						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
+					}
+				});
+			});
+		});
+
+		['Width', 'Height'].each(function(value){
+			var lower = value.toLowerCase();
+			if(!$chk(size[lower])) return;
+
+			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
+			size['total' + value] = size[lower] + size['total' + value];
+			delete size['computed' + value];
+		}, this);
+
+		return $extend(styles, size);
+	}
+
+});/*
+---
+
+script: Element.Pin.js
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+	var supportsPositionFixed = false;
+	window.addEvent('domready', function(){
+		var test = new Element('div').setStyles({
+			position: 'fixed',
+			top: 0,
+			right: 0
+		}).inject(document.body);
+		supportsPositionFixed = (test.offsetTop === 0);
+		test.dispose();
+	});
+
+	Element.implement({
+
+		pin: function(enable){
+			if (this.getStyle('display') == 'none') return null;
+			
+			var p,
+					scroll = window.getScroll();
+			if (enable !== false){
+				p = this.getPosition();
+				if (!this.retrieve('pinned')){
+					var pos = {
+						top: p.y - scroll.y,
+						left: p.x - scroll.x
+					};
+					if (supportsPositionFixed){
+						this.setStyle('position', 'fixed').setStyles(pos);
+					} else {
+						this.store('pinnedByJS', true);
+						this.setStyles({
+							position: 'absolute',
+							top: p.y,
+							left: p.x
+						}).addClass('isPinned');
+						this.store('scrollFixer', (function(){
+							if (this.retrieve('pinned'))
+								var scroll = window.getScroll();
+								this.setStyles({
+									top: pos.top.toInt() + scroll.y,
+									left: pos.left.toInt() + scroll.x
+								});
+						}).bind(this));
+						window.addEvent('scroll', this.retrieve('scrollFixer'));
+					}
+					this.store('pinned', true);
+				}
+			} else {
+				var op;
+				if (!Browser.Engine.trident){
+					var parent = this.getParent();
+					op = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+				}
+				p = this.getPosition(op);
+				this.store('pinned', false);
+				var reposition;
+				if (supportsPositionFixed && !this.retrieve('pinnedByJS')){
+					reposition = {
+						top: p.y + scroll.y,
+						left: p.x + scroll.x
+					};
+				} else {
+					this.store('pinnedByJS', false);
+					window.removeEvent('scroll', this.retrieve('scrollFixer'));
+					reposition = {
+						top: p.y,
+						left: p.x
+					};
+				}
+				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
+			}
+			return this;
+		},
+
+		unpin: function(){
+			return this.pin(false);
+		},
+
+		togglepin: function(){
+			this.pin(!this.retrieve('pinned'));
+		}
+
+	});
+
+})();/*
+---
+
+script: Element.Position.js
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Element.Measure
+
+provides: [Elements.Position]
+
+...
+*/
+
+(function(){
+
+var original = Element.prototype.position;
+
+Element.implement({
+
+	position: function(options){
+		//call original position if the options are x/y values
+		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
+		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
+		options = $merge({
+			// minimum: { x: 0, y: 0 },
+			// maximum: { x: 0, y: 0},
+			relativeTo: document.body,
+			position: {
+				x: 'center', //left, center, right
+				y: 'center' //top, center, bottom
+			},
+			edge: false,
+			offset: {x: 0, y: 0},
+			returnPos: false,
+			relFixedPosition: false,
+			ignoreMargins: false,
+			ignoreScroll: false,
+			allowNegative: false
+		}, options);
+		//compute the offset of the parent positioned element if this element is in one
+		var parentOffset = {x: 0, y: 0}, 
+				parentPositioned = false;
+		/* dollar around getOffsetParent should not be necessary, but as it does not return
+		 * a mootools extended element in IE, an error occurs on the call to expose. See:
+		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
+		var offsetParent = this.measure(function(){
+			return document.id(this.getOffsetParent());
+		});
+		if (offsetParent && offsetParent != this.getDocument().body){
+			parentOffset = offsetParent.measure(function(){
+				return this.getPosition();
+			});
+			parentPositioned = offsetParent != document.id(options.relativeTo);
+			options.offset.x = options.offset.x - parentOffset.x;
+			options.offset.y = options.offset.y - parentOffset.y;
+		}
+		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
+		//topRight, topLeft, centerTop, centerBottom, center
+		var fixValue = function(option){
+			if ($type(option) != 'string') return option;
+			option = option.toLowerCase();
+			var val = {};
+			if (option.test('left')) val.x = 'left';
+			else if (option.test('right')) val.x = 'right';
+			else val.x = 'center';
+			if (option.test('upper') || option.test('top')) val.y = 'top';
+			else if (option.test('bottom')) val.y = 'bottom';
+			else val.y = 'center';
+			return val;
+		};
+		options.edge = fixValue(options.edge);
+		options.position = fixValue(options.position);
+		if (!options.edge){
+			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
+			else options.edge = {x:'left', y:'top'};
+		}
+
+		this.setStyle('position', 'absolute');
+		var rel = document.id(options.relativeTo) || document.body,
+				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
+				top = calc.y, left = calc.x;
+
+		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
+		var pos = {},
+				prefY = options.offset.y,
+				prefX = options.offset.x,
+				winSize = window.getSize();
+		switch(options.position.x){
+			case 'left':
+				pos.x = left + prefX;
+				break;
+			case 'right':
+				pos.x = left + prefX + rel.offsetWidth;
+				break;
+			default: //center
+				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
+				break;
+		}
+		switch(options.position.y){
+			case 'top':
+				pos.y = top + prefY;
+				break;
+			case 'bottom':
+				pos.y = top + prefY + rel.offsetHeight;
+				break;
+			default: //center
+				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
+				break;
+		}
+		if (options.edge){
+			var edgeOffset = {};
+
+			switch(options.edge.x){
+				case 'left':
+					edgeOffset.x = 0;
+					break;
+				case 'right':
+					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
+					break;
+				default: //center
+					edgeOffset.x = -(dim.totalWidth/2);
+					break;
+			}
+			switch(options.edge.y){
+				case 'top':
+					edgeOffset.y = 0;
+					break;
+				case 'bottom':
+					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
+					break;
+				default: //center
+					edgeOffset.y = -(dim.totalHeight/2);
+					break;
+			}
+			pos.x += edgeOffset.x;
+			pos.y += edgeOffset.y;
+		}
+		pos = {
+			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
+			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
+		};
+		var xy = {left: 'x', top: 'y'};
+		['minimum', 'maximum'].each(function(minmax) {
+			['left', 'top'].each(function(lr) {
+				var val = options[minmax] ? options[minmax][xy[lr]] : null;
+				if (val != null && pos[lr] < val) pos[lr] = val;
+			});
+		});
+		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
+			var winScroll = window.getScroll();
+			pos.top+= winScroll.y;
+			pos.left+= winScroll.x;
+		}
+		if (options.ignoreScroll) {
+			var relScroll = rel.getScroll();
+			pos.top-= relScroll.y;
+			pos.left-= relScroll.x;
+		}
+		if (options.ignoreMargins) {
+			pos.left += (
+				options.edge.x == 'right' ? dim['margin-right'] : 
+				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) : 
+					- dim['margin-left']
+			);
+			pos.top += (
+				options.edge.y == 'bottom' ? dim['margin-bottom'] : 
+				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) : 
+					- dim['margin-top']
+			);
+		}
+		pos.left = Math.ceil(pos.left);
+		pos.top = Math.ceil(pos.top);
+		if (options.returnPos) return pos;
+		else this.setStyles(pos);
+		return this;
+	}
+
+});
+
+})();/*
+---
+
+script: Element.Shortcuts.js
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+	isDisplayed: function(){
+		return this.getStyle('display') != 'none';
+	},
+
+	isVisible: function(){
+		var w = this.offsetWidth,
+			h = this.offsetHeight;
+		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
+	},
+
+	toggle: function(){
+		return this[this.isDisplayed() ? 'hide' : 'show']();
+	},
+
+	hide: function(){
+		var d;
+		try {
+			//IE fails here if the element is not in the dom
+			d = this.getStyle('display');
+		} catch(e){}
+		return this.store('originalDisplay', d || '').setStyle('display', 'none');
+	},
+
+	show: function(display){
+		display = display || this.retrieve('originalDisplay') || 'block';
+		return this.setStyle('display', (display == 'none') ? 'block' : display);
+	},
+
+	swapClass: function(remove, add){
+		return this.removeClass(remove).addClass(add);
+	}
+
+});
+/*
+---
+
+script: IframeShim.js
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Options Events
+ - /Element.Position
+ - /Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+var IframeShim = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		className: 'iframeShim',
+		src: 'javascript:false;document.write("");',
+		display: false,
+		zIndex: null,
+		margin: 0,
+		offset: {x: 0, y: 0},
+		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
+	},
+
+	property: 'IframeShim',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.makeShim();
+		return this;
+	},
+
+	makeShim: function(){
+		if(this.options.browsers){
+			var zIndex = this.element.getStyle('zIndex').toInt();
+
+			if (!zIndex){
+				zIndex = 1;
+				var pos = this.element.getStyle('position');
+				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+				this.element.setStyle('zIndex', zIndex);
+			}
+			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+			if (zIndex < 0) zIndex = 1;
+			this.shim = new Element('iframe', {
+				src: this.options.src,
+				scrolling: 'no',
+				frameborder: 0,
+				styles: {
+					zIndex: zIndex,
+					position: 'absolute',
+					border: 'none',
+					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+				},
+				'class': this.options.className
+			}).store('IframeShim', this);
+			var inject = (function(){
+				this.shim.inject(this.element, 'after');
+				this[this.options.display ? 'show' : 'hide']();
+				this.fireEvent('inject');
+			}).bind(this);
+			if (!IframeShim.ready) window.addEvent('load', inject);
+			else inject();
+		} else {
+			this.position = this.hide = this.show = this.dispose = $lambda(this);
+		}
+	},
+
+	position: function(){
+		if (!IframeShim.ready || !this.shim) return this;
+		var size = this.element.measure(function(){ 
+			return this.getSize(); 
+		});
+		if (this.options.margin != undefined){
+			size.x = size.x - (this.options.margin * 2);
+			size.y = size.y - (this.options.margin * 2);
+			this.options.offset.x += this.options.margin;
+			this.options.offset.y += this.options.margin;
+		}
+		this.shim.set({width: size.x, height: size.y}).position({
+			relativeTo: this.element,
+			offset: this.options.offset
+		});
+		return this;
+	},
+
+	hide: function(){
+		if (this.shim) this.shim.setStyle('display', 'none');
+		return this;
+	},
+
+	show: function(){
+		if (this.shim) this.shim.setStyle('display', 'block');
+		return this.position();
+	},
+
+	dispose: function(){
+		if (this.shim) this.shim.dispose();
+		return this;
+	},
+
+	destroy: function(){
+		if (this.shim) this.shim.destroy();
+		return this;
+	}
+
+});
+
+window.addEvent('load', function(){
+	IframeShim.ready = true;
+});/*
+---
+
+script: Mask.js
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - /Class.Binds
+ - /Element.Position
+ - /IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['position'],
+
+	options: {
+		// onShow: $empty,
+		// onHide: $empty,
+		// onDestroy: $empty,
+		// onClick: $empty,
+		//inject: {
+		//  where: 'after',
+		//  target: null,
+		//},
+		// hideOnClick: false,
+		// id: null,
+		// destroyOnHide: false,
+		style: {},
+		'class': 'mask',
+		maskMargins: false,
+		useIframeShim: true,
+		iframeShimOptions: {}
+	},
+
+	initialize: function(target, options){
+		this.target = document.id(target) || document.id(document.body);
+		this.target.store('Mask', this);
+		this.setOptions(options);
+		this.render();
+		this.inject();
+	},
+	
+	render: function() {
+		this.element = new Element('div', {
+			'class': this.options['class'],
+			id: this.options.id || 'mask-' + $time(),
+			styles: $merge(this.options.style, {
+				display: 'none'
+			}),
+			events: {
+				click: function(){
+					this.fireEvent('click');
+					if (this.options.hideOnClick) this.hide();
+				}.bind(this)
+			}
+		});
+		this.hidden = true;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	inject: function(target, where){
+		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
+		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
+		this.element.inject(target, where);
+		if (this.options.useIframeShim) {
+			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+			this.addEvents({
+				show: this.shim.show.bind(this.shim),
+				hide: this.shim.hide.bind(this.shim),
+				destroy: this.shim.destroy.bind(this.shim)
+			});
+		}
+	},
+
+	position: function(){
+		this.resize(this.options.width, this.options.height);
+		this.element.position({
+			relativeTo: this.target,
+			position: 'topLeft',
+			ignoreMargins: !this.options.maskMargins,
+			ignoreScroll: this.target == document.body
+		});
+		return this;
+	},
+
+	resize: function(x, y){
+		var opt = {
+			styles: ['padding', 'border']
+		};
+		if (this.options.maskMargins) opt.styles.push('margin');
+		var dim = this.target.getComputedSize(opt);
+		if (this.target == document.body) {
+			var win = window.getSize();
+			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+		}
+		this.element.setStyles({
+			width: $pick(x, dim.totalWidth, dim.x),
+			height: $pick(y, dim.totalHeight, dim.y)
+		});
+		return this;
+	},
+
+	show: function(){
+		if (!this.hidden) return this;
+		window.addEvent('resize', this.position);
+		this.position();
+		this.showMask.apply(this, arguments);
+		return this;
+	},
+
+	showMask: function(){
+		this.element.setStyle('display', 'block');
+		this.hidden = false;
+		this.fireEvent('show');
+	},
+
+	hide: function(){
+		if (this.hidden) return this;
+		window.removeEvent('resize', this.position);
+		this.hideMask.apply(this, arguments);
+		if (this.options.destroyOnHide) return this.destroy();
+		return this;
+	},
+
+	hideMask: function(){
+		this.element.setStyle('display', 'none');
+		this.hidden = true;
+		this.fireEvent('hide');
+	},
+
+	toggle: function(){
+		this[this.hidden ? 'show' : 'hide']();
+	},
+
+	destroy: function(){
+		this.hide();
+		this.element.destroy();
+		this.fireEvent('destroy');
+		this.target.eliminate('mask');
+	}
+
+});
+
+Element.Properties.mask = {
+
+	set: function(options){
+		var mask = this.retrieve('mask');
+		return this.eliminate('mask').store('mask:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('mask')){
+			if (this.retrieve('mask')) this.retrieve('mask').destroy();
+			if (options || !this.retrieve('mask:options')) this.set('mask', options);
+			this.store('mask', new Mask(this, this.retrieve('mask:options')));
+		}
+		return this.retrieve('mask');
+	}
+
+};
+
+Element.implement({
+
+	mask: function(options){
+		this.get('mask', options).show();
+		return this;
+	},
+
+	unmask: function(){
+		this.get('mask').hide();
+		return this;
+	}
+
+});/*
+---
+
+script: Spinner.js
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Tween
+ - /Class.refactor
+ - /Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+	Extends: Mask,
+
+	options: {
+		/*message: false,*/
+		'class':'spinner',
+		containerPosition: {},
+		content: {
+			'class':'spinner-content'
+		},
+		messageContainer: {
+			'class':'spinner-msg'
+		},
+		img: {
+			'class':'spinner-img'
+		},
+		fxOptions: {
+			link: 'chain'
+		}
+	},
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		this.target.store('spinner', this);
+
+		//add this to events for when noFx is true; parent methods handle hide/show
+		var deactivate = function(){ this.active = false; }.bind(this);
+		this.addEvents({
+			hide: deactivate,
+			show: deactivate
+		});
+	},
+
+	render: function(){
+		this.parent();
+		this.element.set('id', this.options.id || 'spinner-'+$time());
+		this.content = document.id(this.options.content) || new Element('div', this.options.content);
+		this.content.inject(this.element);
+		if (this.options.message) {
+			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+			this.msg.inject(this.content);
+		}
+		if (this.options.img) {
+			this.img = document.id(this.options.img) || new Element('div', this.options.img);
+			this.img.inject(this.content);
+		}
+		this.element.set('tween', this.options.fxOptions);
+	},
+
+	show: function(noFx){
+		if (this.active) return this.chain(this.show.bind(this));
+		if (!this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	showMask: function(noFx){
+		var pos = function(){
+			this.content.position($merge({
+				relativeTo: this.element
+			}, this.options.containerPosition));
+		}.bind(this);
+		if (noFx) {
+			this.parent();
+			pos();
+		} else {
+			this.element.setStyles({
+				display: 'block',
+				opacity: 0
+			}).tween('opacity', this.options.style.opacity || 0.9);
+			pos();
+			this.hidden = false;
+			this.fireEvent('show');
+			this.callChain();
+		}
+	},
+
+	hide: function(noFx){
+		if (this.active) return this.chain(this.hide.bind(this));
+		if (this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	hideMask: function(noFx){
+		if (noFx) return this.parent();
+		this.element.tween('opacity', 0).get('tween').chain(function(){
+			this.element.setStyle('display', 'none');
+			this.hidden = true;
+			this.fireEvent('hide');
+			this.callChain();
+		}.bind(this));
+	},
+
+	destroy: function(){
+		this.content.destroy();
+		this.parent();
+		this.target.eliminate('spinner');
+	}
+
+});
+
+Spinner.implement(new Chain);
+
+if (window.Request) {
+	Request = Class.refactor(Request, {
+		
+		options: {
+			useSpinner: false,
+			spinnerOptions: {},
+			spinnerTarget: false
+		},
+		
+		initialize: function(options){
+			this._send = this.send;
+			this.send = function(options){
+				if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
+				else this._send(options);
+				return this;
+			};
+			this.previous(options);
+			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+			if (this.options.useSpinner && update) {
+				this.spinner = update.get('spinner', this.options.spinnerOptions);
+				['onComplete', 'onException', 'onCancel'].each(function(event){
+					this.addEvent(event, this.spinner.hide.bind(this.spinner));
+				}, this);
+			}
+		},
+		
+		getSpinner: function(){
+			return this.spinner;
+		}
+		
+	});
+}
+
+Element.Properties.spinner = {
+
+	set: function(options){
+		var spinner = this.retrieve('spinner');
+		return this.eliminate('spinner').store('spinner:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('spinner')){
+			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
+			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
+			new Spinner(this, this.retrieve('spinner:options'));
+		}
+		return this.retrieve('spinner');
+	}
+
+};
+
+Element.implement({
+
+	spin: function(options){
+		this.get('spinner', options).show();
+		return this;
+	},
+
+	unspin: function(){
+		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
+		this.get('spinner', opt.options).hide(opt.callback);
+		return this;
+	}
+
+});/*
+---
+
+script: Form.Request.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Request.HTML
+ - /Class.Binds
+ - /Class.Occlude
+ - /Spinner
+ - /String.QueryString
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+	Form.Request = new Class({
+
+		Binds: ['onSubmit', 'onFormValidate'],
+
+		Implements: [Options, Events, Class.Occlude],
+
+		options: {
+			//onFailure: $empty,
+			//onSuccess: #empty, //aliased to onComplete,
+			//onSend: $empty
+			requestOptions: {
+				evalScripts: true,
+				useSpinner: true,
+				emulation: false,
+				link: 'ignore'
+			},
+			extraData: {},
+			resetForm: true
+		},
+
+		property: 'form.request',
+
+		initialize: function(form, update, options) {
+			this.element = document.id(form);
+			if (this.occlude()) return this.occluded;
+			this.update = document.id(update);
+			this.setOptions(options);
+			this.makeRequest();
+			if (this.options.resetForm) {
+				this.request.addEvent('success', function(){
+					$try(function(){ this.element.reset(); }.bind(this));
+					if (window.OverText) OverText.update();
+				}.bind(this));
+			}
+			this.attach();
+		},
+
+		toElement: function() {
+			return this.element;
+		},
+
+		makeRequest: function(){
+			this.request = new Request.HTML($merge({
+					update: this.update,
+					emulation: false,
+					spinnerTarget: this.element,
+					method: this.element.get('method') || 'post'
+			}, this.options.requestOptions)).addEvents({
+				success: function(text, xml){
+					['complete', 'success'].each(function(evt){
+						this.fireEvent(evt, [this.update, text, xml]);
+					}, this);
+				}.bind(this),
+				failure: function(xhr){
+					this.fireEvent('complete').fireEvent('failure', xhr);
+				}.bind(this),
+				exception: function(){
+					this.fireEvent('failure', xhr);
+				}.bind(this)
+			});
+		},
+
+		attach: function(attach){
+			attach = $pick(attach, true);
+			method = attach ? 'addEvent' : 'removeEvent';
+			
+			var fv = this.element.retrieve('validator');
+			if (fv) fv[method]('onFormValidate', this.onFormValidate);
+			if (!fv || !attach) this.element[method]('submit', this.onSubmit);
+		},
+
+		detach: function(){
+			this.attach(false);
+		},
+
+		//public method
+		enable: function(){
+			this.attach();
+		},
+
+		//public method
+		disable: function(){
+			this.detach();
+		},
+
+		onFormValidate: function(valid, form, e) {
+			var fv = this.element.retrieve('validator');
+			if (valid || (fv && !fv.options.stopOnFailure)) {
+				if (e && e.stop) e.stop();
+				this.send();
+			}
+		},
+
+		onSubmit: function(e){
+			if (this.element.retrieve('validator')) {
+				//form validator was created after Form.Request
+				this.detach();
+				return;
+			}
+			e.stop();
+			this.send();
+		},
+
+		send: function(){
+			var str = this.element.toQueryString().trim();
+			var data = $H(this.options.extraData).toQueryString();
+			if (str) str += "&" + data;
+			else str = data;
+			this.fireEvent('send', [this.element, str.parseQueryString()]);
+			this.request.send({data: str, url: this.element.get("action")});
+			return this;
+		}
+
+	});
+
+	Element.Properties.formRequest = {
+
+		set: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			var updater = this.retrieve('form.request');
+			if (update) {
+				if (updater) updater.update = document.id(update);
+				this.store('form.request:update', update);
+			}
+			if (opt.options) {
+				if (updater) updater.setOptions(opt.options);
+				this.store('form.request:options', opt.options);
+			}
+			return this;
+		},
+
+		get: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			if (opt.options || update || !this.retrieve('form.request')){
+				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
+				if (update) this.set('form.request', update);
+				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
+			}
+			return this.retrieve('form.request');
+		}
+
+	};
+
+	Element.implement({
+
+		formUpdate: function(update, options){
+			this.get('form.request', update, options).send();
+			return this;
+		}
+
+	});
+
+})();/*
+---
+
+script: Fx.Reveal.js
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Morph
+ - /Element.Shortcuts
+ - /Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+Fx.Reveal = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {/*	  
+		onShow: $empty(thisElement),
+		onHide: $empty(thisElement),
+		onComplete: $empty(thisElement),
+		heightOverride: null,
+		widthOverride: null, */
+		link: 'cancel',
+		styles: ['padding', 'border', 'margin'],
+		transitionOpacity: !Browser.Engine.trident4,
+		mode: 'vertical',
+		display: 'block',
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
+	},
+
+	dissolve: function(){
+		try {
+			if (!this.hiding && !this.showing){
+				if (this.element.getStyle('display') != 'none'){
+					this.hiding = true;
+					this.showing = false;
+					this.hidden = true;
+					this.cssText = this.element.style.cssText;
+					var startStyles = this.element.getComputedSize({
+						styles: this.options.styles,
+						mode: this.options.mode
+					});
+					this.element.setStyle('display', this.options.display);
+					if (this.options.transitionOpacity) startStyles.opacity = 1;
+					var zero = {};
+					$each(startStyles, function(style, name){
+						zero[name] = [style, 0];
+					}, this);
+					this.element.setStyle('overflow', 'hidden');
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					this.$chain.unshift(function(){
+						if (this.hidden){
+							this.hiding = false;
+							$each(startStyles, function(style, name){
+								startStyles[name] = style;
+							}, this);
+							this.element.style.cssText = this.cssText;
+							this.element.setStyle('display', 'none');
+							if (hideThese) hideThese.setStyle('visibility', 'visible');
+						}
+						this.fireEvent('hide', this.element);
+						this.callChain();
+					}.bind(this));
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					this.start(zero);
+				} else {
+					this.callChain.delay(10, this);
+					this.fireEvent('complete', this.element);
+					this.fireEvent('hide', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.dissolve.bind(this));
+			} else if (this.options.link == 'cancel' && !this.hiding){
+				this.cancel();
+				this.dissolve();
+			}
+		} catch(e){
+			this.hiding = false;
+			this.element.setStyle('display', 'none');
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('hide', this.element);
+		}
+		return this;
+	},
+
+	reveal: function(){
+		try {
+			if (!this.showing && !this.hiding){
+				if (this.element.getStyle('display') == 'none' ||
+					 this.element.getStyle('visiblity') == 'hidden' ||
+					 this.element.getStyle('opacity') == 0){
+					this.showing = true;
+					this.hiding = this.hidden =  false;
+					var startStyles;
+					this.cssText = this.element.style.cssText;
+					//toggle display, but hide it
+					this.element.measure(function(){
+						//create the styles for the opened/visible state
+						startStyles = this.element.getComputedSize({
+							styles: this.options.styles,
+							mode: this.options.mode
+						});
+					}.bind(this));
+					$each(startStyles, function(style, name){
+						startStyles[name] = style;
+					});
+					//if we're overridding height/width
+					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
+					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
+					if (this.options.transitionOpacity) {
+						this.element.setStyle('opacity', 0);
+						startStyles.opacity = 1;
+					}
+					//create the zero state for the beginning of the transition
+					var zero = {
+						height: 0,
+						display: this.options.display
+					};
+					$each(startStyles, function(style, name){ zero[name] = 0; });
+					//set to zero
+					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
+					//hide inputs
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					//start the effect
+					this.start(startStyles);
+					this.$chain.unshift(function(){
+						this.element.style.cssText = this.cssText;
+						this.element.setStyle('display', this.options.display);
+						if (!this.hidden) this.showing = false;
+						if (hideThese) hideThese.setStyle('visibility', 'visible');
+						this.callChain();
+						this.fireEvent('show', this.element);
+					}.bind(this));
+				} else {
+					this.callChain();
+					this.fireEvent('complete', this.element);
+					this.fireEvent('show', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.reveal.bind(this));
+			} else if (this.options.link == 'cancel' && !this.showing){
+				this.cancel();
+				this.reveal();
+			}
+		} catch(e){
+			this.element.setStyles({
+				display: this.options.display,
+				visiblity: 'visible',
+				opacity: 1
+			});
+			this.showing = false;
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('show', this.element);
+		}
+		return this;
+	},
+
+	toggle: function(){
+		if (this.element.getStyle('display') == 'none' ||
+			 this.element.getStyle('visiblity') == 'hidden' ||
+			 this.element.getStyle('opacity') == 0){
+			this.reveal();
+		} else {
+			this.dissolve();
+		}
+		return this;
+	},
+
+	cancel: function(){
+		this.parent.apply(this, arguments);
+		this.element.style.cssText = this.cssText;
+		this.hidding = false;
+		this.showing = false;
+	}
+
+});
+
+Element.Properties.reveal = {
+
+	set: function(options){
+		var reveal = this.retrieve('reveal');
+		if (reveal) reveal.cancel();
+		return this.eliminate('reveal').store('reveal:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('reveal')){
+			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
+			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
+		}
+		return this.retrieve('reveal');
+	}
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+	reveal: function(options){
+		this.get('reveal', options).reveal();
+		return this;
+	},
+
+	dissolve: function(options){
+		this.get('reveal', options).dissolve();
+		return this;
+	},
+
+	nix: function(){
+		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
+		this.get('reveal', params.options).dissolve().chain(function(){
+			this[params.destroy ? 'destroy' : 'dispose']();
+		}.bind(this));
+		return this;
+	},
+
+	wink: function(){
+		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
+		var reveal = this.get('reveal', params.options);
+		reveal.reveal().chain(function(){
+			(function(){
+				reveal.dissolve();
+			}).delay(params.duration || 2000);
+		});
+	}
+
+
+});/*
+---
+
+script: Form.Request.Append.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Request
+ - /Fx.Reveal
+ - /Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+	Extends: Form.Request,
+
+	options: {
+		//onBeforeEffect: $empty,
+		useReveal: true,
+		revealOptions: {},
+		inject: 'bottom'
+	},
+
+	makeRequest: function(){
+		this.request = new Request.HTML($merge({
+				url: this.element.get('action'),
+				method: this.element.get('method') || 'post',
+				spinnerTarget: this.element
+			}, this.options.requestOptions, {
+				evalScripts: false
+			})
+		).addEvents({
+			success: function(tree, elements, html, javascript){
+				var container;
+				var kids = Elements.from(html);
+				if (kids.length == 1) {
+					container = kids[0];
+				} else {
+					 container = new Element('div', {
+						styles: {
+							display: 'none'
+						}
+					}).adopt(kids);
+				}
+				container.inject(this.update, this.options.inject);
+				if (this.options.requestOptions.evalScripts) $exec(javascript);
+				this.fireEvent('beforeEffect', container);
+				var finish = function(){
+					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
+				}.bind(this);
+				if (this.options.useReveal) {
+					container.get('reveal', this.options.revealOptions).chain(finish);
+					container.reveal();
+				} else {
+					finish();
+				}
+			}.bind(this),
+			failure: function(xhr){
+				this.fireEvent('failure', xhr);
+			}.bind(this)
+		});
+	}
+
+});/*
+---
+
+script: Form.Validator.English.js
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.English]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Form.Validator', {
+
+	required:'This field is required.',
+	minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
+	maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
+	integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+	numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+	digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+	alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
+	alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
+	dateSuchAs:'Please enter a valid date such as {date}',
+	dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+	email:'Please enter a valid email address. For example "fred at domain.com".',
+	url:'Please enter a valid URL such as http://www.google.com.',
+	currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
+	oneRequired:'Please enter something for at least one of these inputs.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Warning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'There can be no spaces in this input.',
+	reqChkByNode: 'No items are selected.',
+	requiredChk: 'This field is required.',
+	reqChkByName: 'Please select a {label}.',
+	match: 'This field needs to match the {matchName} field',
+	startDate: 'the start date',
+	endDate: 'the end date',
+	currendDate: 'the current date',
+	afterDate: 'The date should be the same or after {label}.',
+	beforeDate: 'The date should be the same or before {label}.',
+	startMonth: 'Please select a start month',
+	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+/*
+---
+
+script: Form.Validator.js
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Selectors
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/JSON
+ - /Lang
+ - /Class.Binds
+ - /Date Element.Forms
+ - /Form.Validator.English
+ - /Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = new Class({
+
+	Implements: [Options],
+
+	options: {
+		errorMsg: 'Validation failed.',
+		test: function(field){return true;}
+	},
+
+	initialize: function(className, options){
+		this.setOptions(options);
+		this.className = className;
+	},
+
+	test: function(field, props){
+		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
+		else return false;
+	},
+
+	getError: function(field, props){
+		var err = this.options.errorMsg;
+		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
+		return err;
+	},
+
+	getProps: function(field){
+		if (!document.id(field)) return {};
+		return field.get('validatorProps');
+	}
+
+});
+
+Element.Properties.validatorProps = {
+
+	set: function(props){
+		return this.eliminate('validatorProps').store('validatorProps', props);
+	},
+
+	get: function(props){
+		if (props) this.set(props);
+		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
+		if (this.getProperty('validatorProps')){
+			try {
+				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
+			}catch(e){
+				return {};
+			}
+		} else {
+			var vals = this.get('class').split(' ').filter(function(cls){
+				return cls.test(':');
+			});
+			if (!vals.length){
+				this.store('validatorProps', {});
+			} else {
+				props = {};
+				vals.each(function(cls){
+					var split = cls.split(':');
+					if (split[1]) {
+						try {
+							props[split[0]] = JSON.decode(split[1]);
+						} catch(e) {}
+					}
+				});
+				this.store('validatorProps', props);
+			}
+		}
+		return this.retrieve('validatorProps');
+	}
+
+};
+
+Form.Validator = new Class({
+
+	Implements:[Options, Events],
+
+	Binds: ['onSubmit'],
+
+	options: {/*
+		onFormValidate: $empty(isValid, form, event),
+		onElementValidate: $empty(isValid, field, className, warn),
+		onElementPass: $empty(field),
+		onElementFail: $empty(field, validatorsFailed) */
+		fieldSelectors: 'input, select, textarea',
+		ignoreHidden: true,
+		ignoreDisabled: true,
+		useTitles: false,
+		evaluateOnSubmit: true,
+		evaluateFieldsOnBlur: true,
+		evaluateFieldsOnChange: true,
+		serial: true,
+		stopOnFailure: true,
+		warningPrefix: function(){
+			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+		},
+		errorPrefix: function(){
+			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+		}
+	},
+
+	initialize: function(form, options){
+		this.setOptions(options);
+		this.element = document.id(form);
+		this.element.store('validator', this);
+		this.warningPrefix = $lambda(this.options.warningPrefix)();
+		this.errorPrefix = $lambda(this.options.errorPrefix)();
+		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
+		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	getFields: function(){
+		return (this.fields = this.element.getElements(this.options.fieldSelectors));
+	},
+
+	watchFields: function(fields){
+		fields.each(function(el){
+			if (this.options.evaluateFieldsOnBlur)
+				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
+			if (this.options.evaluateFieldsOnChange)
+				el.addEvent('change', this.validationMonitor.pass([el, true], this));
+		}, this);
+	},
+
+	validationMonitor: function(){
+		$clear(this.timer);
+		this.timer = this.validateField.delay(50, this, arguments);
+	},
+
+	onSubmit: function(event){
+		if (!this.validate(event) && event) event.preventDefault();
+		else this.reset();
+	},
+
+	reset: function(){
+		this.getFields().each(this.resetField, this);
+		return this;
+	},
+
+	validate: function(event){
+		var result = this.getFields().map(function(field){
+			return this.validateField(field, true);
+		}, this).every(function(v){ return v;});
+		this.fireEvent('formValidate', [result, this.element, event]);
+		if (this.options.stopOnFailure && !result && event) event.preventDefault();
+		return result;
+	},
+
+	validateField: function(field, force){
+		if (this.paused) return true;
+		field = document.id(field);
+		var passed = !field.hasClass('validation-failed');
+		var failed, warned;
+		if (this.options.serial && !force){
+			failed = this.element.getElement('.validation-failed');
+			warned = this.element.getElement('.warning');
+		}
+		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+			var validators = field.className.split(' ').some(function(cn){
+				return this.getValidator(cn);
+			}, this);
+			var validatorsFailed = [];
+			field.className.split(' ').each(function(className){
+				if (className && !this.test(className, field)) validatorsFailed.include(className);
+			}, this);
+			passed = validatorsFailed.length === 0;
+			if (validators && !field.hasClass('warnOnly')){
+				if (passed){
+					field.addClass('validation-passed').removeClass('validation-failed');
+					this.fireEvent('elementPass', field);
+				} else {
+					field.addClass('validation-failed').removeClass('validation-passed');
+					this.fireEvent('elementFail', [field, validatorsFailed]);
+				}
+			}
+			if (!warned){
+				var warnings = field.className.split(' ').some(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.getValidator(cn.replace(/^warn-/,''));
+					else return null;
+				}, this);
+				field.removeClass('warning');
+				var warnResult = field.className.split(' ').map(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.test(cn.replace(/^warn-/,''), field, true);
+					else return null;
+				}, this);
+			}
+		}
+		return passed;
+	},
+
+	test: function(className, field, warn){
+		field = document.id(field);
+		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+		var validator = this.getValidator(className);
+		if (field.hasClass('ignoreValidation')) return true;
+		warn = $pick(warn, false);
+		if (field.hasClass('warnOnly')) warn = true;
+		var isValid = validator ? validator.test(field) : true;
+		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+		if (warn) return true;
+		return isValid;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (field){
+			field.className.split(' ').each(function(className){
+				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+				field.removeClass('validation-failed');
+				field.removeClass('warning');
+				field.removeClass('validation-passed');
+			}, this);
+		}
+		return this;
+	},
+
+	stop: function(){
+		this.paused = true;
+		return this;
+	},
+
+	start: function(){
+		this.paused = false;
+		return this;
+	},
+
+	ignoreField: function(field, warn){
+		field = document.id(field);
+		if (field){
+			this.enforceField(field);
+			if (warn) field.addClass('warnOnly');
+			else field.addClass('ignoreValidation');
+		}
+		return this;
+	},
+
+	enforceField: function(field){
+		field = document.id(field);
+		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+		return this;
+	}
+
+});
+
+Form.Validator.getMsg = function(key){
+	return MooTools.lang.get('Form.Validator', key);
+};
+
+Form.Validator.adders = {
+
+	validators:{},
+
+	add : function(className, options){
+		this.validators[className] = new InputValidator(className, options);
+		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+		//extend these validators into it
+		//this allows validators to be global and/or per instance
+		if (!this.initialize){
+			this.implement({
+				validators: this.validators
+			});
+		}
+	},
+
+	addAllThese : function(validators){
+		$A(validators).each(function(validator){
+			this.add(validator[0], validator[1]);
+		}, this);
+	},
+
+	getValidator: function(className){
+		return this.validators[className.split(':')[0]];
+	}
+
+};
+
+$extend(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+	errorMsg: false,
+	test: function(element){
+		if (element.type == 'select-one' || element.type == 'select')
+			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+		else
+			return ((element.get('value') == null) || (element.get('value').length == 0));
+	}
+
+});
+
+Form.Validator.addAllThese([
+
+	['required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element){
+			return !Form.Validator.getValidator('IsEmpty').test(element);
+		}
+	}],
+
+	['minLength', {
+		errorMsg: function(element, props){
+			if ($type(props.minLength))
+				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
+			else return true;
+		}
+	}],
+
+	['maxLength', {
+		errorMsg: function(element, props){
+			//props is {maxLength:10}
+			if ($type(props.maxLength))
+				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			//if the value is <= than the maxLength value, element passes test
+			return (element.get('value').length <= $pick(props.maxLength, 10000));
+		}
+	}],
+
+	['validate-integer', {
+		errorMsg: Form.Validator.getMsg.pass('integer'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-numeric', {
+		errorMsg: Form.Validator.getMsg.pass('numeric'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||
+				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-digits', {
+		errorMsg: Form.Validator.getMsg.pass('digits'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+		}
+	}],
+
+	['validate-alpha', {
+		errorMsg: Form.Validator.getMsg.pass('alpha'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-alphanum', {
+		errorMsg: Form.Validator.getMsg.pass('alphanum'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+		}
+	}],
+
+	['validate-date', {
+		errorMsg: function(element, props){
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+			} else {
+				return Form.Validator.getMsg('dateInFormatMDY');
+			}
+		},
+		test: function(element, props){
+			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+			var d;
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				d = Date.parse(element.get('value'));
+				var formatted = d.format(format);
+				if (formatted != 'invalid date') element.set('value', formatted);
+				return !isNaN(d);
+			} else {
+				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
+				if (!regex.test(element.get('value'))) return false;
+				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
+				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
+					(parseInt(RegExp.$2, 10) == d.getDate()) &&
+					(parseInt(RegExp.$3, 10) == d.getFullYear());
+			}
+		}
+	}],
+
+	['validate-email', {
+		errorMsg: Form.Validator.getMsg.pass('email'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-url', {
+		errorMsg: Form.Validator.getMsg.pass('url'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-currency-dollar', {
+		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+		test: function(element){
+			// [$]1[##][,###]+[.##]
+			// [$]1###+[.##]
+			// [$]0.##
+			// [$].##
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-one-required', {
+		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+		test: function(element, props){
+			var p = document.id(props['validate-one-required']) || element.getParent();
+			return p.getElements('input').some(function(el){
+				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+				return el.get('value');
+			});
+		}
+	}]
+
+]);
+
+Element.Properties.validator = {
+
+	set: function(options){
+		var validator = this.retrieve('validator');
+		if (validator) validator.setOptions(options);
+		return this.store('validator:options');
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('validator')){
+			if (options || !this.retrieve('validator:options')) this.set('validator', options);
+			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
+		}
+		return this.retrieve('validator');
+	}
+
+};
+
+Element.implement({
+
+	validate: function(options){
+		this.set('validator', options);
+		return this.get('validator', options).validate();
+	}
+
+});
+//legacy
+var FormValidator = Form.Validator;/*
+---
+
+script: Form.Validator.Inline.js
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+	Extends: Form.Validator,
+
+	options: {
+		scrollToErrorsOnSubmit: true,
+		scrollFxOptions: {
+			transition: 'quad:out',
+			offset: {
+				y: -20
+			}
+		}
+	},
+
+	initialize: function(form, options){
+		this.parent(form, options);
+		this.addEvent('onElementValidate', function(isValid, field, className, warn){
+			var validator = this.getValidator(className);
+			if (!isValid && validator.getError(field)){
+				if (warn) field.addClass('warning');
+				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+				this.insertAdvice(advice, field);
+				this.showAdvice(className, field);
+			} else {
+				this.hideAdvice(className, field);
+			}
+		});
+	},
+
+	makeAdvice: function(className, field, error, warn){
+		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
+			errorMsg += (this.options.useTitles) ? field.title || error:error;
+		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+		var advice = this.getAdvice(className, field);
+		if(advice) {
+			advice = advice.set('html', errorMsg);
+		} else {
+			advice = new Element('div', {
+				html: errorMsg,
+				styles: { display: 'none' },
+				id: 'advice-' + className + '-' + this.getFieldId(field)
+			}).addClass(cssClass);
+		}
+		field.store('advice-' + className, advice);
+		return advice;
+	},
+
+	getFieldId : function(field){
+		return field.id ? field.id : field.id = 'input_' + field.name;
+	},
+
+	showAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && !field.retrieve(this.getPropName(className))
+				&& (advice.getStyle('display') == 'none'
+				|| advice.getStyle('visiblity') == 'hidden'
+				|| advice.getStyle('opacity') == 0)){
+			field.store(this.getPropName(className), true);
+			if (advice.reveal) advice.reveal();
+			else advice.setStyle('display', 'block');
+		}
+	},
+
+	hideAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && field.retrieve(this.getPropName(className))){
+			field.store(this.getPropName(className), false);
+			//if Fx.Reveal.js is present, transition the advice out
+			if (advice.dissolve) advice.dissolve();
+			else advice.setStyle('display', 'none');
+		}
+	},
+
+	getPropName: function(className){
+		return 'advice' + className;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (!field) return this;
+		this.parent(field);
+		field.className.split(' ').each(function(className){
+			this.hideAdvice(className, field);
+		}, this);
+		return this;
+	},
+
+	getAllAdviceMessages: function(field, force){
+		var advice = [];
+		if (field.hasClass('ignoreValidation') && !force) return advice;
+		var validators = field.className.split(' ').some(function(cn){
+			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+			if (warner) cn = cn.replace(/^warn-/, '');
+			var validator = this.getValidator(cn);
+			if (!validator) return;
+			advice.push({
+				message: validator.getError(field),
+				warnOnly: warner,
+				passed: validator.test(),
+				validator: validator
+			});
+		}, this);
+		return advice;
+	},
+
+	getAdvice: function(className, field){
+		return field.retrieve('advice-' + className);
+	},
+
+	insertAdvice: function(advice, field){
+		//Check for error position prop
+		var props = field.get('validatorProps');
+		//Build advice
+		if (!props.msgPos || !document.id(props.msgPos)){
+			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+			else advice.inject(document.id(field), 'after');
+		} else {
+			document.id(props.msgPos).grab(advice);
+		}
+	},
+
+	validateField: function(field, force){
+		var result = this.parent(field, force);
+		if (this.options.scrollToErrorsOnSubmit && !result){
+			var failed = document.id(this).getElement('.validation-failed');
+			var par = document.id(this).getParent();
+			while (par != document.body && par.getScrollSize().y == par.getSize().y){
+				par = par.getParent();
+			}
+			var fx = par.retrieve('fvScroller');
+			if (!fx && window.Fx && Fx.Scroll){
+				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+				par.store('fvScroller', fx);
+			}
+			if (failed){
+				if (fx) fx.toElement(failed);
+				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+			}
+		}
+		return result;
+	}
+
+});
+/*
+---
+
+script: Form.Validator.Extras.js
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+	['validate-enforce-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-ignore-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-nospace', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('noSpace');
+		},
+		test: function(element, props){
+			return !element.get('value').test(/\s/);
+		}
+	}],
+
+	['validate-toggle-oncheck', {
+		test: function(element, props){
+			var fv = element.getParent('form').retrieve('validator');
+			if (!fv) return true;
+			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+			if (!element.checked){
+				eleArr.each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			} else {
+				eleArr.each(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-reqchk-bynode', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('reqChkByNode');
+		},
+		test: function(element, props){
+			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+				return item.checked;
+			});
+		}
+	}],
+
+	['validate-required-check', {
+		errorMsg: function(element, props){
+			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+		},
+		test: function(element, props){
+			return !!element.checked;
+		}
+	}],
+
+	['validate-reqchk-byname', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+		},
+		test: function(element, props){
+			var grpName = props.groupName || element.get('name');
+			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+				return item.checked;
+			});
+			var fv = element.getParent('form').retrieve('validator');
+			if (oneCheckedItem && fv) fv.resetField(element);
+			return oneCheckedItem;
+		}
+	}],
+
+	['validate-match', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+		},
+		test: function(element, props){
+			var eleVal = element.get('value');
+			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+			return eleVal && matchVal ? eleVal == matchVal : true;
+		}
+	}],
+
+	['validate-after-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('afterDate').substitute({
+				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+			var end = Date.parse(element.get('value'));
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-before-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('beforeDate').substitute({
+				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = Date.parse(element.get('value'));
+			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-custom-required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element, props){
+			return element.get('value') != props.emptyValue;
+		}
+	}],
+
+	['validate-same-month', {
+		errorMsg: function(element, props){
+			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+			var eleVal = element.get('value');
+			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+		},
+		test: function(element, props){
+			var d1 = Date.parse(element.get('value'));
+			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+		}
+	}],
+
+
+	['validate-cc-num', {
+		errorMsg: function(element){
+			var ccNum = element.get('value').replace(/[^0-9]/g, '');
+			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+		},
+		test: function(element){
+			// required is a different test
+			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }
+
+			// Clean number value
+			var ccNum = element.get('value');
+			ccNum = ccNum.replace(/[^0-9]/g, '');
+
+			var valid_type = false;
+
+			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
+
+			if (valid_type) {
+				var sum = 0;
+				var cur = 0;
+
+				for(var i=ccNum.length-1; i>=0; --i) {
+					cur = ccNum.charAt(i).toInt();
+					if (cur == 0) { continue; }
+
+					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
+					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }
+
+					sum += cur;
+				}
+				if ((sum % 10) == 0) { return true; }
+			}
+
+			var chunks = '';
+			while (ccNum != '') {
+				chunks += ' ' + ccNum.substr(0,4);
+				ccNum = ccNum.substr(4);
+			}
+
+			element.getParent('form').retrieve('validator').ignoreField(element);
+			element.set('value', chunks.clean());
+			element.getParent('form').retrieve('validator').enforceField(element);
+			return false;
+		}
+	}]
+
+
+]);/*
+---
+
+script: OverText.js
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - /Class.Binds
+ - /Class.Occlude
+ - /Element.Position
+ - /Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+	options: {/*
+		textOverride: null,
+		onFocus: $empty()
+		onTextHide: $empty(textEl, inputEl),
+		onTextShow: $empty(textEl, inputEl), */
+		element: 'label',
+		positionOptions: {
+			position: 'upperLeft',
+			edge: 'upperLeft',
+			offset: {
+				x: 4,
+				y: 2
+			}
+		},
+		poll: false,
+		pollInterval: 250,
+		wrap: false
+	},
+
+	property: 'OverText',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.attach(this.element);
+		OverText.instances.push(this);
+		if (this.options.poll) this.poll();
+		return this;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	attach: function(){
+		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
+		if (!val) return;
+		this.text = new Element(this.options.element, {
+			'class': 'overTxtLabel',
+			styles: {
+				lineHeight: 'normal',
+				position: 'absolute',
+				cursor: 'text'
+			},
+			html: val,
+			events: {
+				click: this.hide.pass(this.options.element == 'label', this)
+			}
+		}).inject(this.element, 'after');
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+
+		if (this.options.wrap) {
+			this.textHolder = new Element('div', {
+				styles: {
+					lineHeight: 'normal',
+					position: 'relative'
+				},
+				'class':'overTxtWrapper'
+			}).adopt(this.text).inject(this.element, 'before');
+		}
+
+		this.element.addEvents({
+			focus: this.focus,
+			blur: this.assert,
+			change: this.assert
+		}).store('OverTextDiv', this.text);
+		window.addEvent('resize', this.reposition.bind(this));
+		this.assert(true);
+		this.reposition();
+	},
+
+	wrap: function(){
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+	},
+
+	startPolling: function(){
+		this.pollingPaused = false;
+		return this.poll();
+	},
+
+	poll: function(stop){
+		//start immediately
+		//pause on focus
+		//resumeon blur
+		if (this.poller && !stop) return this;
+		var test = function(){
+			if (!this.pollingPaused) this.assert(true);
+		}.bind(this);
+		if (stop) $clear(this.poller);
+		else this.poller = test.periodical(this.options.pollInterval, this);
+		return this;
+	},
+
+	stopPolling: function(){
+		this.pollingPaused = true;
+		return this.poll(true);
+	},
+
+	focus: function(){
+		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
+		this.hide();
+	},
+
+	hide: function(suppressFocus, force){
+		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+			this.text.hide();
+			this.fireEvent('textHide', [this.text, this.element]);
+			this.pollingPaused = true;
+			if (!suppressFocus){
+				try {
+					this.element.fireEvent('focus');
+					this.element.focus();
+				} catch(e){} //IE barfs if you call focus on hidden elements
+			}
+		}
+		return this;
+	},
+
+	show: function(){
+		if (this.text && !this.text.isDisplayed()){
+			this.text.show();
+			this.reposition();
+			this.fireEvent('textShow', [this.text, this.element]);
+			this.pollingPaused = false;
+		}
+		return this;
+	},
+
+	assert: function(suppressFocus){
+		this[this.test() ? 'show' : 'hide'](suppressFocus);
+	},
+
+	test: function(){
+		var v = this.element.get('value');
+		return !v;
+	},
+
+	reposition: function(){
+		this.assert(true);
+		if (!this.element.isVisible()) return this.stopPolling().hide();
+		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
+		return this;
+	}
+
+});
+
+OverText.instances = [];
+
+$extend(OverText, {
+
+	each: function(fn) {
+		return OverText.instances.map(function(ot, i){
+			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
+			return null; //the input or the text was destroyed
+		});
+	},
+	
+	update: function(){
+
+		return OverText.each(function(ot){
+			return ot.reposition();
+		});
+
+	},
+
+	hideAll: function(){
+
+		return OverText.each(function(ot){
+			return ot.hide(true, true);
+		});
+
+	},
+
+	showAll: function(){
+		return OverText.each(function(ot) {
+			return ot.show();
+		});
+	}
+
+});
+
+if (window.Fx && Fx.Reveal) {
+	Fx.Reveal.implement({
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
+	});
+}/*
+---
+
+script: Fx.Elements.js
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx.CSS
+ - /MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(elements, options){
+		this.elements = this.subject = $$(elements);
+		this.parent(options);
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var i in from){
+			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+		}
+		return now;
+	},
+
+	set: function(now){
+		for (var i in now){
+			var iNow = now[i];
+			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+		}
+		return this;
+	},
+
+	start: function(obj){
+		if (!this.check(obj)) return this;
+		var from = {}, to = {};
+		for (var i in obj){
+			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+			for (var p in iProps){
+				var parsed = this.prepare(this.elements[i], p, iProps[p]);
+				iFrom[p] = parsed.from;
+				iTo[p] = parsed.to;
+			}
+		}
+		return this.parent(from, to);
+	}
+
+});/*
+---
+
+script: Fx.Accordion.js
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Event
+ - /Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {/*
+		onActive: $empty(toggler, section),
+		onBackground: $empty(toggler, section),
+		fixedHeight: false,
+		fixedWidth: false,
+		*/
+		display: 0,
+		show: false,
+		height: true,
+		width: false,
+		opacity: true,
+		alwaysHide: false,
+		trigger: 'click',
+		initialDisplayFx: true,
+		returnHeightToAuto: true
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {
+			'container': Element.type, //deprecated
+			'options': Object.type,
+			'togglers': $defined,
+			'elements': $defined
+		});
+		this.parent(params.elements, params.options);
+		this.togglers = $$(params.togglers);
+		this.previous = -1;
+		this.internalChain = new Chain();
+		if (this.options.alwaysHide) this.options.wait = true;
+		if ($chk(this.options.show)){
+			this.options.display = false;
+			this.previous = this.options.show;
+		}
+		if (this.options.start){
+			this.options.display = false;
+			this.options.show = false;
+		}
+		this.effects = {};
+		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
+		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
+		this.elements.each(function(el, i){
+			if (this.options.show === i){
+				this.fireEvent('active', [this.togglers[i], el]);
+			} else {
+				for (var fx in this.effects) el.setStyle(fx, 0);
+			}
+		}, this);
+		if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
+		if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
+		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+	},
+
+	addSection: function(toggler, element){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		this.togglers.include(toggler);
+		this.elements.include(element);
+		var idx = this.togglers.indexOf(toggler);
+		var displayer = this.display.bind(this, idx);
+		toggler.store('accordion:display', displayer);
+		toggler.addEvent(this.options.trigger, displayer);
+		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+		element.fullOpacity = 1;
+		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
+		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
+		element.setStyle('overflow', 'hidden');
+		if (!test){
+			for (var fx in this.effects) element.setStyle(fx, 0);
+		}
+		return this;
+	},
+
+	detach: function(){
+		this.togglers.each(function(toggler) {
+			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+		}, this);
+	},
+
+	display: function(index, useFx){
+		if (!this.check(index, useFx)) return this;
+		useFx = $pick(useFx, true);
+		if (this.options.returnHeightToAuto){
+			var prev = this.elements[this.previous];
+			if (prev && !this.selfHidden){
+				for (var fx in this.effects){
+					prev.setStyle(fx, prev[this.effects[fx]]);
+				}
+			}
+		}
+		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
+		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
+		this.previous = index;
+		var obj = {};
+		this.elements.each(function(el, i){
+			obj[i] = {};
+			var hide;
+			if (i != index){
+				hide = true;
+			} else if (this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0 && this.options.width)){
+				hide = true;
+				this.selfHidden = true;
+			}
+			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
+		}, this);
+		this.internalChain.chain(function(){
+			if (this.options.returnHeightToAuto && !this.selfHidden){
+				var el = this.elements[index];
+				if (el) el.setStyle('height', 'auto');
+			};
+		}.bind(this));
+		return useFx ? this.start(obj) : this.set(obj);
+	}
+
+});
+
+/*
+	Compatibility with 1.2.0
+*/
+var Accordion = new Class({
+
+	Extends: Fx.Accordion,
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		var params = Array.link(arguments, {'container': Element.type});
+		this.container = params.container;
+	},
+
+	addSection: function(toggler, element, pos){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		var len = this.togglers.length;
+		if (len && (!test || pos)){
+			pos = $pick(pos, len - 1);
+			toggler.inject(this.togglers[pos], 'before');
+			element.inject(toggler, 'after');
+		} else if (this.container && !test){
+			toggler.inject(this.container);
+			element.inject(this.container);
+		}
+		return this.parent.apply(this, arguments);
+	}
+
+});/*
+---
+
+script: Fx.Move.js
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Morph
+ - /Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {
+		relativeTo: document.body,
+		position: 'center',
+		edge: false,
+		offset: {x: 0, y: 0}
+	},
+
+	start: function(destination){
+		return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
+	}
+
+});
+
+Element.Properties.move = {
+
+	set: function(options){
+		var morph = this.retrieve('move');
+		if (morph) morph.cancel();
+		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('move')){
+			if (options || !this.retrieve('move:options')) this.set('move', options);
+			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
+		}
+		return this.retrieve('move');
+	}
+
+};
+
+Element.implement({
+
+	move: function(options){
+		this.get('move').start(options);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Scroll.js
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+Fx.Scroll = new Class({
+
+	Extends: Fx,
+
+	options: {
+		offset: {x: 0, y: 0},
+		wheelStops: true
+	},
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var cancel = this.cancel.bind(this, false);
+
+		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+		var stopper = this.element;
+
+		if (this.options.wheelStops){
+			this.addEvent('start', function(){
+				stopper.addEvent('mousewheel', cancel);
+			}, true);
+			this.addEvent('complete', function(){
+				stopper.removeEvent('mousewheel', cancel);
+			}, true);
+		}
+	},
+
+	set: function(){
+		var now = Array.flatten(arguments);
+		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
+		this.element.scrollTo(now[0], now[1]);
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(x, y){
+		if (!this.check(x, y)) return this;
+		var scrollSize = this.element.getScrollSize(),
+			scroll = this.element.getScroll(), 
+			values = {x: x, y: y};
+		for (var z in values){
+			var max = scrollSize[z];
+			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
+			else values[z] = scroll[z];
+			values[z] += this.options.offset[z];
+		}
+		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
+	},
+
+	toTop: function(){
+		return this.start(false, 0);
+	},
+
+	toLeft: function(){
+		return this.start(0, false);
+	},
+
+	toRight: function(){
+		return this.start('right', false);
+	},
+
+	toBottom: function(){
+		return this.start(false, 'bottom');
+	},
+
+	toElement: function(el){
+		var position = document.id(el).getPosition(this.element);
+		return this.start(position.x, position.y);
+	},
+
+	scrollIntoView: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x','y'];
+		var to = {};
+		el = document.id(el);
+		var pos = el.getPosition(this.element);
+		var size = el.getSize();
+		var scroll = this.element.getScroll();
+		var containerSize = this.element.getSize();
+		var edge = {
+			x: pos.x + size.x,
+			y: pos.y + size.y
+		};
+		['x','y'].each(function(axis) {
+			if (axes.contains(axis)) {
+				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
+			}
+			if (to[axis] == null) to[axis] = scroll[axis];
+			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	},
+
+	scrollToCenter: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x', 'y'];
+		el = $(el);
+		var to = {},
+			pos = el.getPosition(this.element),
+			size = el.getSize(),
+			scroll = this.element.getScroll(),
+			containerSize = this.element.getSize(),
+			edge = {
+				x: pos.x + size.x,
+				y: pos.y + size.y
+			};
+
+		['x','y'].each(function(axis){
+			if(axes.contains(axis)){
+				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
+			}
+			if(to[axis] == null) to[axis] = scroll[axis];
+			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Slide.js
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx Element.Style
+ - /MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+	Extends: Fx,
+
+	options: {
+		mode: 'vertical',
+		wrapper: false,
+		hideOverflow: true
+	},
+
+	initialize: function(element, options){
+		this.addEvent('complete', function(){
+			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
+			if (this.open) this.wrapper.setStyle('height', '');
+			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
+		}, true);
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var wrapper = this.element.retrieve('wrapper');
+		var styles = this.element.getStyles('margin', 'position', 'overflow');
+		if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
+		if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
+		this.wrapper = wrapper || new Element('div', {
+			styles: styles
+		}).wraps(this.element);
+		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
+		this.now = [];
+		this.open = true;
+	},
+
+	vertical: function(){
+		this.margin = 'margin-top';
+		this.layout = 'height';
+		this.offset = this.element.offsetHeight;
+	},
+
+	horizontal: function(){
+		this.margin = 'margin-left';
+		this.layout = 'width';
+		this.offset = this.element.offsetWidth;
+	},
+
+	set: function(now){
+		this.element.setStyle(this.margin, now[0]);
+		this.wrapper.setStyle(this.layout, now[1]);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(how, mode){
+		if (!this.check(how, mode)) return this;
+		this[mode || this.options.mode]();
+		var margin = this.element.getStyle(this.margin).toInt();
+		var layout = this.wrapper.getStyle(this.layout).toInt();
+		var caseIn = [[margin, layout], [0, this.offset]];
+		var caseOut = [[margin, layout], [-this.offset, 0]];
+		var start;
+		switch (how){
+			case 'in': start = caseIn; break;
+			case 'out': start = caseOut; break;
+			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+		}
+		return this.parent(start[0], start[1]);
+	},
+
+	slideIn: function(mode){
+		return this.start('in', mode);
+	},
+
+	slideOut: function(mode){
+		return this.start('out', mode);
+	},
+
+	hide: function(mode){
+		this[mode || this.options.mode]();
+		this.open = false;
+		return this.set([-this.offset, 0]);
+	},
+
+	show: function(mode){
+		this[mode || this.options.mode]();
+		this.open = true;
+		return this.set([0, this.offset]);
+	},
+
+	toggle: function(mode){
+		return this.start('toggle', mode);
+	}
+
+});
+
+Element.Properties.slide = {
+
+	set: function(options){
+		var slide = this.retrieve('slide');
+		if (slide) slide.cancel();
+		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('slide')){
+			if (options || !this.retrieve('slide:options')) this.set('slide', options);
+			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
+		}
+		return this.retrieve('slide');
+	}
+
+};
+
+Element.implement({
+
+	slide: function(how, mode){
+		how = how || 'toggle';
+		var slide = this.get('slide'), toggle;
+		switch (how){
+			case 'hide': slide.hide(mode); break;
+			case 'show': slide.show(mode); break;
+			case 'toggle':
+				var flag = this.retrieve('slide:flag', slide.open);
+				slide[flag ? 'slideOut' : 'slideIn'](mode);
+				this.store('slide:flag', !flag);
+				toggle = true;
+			break;
+			default: slide.start(how, mode);
+		}
+		if (!toggle) this.eliminate('slide:flag');
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Selectors
+ - /Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+var SmoothScroll = Fx.SmoothScroll = new Class({
+
+	Extends: Fx.Scroll,
+
+	initialize: function(options, context){
+		context = context || document;
+		this.doc = context.getDocument();
+		var win = context.getWindow();
+		this.parent(this.doc, options);
+		this.links = $$(this.options.links || this.doc.links);
+		var location = win.location.href.match(/^[^#]*/)[0] + '#';
+		this.links.each(function(link){
+			if (link.href.indexOf(location) != 0) {return;}
+			var anchor = link.href.substr(location.length);
+			if (anchor) this.useLink(link, anchor);
+		}, this);
+		if (!Browser.Engine.webkit419) {
+			this.addEvent('complete', function(){
+				win.location.hash = this.anchor;
+			}, true);
+		}
+	},
+
+	useLink: function(link, anchor){
+		var el;
+		link.addEvent('click', function(event){
+			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+			if (el) {
+				event.preventDefault();
+				this.anchor = anchor;
+				this.toElement(el).chain(function(){
+					this.fireEvent('scrolledTo', [link, el]);
+				}.bind(this));
+				link.blur();
+			}
+		}.bind(this));
+	}
+});/*
+---
+
+script: Fx.Sort.js
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Fx.Elements
+ - /Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {
+		mode: 'vertical'
+	},
+
+	initialize: function(elements, options){
+		this.parent(elements, options);
+		this.elements.each(function(el){
+			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+		});
+		this.setDefaultOrder();
+	},
+
+	setDefaultOrder: function(){
+		this.currentOrder = this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	sort: function(newOrder){
+		if ($type(newOrder) != 'array') return false;
+		var top = 0,
+			left = 0,
+			next = {},
+			zero = {},
+			vert = this.options.mode == 'vertical';
+		var current = this.elements.map(function(el, index){
+			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+			var val;
+			if (vert){
+				val = {
+					top: top,
+					margin: size['margin-top'],
+					height: size.totalHeight
+				};
+				top += val.height - size['margin-top'];
+			} else {
+				val = {
+					left: left,
+					margin: size['margin-left'],
+					width: size.totalWidth
+				};
+				left += val.width;
+			}
+			var plain = vert ? 'top' : 'left';
+			zero[index] = {};
+			var start = el.getStyle(plain).toInt();
+			zero[index][plain] = start || 0;
+			return val;
+		}, this);
+		this.set(zero);
+		newOrder = newOrder.map(function(i){ return i.toInt(); });
+		if (newOrder.length != this.elements.length){
+			this.currentOrder.each(function(index){
+				if (!newOrder.contains(index)) newOrder.push(index);
+			});
+			if (newOrder.length > this.elements.length)
+				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+		}
+		var margin = top = left = 0;
+		newOrder.each(function(item, index){
+			var newPos = {};
+			if (vert){
+				newPos.top = top - current[item].top - margin;
+				top += current[item].height;
+			} else {
+				newPos.left = left - current[item].left;
+				left += current[item].width;
+			}
+			margin = margin + current[item].margin;
+			next[item]=newPos;
+		}, this);
+		var mapped = {};
+		$A(newOrder).sort().each(function(index){
+			mapped[index] = next[index];
+		});
+		this.start(mapped);
+		this.currentOrder = newOrder;
+		return this;
+	},
+
+	rearrangeDOM: function(newOrder){
+		newOrder = newOrder || this.currentOrder;
+		var parent = this.elements[0].getParent();
+		var rearranged = [];
+		this.elements.setStyle('opacity', 0);
+		//move each element and store the new default order
+		newOrder.each(function(index){
+			rearranged.push(this.elements[index].inject(parent).setStyles({
+				top: 0,
+				left: 0
+			}));
+		}, this);
+		this.elements.setStyle('opacity', 1);
+		this.elements = $$(rearranged);
+		this.setDefaultOrder();
+		return this;
+	},
+
+	getDefaultOrder: function(){
+		return this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	forward: function(){
+		return this.sort(this.getDefaultOrder());
+	},
+
+	backward: function(){
+		return this.sort(this.getDefaultOrder().reverse());
+	},
+
+	reverse: function(){
+		return this.sort(this.currentOrder.reverse());
+	},
+
+	sortByElements: function(elements){
+		return this.sort(elements.map(function(el){
+			return this.elements.indexOf(el);
+		}, this));
+	},
+
+	swap: function(one, two){
+		if ($type(one) == 'element') one = this.elements.indexOf(one);
+		if ($type(two) == 'element') two = this.elements.indexOf(two);
+		
+		var newOrder = $A(this.currentOrder);
+		newOrder[this.currentOrder.indexOf(one)] = two;
+		newOrder[this.currentOrder.indexOf(two)] = one;
+		return this.sort(newOrder);
+	}
+
+});/*
+---
+
+script: Drag.js
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Drag]
+
+...
+*/
+
+var Drag = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onBeforeStart: $empty(thisElement),
+		onStart: $empty(thisElement, event),
+		onSnap: $empty(thisElement)
+		onDrag: $empty(thisElement, event),
+		onCancel: $empty(thisElement),
+		onComplete: $empty(thisElement, event),*/
+		snap: 6,
+		unit: 'px',
+		grid: false,
+		style: true,
+		limit: false,
+		handle: false,
+		invert: false,
+		preventDefault: false,
+		stopPropagation: false,
+		modifiers: {x: 'left', y: 'top'}
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
+		this.element = document.id(params.element);
+		this.document = this.element.getDocument();
+		this.setOptions(params.options || {});
+		var htype = $type(this.options.handle);
+		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+		this.mouse = {'now': {}, 'pos': {}};
+		this.value = {'start': {}, 'now': {}};
+
+		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
+
+		this.bound = {
+			start: this.start.bind(this),
+			check: this.check.bind(this),
+			drag: this.drag.bind(this),
+			stop: this.stop.bind(this),
+			cancel: this.cancel.bind(this),
+			eventStop: $lambda(false)
+		};
+		this.attach();
+	},
+
+	attach: function(){
+		this.handles.addEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	detach: function(){
+		this.handles.removeEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	start: function(event){
+		if (event.rightClick) return;
+		if (this.options.preventDefault) event.preventDefault();
+		if (this.options.stopPropagation) event.stopPropagation();
+		this.mouse.start = event.page;
+		this.fireEvent('beforeStart', this.element);
+		var limit = this.options.limit;
+		this.limit = {x: [], y: []};
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
+			else this.value.now[z] = this.element[this.options.modifiers[z]];
+			if (this.options.invert) this.value.now[z] *= -1;
+			this.mouse.pos[z] = event.page[z] - this.value.now[z];
+			if (limit && limit[z]){
+				for (var i = 2; i--; i){
+					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
+				}
+			}
+		}
+		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
+		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
+		this.document.addEvent(this.selection, this.bound.eventStop);
+	},
+
+	check: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+		if (distance > this.options.snap){
+			this.cancel();
+			this.document.addEvents({
+				mousemove: this.bound.drag,
+				mouseup: this.bound.stop
+			});
+			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+		}
+	},
+
+	drag: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		this.mouse.now = event.page;
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+			if (this.options.invert) this.value.now[z] *= -1;
+			if (this.options.limit && this.limit[z]){
+				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
+					this.value.now[z] = this.limit[z][1];
+				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
+					this.value.now[z] = this.limit[z][0];
+				}
+			}
+			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
+			if (this.options.style) {
+				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
+			} else {
+				this.element[this.options.modifiers[z]] = this.value.now[z];
+			}
+		}
+		this.fireEvent('drag', [this.element, event]);
+	},
+
+	cancel: function(event){
+		this.document.removeEvent('mousemove', this.bound.check);
+		this.document.removeEvent('mouseup', this.bound.cancel);
+		if (event){
+			this.document.removeEvent(this.selection, this.bound.eventStop);
+			this.fireEvent('cancel', this.element);
+		}
+	},
+
+	stop: function(event){
+		this.document.removeEvent(this.selection, this.bound.eventStop);
+		this.document.removeEvent('mousemove', this.bound.drag);
+		this.document.removeEvent('mouseup', this.bound.stop);
+		if (event) this.fireEvent('complete', [this.element, event]);
+	}
+
+});
+
+Element.implement({
+
+	makeResizable: function(options){
+		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
+		this.store('resizer', drag);
+		return drag.addEvent('drag', function(){
+			this.fireEvent('resize', drag);
+		}.bind(this));
+	}
+
+});
+/*
+---
+
+script: Drag.Move.js
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+	Extends: Drag,
+
+	options: {/*
+		onEnter: $empty(thisElement, overed),
+		onLeave: $empty(thisElement, overed),
+		onDrop: $empty(thisElement, overed, event),*/
+		droppables: [],
+		container: false,
+		precalculate: false,
+		includeMargins: true,
+		checkDroppables: true
+	},
+
+	initialize: function(element, options){
+		this.parent(element, options);
+		element = this.element;
+		
+		this.droppables = $$(this.options.droppables);
+		this.container = document.id(this.options.container);
+		
+		if (this.container && $type(this.container) != 'element')
+			this.container = document.id(this.container.getDocument().body);
+		
+		var styles = element.getStyles('left', 'top', 'position');
+		if (styles.left == 'auto' || styles.top == 'auto')
+			element.setPosition(element.getPosition(element.getOffsetParent()));
+		
+		if (styles.position == 'static')
+			element.setStyle('position', 'absolute');
+
+		this.addEvent('start', this.checkDroppables, true);
+
+		this.overed = null;
+	},
+
+	start: function(event){
+		if (this.container) this.options.limit = this.calculateLimit();
+		
+		if (this.options.precalculate){
+			this.positions = this.droppables.map(function(el){
+				return el.getCoordinates();
+			});
+		}
+		
+		this.parent(event);
+	},
+	
+	calculateLimit: function(){
+		var offsetParent = this.element.getOffsetParent(),
+			containerCoordinates = this.container.getCoordinates(offsetParent),
+			containerBorder = {},
+			elementMargin = {},
+			elementBorder = {},
+			containerMargin = {},
+			offsetParentPadding = {};
+
+		['top', 'right', 'bottom', 'left'].each(function(pad){
+			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
+			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
+			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
+			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
+			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+		}, this);
+
+		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
+			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
+			left = 0,
+			top = 0,
+			right = containerCoordinates.right - containerBorder.right - width,
+			bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+		if (this.options.includeMargins){
+			left += elementMargin.left;
+			top += elementMargin.top;
+		} else {
+			right += elementMargin.right;
+			bottom += elementMargin.bottom;
+		}
+		
+		if (this.element.getStyle('position') == 'relative'){
+			var coords = this.element.getCoordinates(offsetParent);
+			coords.left -= this.element.getStyle('left').toInt();
+			coords.top -= this.element.getStyle('top').toInt();
+			
+			left += containerBorder.left - coords.left;
+			top += containerBorder.top - coords.top;
+			right += elementMargin.left - coords.left;
+			bottom += elementMargin.top - coords.top;
+			
+			if (this.container != offsetParent){
+				left += containerMargin.left + offsetParentPadding.left;
+				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
+			}
+		} else {
+			left -= elementMargin.left;
+			top -= elementMargin.top;
+			
+			if (this.container == offsetParent){
+				right -= containerBorder.left;
+				bottom -= containerBorder.top;
+			} else {
+				left += containerCoordinates.left + containerBorder.left;
+				top += containerCoordinates.top + containerBorder.top;
+			}
+		}
+		
+		return {
+			x: [left, right],
+			y: [top, bottom]
+		};
+	},
+
+	checkAgainst: function(el, i){
+		el = (this.positions) ? this.positions[i] : el.getCoordinates();
+		var now = this.mouse.now;
+		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+	},
+
+	checkDroppables: function(){
+		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
+		if (this.overed != overed){
+			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+			if (overed) this.fireEvent('enter', [this.element, overed]);
+			this.overed = overed;
+		}
+	},
+
+	drag: function(event){
+		this.parent(event);
+		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+	},
+
+	stop: function(event){
+		this.checkDroppables();
+		this.fireEvent('drop', [this.element, this.overed, event]);
+		this.overed = null;
+		return this.parent(event);
+	}
+
+});
+
+Element.implement({
+
+	makeDraggable: function(options){
+		var drag = new Drag.Move(this, options);
+		this.store('dragger', drag);
+		return drag;
+	}
+
+});
+/*
+---
+
+script: Slider.js
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Class.Binds
+ - /Drag
+ - /Element.Dimensions
+ - /Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+	Implements: [Events, Options],
+
+	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+	options: {/*
+		onTick: $empty(intPosition),
+		onChange: $empty(intStep),
+		onComplete: $empty(strStep),*/
+		onTick: function(position){
+			if (this.options.snap) position = this.toPosition(this.step);
+			this.knob.setStyle(this.property, position);
+		},
+		initialStep: 0,
+		snap: false,
+		offset: 0,
+		range: false,
+		wheel: false,
+		steps: 100,
+		mode: 'horizontal'
+	},
+
+	initialize: function(element, knob, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.knob = document.id(knob);
+		this.previousChange = this.previousEnd = this.step = -1;
+		var offset, limit = {}, modifiers = {'x': false, 'y': false};
+		switch (this.options.mode){
+			case 'vertical':
+				this.axis = 'y';
+				this.property = 'top';
+				offset = 'offsetHeight';
+				break;
+			case 'horizontal':
+				this.axis = 'x';
+				this.property = 'left';
+				offset = 'offsetWidth';
+		}
+		
+		this.full = this.element.measure(function(){ 
+			this.half = this.knob[offset] / 2; 
+			return this.element[offset] - this.knob[offset] + (this.options.offset * 2); 
+		}.bind(this));
+		
+		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
+		this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
+		this.range = this.max - this.min;
+		this.steps = this.options.steps || this.full;
+		this.stepSize = Math.abs(this.range) / this.steps;
+		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
+
+		this.knob.setStyle('position', 'relative').setStyle(this.property, this.options.initialStep ? this.toPosition(this.options.initialStep) : - this.options.offset);
+		modifiers[this.axis] = this.property;
+		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];
+
+		var dragOptions = {
+			snap: 0,
+			limit: limit,
+			modifiers: modifiers,
+			onDrag: this.draggedKnob,
+			onStart: this.draggedKnob,
+			onBeforeStart: (function(){
+				this.isDragging = true;
+			}).bind(this),
+			onCancel: function() {
+				this.isDragging = false;
+			}.bind(this),
+			onComplete: function(){
+				this.isDragging = false;
+				this.draggedKnob();
+				this.end();
+			}.bind(this)
+		};
+		if (this.options.snap){
+			dragOptions.grid = Math.ceil(this.stepWidth);
+			dragOptions.limit[this.axis][1] = this.full;
+		}
+
+		this.drag = new Drag(this.knob, dragOptions);
+		this.attach();
+	},
+
+	attach: function(){
+		this.element.addEvent('mousedown', this.clickedElement);
+		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+		this.drag.attach();
+		return this;
+	},
+
+	detach: function(){
+		this.element.removeEvent('mousedown', this.clickedElement);
+		this.element.removeEvent('mousewheel', this.scrolledElement);
+		this.drag.detach();
+		return this;
+	},
+
+	set: function(step){
+		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+		if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+		this.step = Math.round(step);
+		this.checkStep();
+		this.fireEvent('tick', this.toPosition(this.step));
+		this.end();
+		return this;
+	},
+
+	clickedElement: function(event){
+		if (this.isDragging || event.target == this.knob) return;
+
+		var dir = this.range < 0 ? -1 : 1;
+		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+		this.fireEvent('tick', position);
+		this.end();
+	},
+
+	scrolledElement: function(event){
+		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
+		event.stop();
+	},
+
+	draggedKnob: function(){
+		var dir = this.range < 0 ? -1 : 1;
+		var position = this.drag.value.now[this.axis];
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+	},
+
+	checkStep: function(){
+		if (this.previousChange != this.step){
+			this.previousChange = this.step;
+			this.fireEvent('change', this.step);
+		}
+	},
+
+	end: function(){
+		if (this.previousEnd !== this.step){
+			this.previousEnd = this.step;
+			this.fireEvent('complete', this.step + '');
+		}
+	},
+
+	toStep: function(position){
+		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
+	},
+
+	toPosition: function(step){
+		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
+	}
+
+});/*
+---
+
+script: Sortables.js
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - /Drag.Move
+
+provides: [Slider]
+
+...
+*/
+
+var Sortables = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onSort: $empty(element, clone),
+		onStart: $empty(element, clone),
+		onComplete: $empty(element),*/
+		snap: 4,
+		opacity: 1,
+		clone: false,
+		revert: false,
+		handle: false,
+		constrain: false
+	},
+
+	initialize: function(lists, options){
+		this.setOptions(options);
+		this.elements = [];
+		this.lists = [];
+		this.idle = true;
+
+		this.addLists($$(document.id(lists) || lists));
+		if (!this.options.clone) this.options.revert = false;
+		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
+	},
+
+	attach: function(){
+		this.addLists(this.lists);
+		return this;
+	},
+
+	detach: function(){
+		this.lists = this.removeLists(this.lists);
+		return this;
+	},
+
+	addItems: function(){
+		Array.flatten(arguments).each(function(element){
+			this.elements.push(element);
+			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+		}, this);
+		return this;
+	},
+
+	addLists: function(){
+		Array.flatten(arguments).each(function(list){
+			this.lists.push(list);
+			this.addItems(list.getChildren());
+		}, this);
+		return this;
+	},
+
+	removeItems: function(){
+		return $$(Array.flatten(arguments).map(function(element){
+			this.elements.erase(element);
+			var start = element.retrieve('sortables:start');
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+			
+			return element;
+		}, this));
+	},
+
+	removeLists: function(){
+		return $$(Array.flatten(arguments).map(function(list){
+			this.lists.erase(list);
+			this.removeItems(list.getChildren());
+			
+			return list;
+		}, this));
+	},
+
+	getClone: function(event, element){
+		if (!this.options.clone) return new Element('div').inject(document.body);
+		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+		var clone = element.clone(true).setStyles({
+			margin: '0px',
+			position: 'absolute',
+			visibility: 'hidden',
+			'width': element.getStyle('width')
+		});
+		//prevent the duplicated radio inputs from unchecking the real one
+		if (clone.get('html').test('radio')) {
+			clone.getElements('input[type=radio]').each(function(input, i) {
+				input.set('name', 'clone_' + i);
+			});
+		}
+		
+		return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
+	},
+
+	getDroppables: function(){
+		var droppables = this.list.getChildren();
+		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
+		return droppables.erase(this.clone).erase(this.element);
+	},
+
+	insert: function(dragging, element){
+		var where = 'inside';
+		if (this.lists.contains(element)){
+			this.list = element;
+			this.drag.droppables = this.getDroppables();
+		} else {
+			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+		}
+		this.element.inject(element, where);
+		this.fireEvent('sort', [this.element, this.clone]);
+	},
+
+	start: function(event, element){
+		if (!this.idle) return;
+		this.idle = false;
+		this.element = element;
+		this.opacity = element.get('opacity');
+		this.list = element.getParent();
+		this.clone = this.getClone(event, element);
+
+		this.drag = new Drag.Move(this.clone, {
+			snap: this.options.snap,
+			container: this.options.constrain && this.element.getParent(),
+			droppables: this.getDroppables(),
+			onSnap: function(){
+				event.stop();
+				this.clone.setStyle('visibility', 'visible');
+				this.element.set('opacity', this.options.opacity || 0);
+				this.fireEvent('start', [this.element, this.clone]);
+			}.bind(this),
+			onEnter: this.insert.bind(this),
+			onCancel: this.reset.bind(this),
+			onComplete: this.end.bind(this)
+		});
+
+		this.clone.inject(this.element, 'before');
+		this.drag.start(event);
+	},
+
+	end: function(){
+		this.drag.detach();
+		this.element.set('opacity', this.opacity);
+		if (this.effect){
+			var dim = this.element.getStyles('width', 'height');
+			var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
+			this.effect.element = this.clone;
+			this.effect.start({
+				top: pos.top,
+				left: pos.left,
+				width: dim.width,
+				height: dim.height,
+				opacity: 0.25
+			}).chain(this.reset.bind(this));
+		} else {
+			this.reset();
+		}
+	},
+
+	reset: function(){
+		this.idle = true;
+		this.clone.destroy();
+		this.fireEvent('complete', this.element);
+	},
+
+	serialize: function(){
+		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
+		var serial = this.lists.map(function(list){
+			return list.getChildren().map(params.modifier || function(element){
+				return element.get('id');
+			}, this);
+		}, this);
+
+		var index = params.index;
+		if (this.lists.length == 1) index = 0;
+		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+	}
+
+});
+/*
+---
+
+script: Request.JSONP.js
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+
+requires:
+ - core:1.2.4/Element
+ - core:1.2.4/Request
+ - /Log
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+	Implements: [Chain, Events, Options, Log],
+
+	options: {/*
+		onRetry: $empty(intRetries),
+		onRequest: $empty(scriptElement),
+		onComplete: $empty(data),
+		onSuccess: $empty(data),
+		onCancel: $empty(),
+		log: false,
+		*/
+		url: '',
+		data: {},
+		retries: 0,
+		timeout: 0,
+		link: 'ignore',
+		callbackKey: 'callback',
+		injectScript: document.head
+	},
+
+	initialize: function(options){
+		this.setOptions(options);
+		if (this.options.log) this.enableLog();
+		this.running = false;
+		this.requests = 0;
+		this.triesRemaining = [];
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!$chk(arguments[1]) && !this.check(options)) return this;
+
+		var type = $type(options), 
+				old = this.options, 
+				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		options = $extend({data: old.data, url: old.url}, options);
+
+		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
+		var remaining = this.triesRemaining[index];
+
+		(function(){
+			var script = this.getScript(options);
+			this.log('JSONP retrieving script with url: ' + script.get('src'));
+			this.fireEvent('request', script);
+			this.running = true;
+
+			(function(){
+				if (remaining){
+					this.triesRemaining[index] = remaining - 1;
+					if (script){
+						script.destroy();
+						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
+					}
+				} else if(script && this.options.timeout){
+					script.destroy();
+					this.cancel().fireEvent('failure');
+				}
+			}).delay(this.options.timeout, this);
+		}).delay(Browser.Engine.trident ? 50 : 0, this);
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.fireEvent('cancel');
+		return this;
+	},
+
+	getScript: function(options){
+		var index = Request.JSONP.counter,
+				data;
+		Request.JSONP.counter++;
+
+		switch ($type(options.data)){
+			case 'element': data = document.id(options.data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(options.data);
+		}
+
+		var src = options.url + 
+			 (options.url.test('\\?') ? '&' :'?') + 
+			 (options.callbackKey || this.options.callbackKey) + 
+			 '=Request.JSONP.request_map.request_'+ index + 
+			 (data ? '&' + data : '');
+		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+
+		var script = new Element('script', {type: 'text/javascript', src: src});
+		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
+		return script.inject(this.options.injectScript);
+	},
+
+	success: function(args, script){
+		if (script) script.destroy();
+		this.running = false;
+		this.log('JSONP successfully retrieved: ', args);
+		this.fireEvent('complete', args).fireEvent('success', args).callChain();
+	}
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};/*
+---
+
+script: Request.Queue.js
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - core:1.2.4/Request
+ - /Log
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+	options: {/*
+		onRequest: $empty(argsPassedToOnRequest),
+		onSuccess: $empty(argsPassedToOnSuccess),
+		onComplete: $empty(argsPassedToOnComplete),
+		onCancel: $empty(argsPassedToOnCancel),
+		onException: $empty(argsPassedToOnException),
+		onFailure: $empty(argsPassedToOnFailure),
+		onEnd: $empty,
+		*/
+		stopOnFailure: true,
+		autoAdvance: true,
+		concurrent: 1,
+		requests: {}
+	},
+
+	initialize: function(options){
+		if(options){
+			var requests = options.requests;
+			delete options.requests;	
+		}
+		this.setOptions(options);
+		this.requests = new Hash;
+		this.queue = [];
+		this.reqBinders = {};
+		
+		if(requests) this.addRequests(requests);
+	},
+
+	addRequest: function(name, request){
+		this.requests.set(name, request);
+		this.attach(name, request);
+		return this;
+	},
+
+	addRequests: function(obj){
+		$each(obj, function(req, name){
+			this.addRequest(name, req);
+		}, this);
+		return this;
+	},
+
+	getName: function(req){
+		return this.requests.keyOf(req);
+	},
+
+	attach: function(name, req){
+		if (req._groupSend) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			if(!this.reqBinders[name]) this.reqBinders[name] = {};
+			this.reqBinders[name][evt] = function(){
+				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
+			}.bind(this);
+			req.addEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req._groupSend = req.send;
+		req.send = function(options){
+			this.send(name, options);
+			return req;
+		}.bind(this);
+		return this;
+	},
+
+	removeRequest: function(req){
+		var name = $type(req) == 'object' ? this.getName(req) : req;
+		if (!name && $type(name) != 'string') return this;
+		req = this.requests.get(name);
+		if (!req) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			req.removeEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req.send = req._groupSend;
+		delete req._groupSend;
+		return this;
+	},
+
+	getRunning: function(){
+		return this.requests.filter(function(r){
+			return r.running;
+		});
+	},
+
+	isRunning: function(){
+		return !!(this.getRunning().getKeys().length);
+	},
+
+	send: function(name, options){
+		var q = function(){
+			this.requests.get(name)._groupSend(options);
+			this.queue.erase(q);
+		}.bind(this);
+		q.name = name;
+		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+		else q();
+		return this;
+	},
+
+	hasNext: function(name){
+		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+	},
+
+	resume: function(){
+		this.error = false;
+		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
+		return this;
+	},
+
+	runNext: function(name){
+		if (!this.queue.length) return this;
+		if (!name){
+			this.queue[0]();
+		} else {
+			var found;
+			this.queue.each(function(q){
+				if (!found && q.name == name){
+					found = true;
+					q();
+				}
+			});
+		}
+		return this;
+	},
+
+	runAll: function() {
+		this.queue.each(function(q) {
+			q();
+		});
+		return this;
+	},
+
+	clear: function(name){
+		if (!name){
+			this.queue.empty();
+		} else {
+			this.queue = this.queue.map(function(q){
+				if (q.name != name) return q;
+				else return false;
+			}).filter(function(q){ return q; });
+		}
+		return this;
+	},
+
+	cancel: function(name){
+		this.requests.get(name).cancel();
+		return this;
+	},
+
+	onRequest: function(){
+		this.fireEvent('request', arguments);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', arguments);
+		if (!this.queue.length) this.fireEvent('end');
+	},
+
+	onCancel: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('cancel', arguments);
+	},
+
+	onSuccess: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('success', arguments);
+	},
+
+	onFailure: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('failure', arguments);
+	},
+
+	onException: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('exception', arguments);
+	}
+
+});
+/*
+---
+
+script: Request.Periodical.js
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Request
+ - /MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+	options: {
+		initialDelay: 5000,
+		delay: 5000,
+		limit: 60000
+	},
+
+	startTimer: function(data){
+		var fn = function(){
+			if (!this.running) this.send({data: data});
+		};
+		this.timer = fn.delay(this.options.initialDelay, this);
+		this.lastDelay = this.options.initialDelay;
+		this.completeCheck = function(response){
+			$clear(this.timer);
+			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+			this.timer = fn.delay(this.lastDelay, this);
+		};
+		return this.addEvent('complete', this.completeCheck);
+	},
+
+	stopTimer: function(){
+		$clear(this.timer);
+		return this.removeEvent('complete', this.completeCheck);
+	}
+
+});/*
+---
+
+script: Assets.js
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Event
+ - /MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+	javascript: function(source, properties){
+		properties = $extend({
+			onload: $empty,
+			document: document,
+			check: $lambda(true)
+		}, properties);
+		
+		if (properties.onLoad) properties.onload = properties.onLoad;
+		
+		var script = new Element('script', {src: source, type: 'text/javascript'});
+
+		var load = properties.onload.bind(script), 
+			check = properties.check, 
+			doc = properties.document;
+		delete properties.onload;
+		delete properties.check;
+		delete properties.document;
+
+		script.addEvents({
+			load: load,
+			readystatechange: function(){
+				if (['loaded', 'complete'].contains(this.readyState)) load();
+			}
+		}).set(properties);
+
+		if (Browser.Engine.webkit419) var checker = (function(){
+			if (!$try(check)) return;
+			$clear(checker);
+			load();
+		}).periodical(50);
+
+		return script.inject(doc.head);
+	},
+
+	css: function(source, properties){
+		return new Element('link', $merge({
+			rel: 'stylesheet',
+			media: 'screen',
+			type: 'text/css',
+			href: source
+		}, properties)).inject(document.head);
+	},
+
+	image: function(source, properties){
+		properties = $merge({
+			onload: $empty,
+			onabort: $empty,
+			onerror: $empty
+		}, properties);
+		var image = new Image();
+		var element = document.id(image) || new Element('img');
+		['load', 'abort', 'error'].each(function(name){
+			var type = 'on' + name;
+			var cap = name.capitalize();
+			if (properties['on' + cap]) properties[type] = properties['on' + cap];
+			var event = properties[type];
+			delete properties[type];
+			image[type] = function(){
+				if (!image) return;
+				if (!element.parentNode){
+					element.width = image.width;
+					element.height = image.height;
+				}
+				image = image.onload = image.onabort = image.onerror = null;
+				event.delay(1, element, element);
+				element.fireEvent(name, element, 1);
+			};
+		});
+		image.src = element.src = source;
+		if (image && image.complete) image.onload.delay(1);
+		return element.set(properties);
+	},
+
+	images: function(sources, options){
+		options = $merge({
+			onComplete: $empty,
+			onProgress: $empty,
+			onError: $empty,
+			properties: {}
+		}, options);
+		sources = $splat(sources);
+		var images = [];
+		var counter = 0;
+		return new Elements(sources.map(function(source){
+			return Asset.image(source, $extend(options.properties, {
+				onload: function(){
+					options.onProgress.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				},
+				onerror: function(){
+					options.onError.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				}
+			}));
+		}));
+	}
+
+};/*
+---
+
+script: Color.js
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - core:1.2.4/Number
+ - core:1.2.4/Hash
+ - core:1.2.4/Function
+ - core:1.2.4/$util
+
+provides: [Color]
+
+...
+*/
+
+var Color = new Native({
+
+	initialize: function(color, type){
+		if (arguments.length >= 3){
+			type = 'rgb'; color = Array.slice(arguments, 0, 3);
+		} else if (typeof color == 'string'){
+			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+			else if (color.match(/hsb/)) color = color.hsbToRgb();
+			else color = color.hexToRgb(true);
+		}
+		type = type || 'rgb';
+		switch (type){
+			case 'hsb':
+				var old = color;
+				color = color.hsbToRgb();
+				color.hsb = old;
+			break;
+			case 'hex': color = color.hexToRgb(true); break;
+		}
+		color.rgb = color.slice(0, 3);
+		color.hsb = color.hsb || color.rgbToHsb();
+		color.hex = color.rgbToHex();
+		return $extend(color, this);
+	}
+
+});
+
+Color.implement({
+
+	mix: function(){
+		var colors = Array.slice(arguments);
+		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
+		var rgb = this.slice();
+		colors.each(function(color){
+			color = new Color(color);
+			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+		});
+		return new Color(rgb, 'rgb');
+	},
+
+	invert: function(){
+		return new Color(this.map(function(value){
+			return 255 - value;
+		}));
+	},
+
+	setHue: function(value){
+		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+	},
+
+	setSaturation: function(percent){
+		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+	},
+
+	setBrightness: function(percent){
+		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+	}
+
+});
+
+var $RGB = function(r, g, b){
+	return new Color([r, g, b], 'rgb');
+};
+
+var $HSB = function(h, s, b){
+	return new Color([h, s, b], 'hsb');
+};
+
+var $HEX = function(hex){
+	return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+	rgbToHsb: function(){
+		var red = this[0],
+				green = this[1],
+				blue = this[2],
+				hue = 0;
+		var max = Math.max(red, green, blue),
+				min = Math.min(red, green, blue);
+		var delta = max - min;
+		var brightness = max / 255,
+				saturation = (max != 0) ? delta / max : 0;
+		if(saturation != 0) {
+			var rr = (max - red) / delta;
+			var gr = (max - green) / delta;
+			var br = (max - blue) / delta;
+			if (red == max) hue = br - gr;
+			else if (green == max) hue = 2 + rr - br;
+			else hue = 4 + gr - rr;
+			hue /= 6;
+			if (hue < 0) hue++;
+		}
+		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+	},
+
+	hsbToRgb: function(){
+		var br = Math.round(this[2] / 100 * 255);
+		if (this[1] == 0){
+			return [br, br, br];
+		} else {
+			var hue = this[0] % 360;
+			var f = hue % 60;
+			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+			switch (Math.floor(hue / 60)){
+				case 0: return [br, t, p];
+				case 1: return [q, br, p];
+				case 2: return [p, br, t];
+				case 3: return [p, q, br];
+				case 4: return [t, p, br];
+				case 5: return [br, p, q];
+			}
+		}
+		return false;
+	}
+
+});
+
+String.implement({
+
+	rgbToHsb: function(){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHsb() : null;
+	},
+
+	hsbToRgb: function(){
+		var hsb = this.match(/\d{1,3}/g);
+		return (hsb) ? hsb.hsbToRgb() : null;
+	}
+
+});
+/*
+---
+
+script: Group.js
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Events
+ - /MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+var Group = new Class({
+
+	initialize: function(){
+		this.instances = Array.flatten(arguments);
+		this.events = {};
+		this.checker = {};
+	},
+
+	addEvent: function(type, fn){
+		this.checker[type] = this.checker[type] || {};
+		this.events[type] = this.events[type] || [];
+		if (this.events[type].contains(fn)) return false;
+		else this.events[type].push(fn);
+		this.instances.each(function(instance, i){
+			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
+		}, this);
+		return this;
+	},
+
+	check: function(type, instance, i){
+		this.checker[type][i] = true;
+		var every = this.instances.every(function(current, j){
+			return this.checker[type][j] || false;
+		}, this);
+		if (!every) return;
+		this.checker[type] = {};
+		this.events[type].each(function(event){
+			event.call(this, this.instances, instance);
+		}, this);
+	}
+
+});
+/*
+---
+
+script: Hash.Cookie.js
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Cookie
+ - core:1.2.4/JSON
+ - /MooTools.More
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+	Extends: Cookie,
+
+	options: {
+		autoSave: true
+	},
+
+	initialize: function(name, options){
+		this.parent(name, options);
+		this.load();
+	},
+
+	save: function(){
+		var value = JSON.encode(this.hash);
+		if (!value || value.length > 4096) return false; //cookie would be truncated!
+		if (value == '{}') this.dispose();
+		else this.write(value);
+		return true;
+	},
+
+	load: function(){
+		this.hash = new Hash(JSON.decode(this.read(), true));
+		return this;
+	}
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+		var value = method.apply(this.hash, arguments);
+		if (this.options.autoSave) this.save();
+		return value;
+	});
+});/*
+---
+
+script: HtmlTable.js
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - /Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		properties: {
+			cellpadding: 0,
+			cellspacing: 0,
+			border: 0
+		},
+		rows: [],
+		headers: [],
+		footers: []
+	},
+
+	property: 'HtmlTable',
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, table: Element.type});
+		this.setOptions(params.options);
+		this.element = params.table || new Element('table', this.options.properties);
+		if (this.occlude()) return this.occluded;
+		this.build();
+	},
+
+	build: function(){
+		this.element.store('HtmlTable', this);
+
+		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+		$$(this.body.rows);
+
+		if (this.options.headers.length) this.setHeaders(this.options.headers);
+		else this.thead = document.id(this.element.tHead);
+		if (this.thead) this.head = document.id(this.thead.rows[0]);
+
+		if (this.options.footers.length) this.setFooters(this.options.footers);
+		this.tfoot = document.id(this.element.tFoot);
+		if (this.tfoot) this.foot = document.id(this.thead.rows[0]);
+
+		this.options.rows.each(function(row){
+			this.push(row);
+		}, this);
+
+		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+				this[method] = this.element[method].bind(this.element);
+		}, this);
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	empty: function(){
+		this.body.empty();
+		return this;
+	},
+
+	set: function(what, items) {
+		var target = (what == 'headers') ? 'tHead' : 'tFoot';
+		this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
+		var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
+		if (what == 'headers') this.head = document.id(this.thead.rows[0]);
+		else this.foot = document.id(this.thead.rows[0]);
+		return data;
+	},
+
+	setHeaders: function(headers){
+		this.set('headers', headers);
+		return this;
+	},
+
+	setFooters: function(footers){
+		this.set('footers', footers);
+		return this;
+	},
+
+	push: function(row, rowProperties, target, tag){
+		var tds = row.map(function(data){
+			var td = new Element(tag || 'td', data.properties),
+				type = data.content || data || '',
+				element = document.id(type);
+			if($type(type) != 'string' && element) td.adopt(element);
+			else td.set('html', type);
+
+			return td;
+		});
+
+		return {
+			tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
+			tds: tds
+		};
+	}
+
+});
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - /HtmlTable
+ - /Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		classZebra: 'table-tr-odd',
+		zebra: true
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		if (this.options.zebra) this.updateZebras();
+	},
+
+	updateZebras: function(){
+		Array.each(this.body.rows, this.zebra, this);
+	},
+
+	zebra: function(row, i){
+		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+	},
+
+	push: function(){
+		var pushed = this.previous.apply(this, arguments);
+		if (this.options.zebra) this.updateZebras();
+		return pushed;
+	}
+
+});/*
+---
+
+script: HtmlTable.Sort.js
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Hash
+ - /HtmlTable
+ - /Class.refactor
+ - /Element.Delegation
+ - /Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {/*
+		onSort: $empty, */
+		sortIndex: 0,
+		sortReverse: false,
+		parsers: [],
+		defaultParser: 'string',
+		classSortable: 'table-sortable',
+		classHeadSort: 'table-th-sort',
+		classHeadSortRev: 'table-th-sort-rev',
+		classNoSort: 'table-th-nosort',
+		classGroupHead: 'table-tr-group-head',
+		classGroup: 'table-tr-group',
+		classCellSort: 'table-td-sort',
+		classSortSpan: 'table-th-sort-span',
+		sortable: false
+	},
+
+	initialize: function () {
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.sorted = {index: null, dir: 1};
+		this.bound = {
+			headClick: this.headClick.bind(this)
+		};
+		this.sortSpans = new Elements();
+		if (this.options.sortable) {
+			this.enableSort();
+			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+		}
+	},
+
+	attachSorts: function(attach){
+		this.element.removeEvents('click:relay(th)');
+		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
+	},
+
+	setHeaders: function(){
+		this.previous.apply(this, arguments);
+		if (this.sortEnabled) this.detectParsers();
+	},
+	
+	detectParsers: function(force){
+		if (!this.head) return;
+		var parsers = this.options.parsers, 
+				rows = this.body.rows;
+
+		// auto-detect
+		this.parsers = $$(this.head.cells).map(function(cell, index) {
+			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
+			var thDiv = new Element('div');
+			$each(cell.childNodes, function(node) {
+				thDiv.adopt(node);
+			});
+			thDiv.inject(cell);
+			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(thDiv, 'top');
+			
+			this.sortSpans.push(sortSpan);
+
+			var parser = parsers[index], 
+					cancel;
+			switch ($type(parser)) {
+				case 'function': parser = {convert: parser}; cancel = true; break;
+				case 'string': parser = parser; cancel = true; break;
+			}
+			if (!cancel) {
+				HtmlTable.Parsers.some(function(current) {
+					var match = current.match;
+					if (!match) return false;
+					for (var i = 0, j = rows.length; i < j; i++) {
+						var text = $(rows[i].cells[index]).get('html').clean();
+						if (text && match.test(text)) {
+							parser = current;
+							return true;
+						}
+					}
+				});
+			}
+
+			if (!parser) parser = this.options.defaultParser;
+			cell.store('htmltable-parser', parser);
+			return parser;
+		}, this);
+	},
+
+	headClick: function(event, el) {
+		console.log(el);
+		if (!this.head || el.hasClass(this.options.classNoSort)) return;
+		var index = Array.indexOf(this.head.cells, el);
+		this.sort(index);
+		return false;
+	},
+
+	sort: function(index, reverse, pre) {
+		if (!this.head) return;
+		pre = !!(pre);
+		var classCellSort = this.options.classCellSort;
+		var classGroup = this.options.classGroup, 
+				classGroupHead = this.options.classGroupHead;
+
+		if (!pre) {
+			if (index != null) {
+				if (this.sorted.index == index) {
+					this.sorted.reverse = !(this.sorted.reverse);
+				} else {
+					if (this.sorted.index != null) {
+						this.sorted.reverse = false;
+						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+					} else {
+						this.sorted.reverse = true;
+					}
+					this.sorted.index = index;
+				}
+			} else {
+				index = this.sorted.index;
+			}
+
+			if (reverse != null) this.sorted.reverse = reverse;
+
+			var head = document.id(this.head.cells[index]);
+			if (head) {
+				head.addClass(this.options.classHeadSort);
+				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+				else head.removeClass(this.options.classHeadSortRev);
+			}
+
+			this.body.getElements('td').removeClass(this.options.classCellSort);
+		}
+
+		var parser = this.parsers[index];
+		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
+		if (!parser) return;
+
+		if (!Browser.Engine.trident) {
+			var rel = this.body.getParent();
+			this.body.dispose();
+		}
+
+		var data = Array.map(this.body.rows, function(row, i) {
+			var value = parser.convert.call(document.id(row.cells[index]));
+
+			return {
+				position: i,
+				value: value,
+				toString:  function() {
+					return value.toString();
+				}
+			};
+		}, this);
+		data.reverse(true);
+
+		data.sort(function(a, b){
+			if (a.value === b.value) return 0;
+			return a.value > b.value ? 1 : -1;
+		});
+
+		if (!this.sorted.reverse) data.reverse(true);
+
+		var i = data.length, body = this.body;
+		var j, position, entry, group;
+
+		while (i) {
+			var item = data[--i];
+			position = item.position;
+			var row = body.rows[position];
+			if (row.disabled) continue;
+
+			if (!pre) {
+				if (group === item.value) {
+					row.removeClass(classGroupHead).addClass(classGroup);
+				} else {
+					group = item.value;
+					row.removeClass(classGroup).addClass(classGroupHead);
+				}
+				if (this.zebra) this.zebra(row, i);
+
+				row.cells[index].addClass(classCellSort);
+			}
+
+			body.appendChild(row);
+			for (j = 0; j < i; j++) {
+				if (data[j].position > position) data[j].position--;
+			}
+		};
+		data = null;
+		if (rel) rel.grab(body);
+
+		return this.fireEvent('sort', [body, index]);
+	},
+
+	reSort: function(){
+		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+		return this;
+	},
+
+	enableSort: function(){
+		this.element.addClass(this.options.classSortable);
+		this.attachSorts(true);
+		this.detectParsers();
+		this.sortEnabled = true;
+		return this;
+	},
+
+	disableSort: function(){
+		this.element.removeClass(this.options.classSortable);
+		this.attachSorts(false);
+		this.sortSpans.each(function(span) { span.destroy(); });
+		this.sortSpans.empty();
+		this.sortEnabled = false;
+		return this;
+	}
+
+});
+
+HtmlTable.Parsers = new Hash({
+
+	'date': {
+		match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+		convert: function() {
+			return Date.parse(this.get('text')).format('db');
+		},
+		type: 'date'
+	},
+	'input-checked': {
+		match: / type="(radio|checkbox)" /,
+		convert: function() {
+			return this.getElement('input').checked;
+		}
+	},
+	'input-value': {
+		match: /<input/,
+		convert: function() {
+			return this.getElement('input').value;
+		}
+	},
+	'number': {
+		match: /^\d+[^\d.,]*$/,
+		convert: function() {
+			return this.get('text').toInt();
+		},
+		number: true
+	},
+	'numberLax': {
+		match: /^[^\d]+\d+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^0-9]/, '').toInt();
+		},
+		number: true
+	},
+	'float': {
+		match: /^[\d]+\.[\d]+/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '').toFloat();
+		},
+		number: true
+	},
+	'floatLax': {
+		match: /^[^\d]+[\d]+\.[\d]+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '');
+		},
+		number: true
+	},
+	'string': {
+		match: null,
+		convert: function() {
+			return this.get('text');
+		}
+	},
+	'title': {
+		match: null,
+		convert: function() {
+			return this.title;
+		}
+	}
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - /Log
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+	
+	var Keyboard = this.Keyboard = new Class({
+
+		Extends: Events,
+
+		Implements: [Options, Log],
+
+		options: {
+			/*
+			onActivate: $empty,
+			onDeactivate: $empty,
+			*/
+			defaultEventType: 'keydown',
+			active: false,
+			events: {},
+			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this.setup();
+		}, 
+		setup: function(){
+			this.addEvents(this.options.events);
+			//if this is the root manager, nothing manages it
+			if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
+			if (this.options.active) this.activate();
+		},
+
+		handle: function(event, type){
+			//Keyboard.stop(event) prevents key propagation
+			if (event.preventKeyboardPropagation) return;
+			
+			var bubbles = !!this.manager;
+			if (bubbles && this.activeKB){
+				this.activeKB.handle(event, type);
+				if (event.preventKeyboardPropagation) return;
+			}
+			this.fireEvent(type, event);
+			
+			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
+		},
+
+		addEvent: function(type, fn, internal){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+		},
+
+		removeEvent: function(type, fn){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+		},
+
+		toggleActive: function(){
+			return this[this.active ? 'deactivate' : 'activate']();
+		},
+
+		activate: function(instance){
+			if (instance) {
+				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
+				if (instance != this.activeKB) this.previous = this.activeKB;
+				//if we're enabling a child, assign it so that events are now passed to it
+				this.activeKB = instance.fireEvent('activate');
+				Keyboard.manager.fireEvent('changed');
+			} else if (this.manager) {
+				//else we're enabling ourselves, we must ask our parent to do it for us
+				this.manager.activate(this);
+			}
+			return this;
+		},
+
+		deactivate: function(instance){
+			if (instance) {
+				if(instance === this.activeKB) {
+					this.activeKB = null;
+					instance.fireEvent('deactivate');
+					Keyboard.manager.fireEvent('changed');
+				}
+			}
+			else if (this.manager) {
+				this.manager.deactivate(this);
+			}
+			return this;
+		},
+
+		relenquish: function(){
+			if (this.previous) this.activate(this.previous);
+		},
+
+		//management logic
+		manage: function(instance){
+			if (instance.manager) instance.manager.drop(instance);
+			this.instances.push(instance);
+			instance.manager = this;
+			if (!this.activeKB) this.activate(instance);
+			else this._disable(instance);
+		},
+
+		_disable: function(instance){
+			if (this.activeKB == instance) this.activeKB = null;
+		},
+
+		drop: function(instance){
+			this._disable(instance);
+			this.instances.erase(instance);
+		},
+
+		instances: [],
+
+		trace: function(){
+			Keyboard.trace(this);
+		},
+
+		each: function(fn){
+			Keyboard.each(this, fn);
+		}
+
+	});
+	
+	var parsed = {};
+	var modifiers = ['shift', 'control', 'alt', 'meta'];
+	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+	
+	Keyboard.parse = function(type, eventType, ignore){
+		if (ignore && ignore.contains(type.toLowerCase())) return type;
+		
+		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+			eventType = $1;
+			return '';
+		});
+
+		if (!parsed[type]){
+			var key, mods = {};
+			type.split('+').each(function(part){
+				if (regex.test(part)) mods[part] = true;
+				else key = part;
+			});
+
+			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+			
+			var keys = [];
+			modifiers.each(function(mod){
+				if (mods[mod]) keys.push(mod);
+			});
+			
+			if (key) keys.push(key);
+			parsed[type] = keys.join('+');
+		}
+
+		return eventType + ':' + parsed[type];
+	};
+
+	Keyboard.each = function(keyboard, fn){
+		var current = keyboard || Keyboard.manager;
+		while (current){
+			fn.run(current);
+			current = current.activeKB;
+		}
+	};
+
+	Keyboard.stop = function(event){
+		event.preventKeyboardPropagation = true;
+	};
+
+	Keyboard.manager = new Keyboard({
+		active: true
+	});
+	
+	Keyboard.trace = function(keyboard){
+		keyboard = keyboard || Keyboard.manager;
+		keyboard.enableLog();
+		keyboard.log('the following items have focus: ');
+		Keyboard.each(keyboard, function(current){
+			keyboard.log(document.id(current.widget) || current.wiget || current);
+		});
+	};
+	
+	var handler = function(event){
+		var keys = [];
+		modifiers.each(function(mod){
+			if (event[mod]) keys.push(mod);
+		});
+		
+		if (!regex.test(event.key)) keys.push(event.key);
+		Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
+	};
+	
+	document.addEvents({
+		'keyup': handler,
+		'keydown': handler
+	});
+
+	Event.Keys.extend({
+		'shift': 16,
+		'control': 17,
+		'alt': 18,
+		'capslock': 20,
+		'pageup': 33,
+		'pagedown': 34,
+		'end': 35,
+		'home': 36,
+		'numlock': 144,
+		'scrolllock': 145,
+		';': 186,
+		'=': 187,
+		',': 188,
+		'-': Browser.Engine.Gecko ? 109 : 189,
+		'.': 190,
+		'/': 191,
+		'`': 192,
+		'[': 219,
+		'\\': 220,
+		']': 221,
+		"'": 222
+	});
+
+})();
+/*
+---
+
+script: HtmlTable.Select.js
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - /Keyboard
+ - /HtmlTable
+ - /Class.refactor
+ - /Element.Delegation
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		/*onRowFocus: $empty,
+		onRowUnfocus: $empty,*/
+		useKeyboard: true,
+		classRowSelected: 'table-tr-selected',
+		classRowHovered: 'table-tr-hovered',
+		classSelectable: 'table-selectable',
+		allowMultiSelect: true,
+		selectable: false
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.selectedRows = new Elements();
+		this.bound = {
+			mouseleave: this.mouseleave.bind(this),
+			focusRow: this.focusRow.bind(this)
+		};
+		if (this.options.selectable) this.enableSelect();
+	},
+
+	enableSelect: function(){
+		this.selectEnabled = true;
+		this.attachSelects();
+		this.element.addClass(this.options.classSelectable);
+	},
+
+	disableSelect: function(){
+		this.selectEnabled = false;
+		this.attach(false);
+		this.element.removeClass(this.options.classSelectable);
+	},
+
+	attachSelects: function(attach){
+		attach = $pick(attach, true);
+		var method = attach ? 'addEvents' : 'removeEvents';
+		this.element[method]({
+			mouseleave: this.bound.mouseleave
+		});
+		this.body[method]({
+			'click:relay(tr)': this.bound.focusRow
+		});
+		if (this.options.useKeyboard || this.keyboard){
+			if (!this.keyboard) this.keyboard = new Keyboard({
+				events: {
+					down: function(e) {
+						e.preventDefault();
+						this.shiftFocus(1);
+					}.bind(this),
+					up: function(e) {
+						e.preventDefault();
+						this.shiftFocus(-1);
+					}.bind(this),
+					enter: function(e) {
+						e.preventDefault();
+						if (this.hover) this.focusRow(this.hover);
+					}.bind(this)
+				},
+				active: true
+			});
+			this.keyboard[attach ? 'activate' : 'deactivate']();
+		}
+		this.updateSelects();
+	},
+
+	mouseleave: function(){
+		if (this.hover) this.leaveRow(this.hover);
+	},
+
+	focus: function(){
+		if (this.keyboard) this.keyboard.activate();
+	},
+
+	blur: function(){
+		if (this.keyboard) this.keyboard.deactivate();
+	},
+
+	push: function(){
+		var ret = this.previous.apply(this, arguments);
+		this.updateSelects();
+		return ret;
+	},
+
+	updateSelects: function(){
+		Array.each(this.body.rows, function(row){
+			var binders = row.retrieve('binders');
+			if ((binders && this.selectEnabled) || (!binders && !this.selectEnabled)) return;
+			if (!binders){
+				binders = {
+					mouseenter: this.enterRow.bind(this, [row]),
+					mouseleave: this.leaveRow.bind(this, [row])
+				};
+				row.store('binders', binders).addEvents(binders);
+			} else {
+				row.removeEvents(binders);
+			}
+		}, this);
+	},
+
+	enterRow: function(row){
+		if (this.hover) this.hover = this.leaveRow(this.hover);
+		this.hover = row.addClass(this.options.classRowHovered);
+	},
+
+	shiftFocus: function(offset){
+		if (!this.hover) return this.enterRow(this.body.rows[0]);
+		var to = Array.indexOf(this.body.rows, this.hover) + offset;
+		if (to < 0) to = 0;
+		if (to >= this.body.rows.length) to = this.body.rows.length - 1;
+		if (this.hover == this.body.rows[to]) return this;
+		this.enterRow(this.body.rows[to]);
+	},
+
+	leaveRow: function(row){
+		row.removeClass(this.options.classRowHovered);
+	},
+
+	focusRow: function(){
+		var row = arguments[1] || arguments[0]; //delegation passes the event first
+		if (!this.body.getChildren().contains(row)) return;
+		var unfocus = function(row){
+			this.selectedRows.erase(row);
+			row.removeClass(this.options.classRowSelected);
+			this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+		}.bind(this);
+		if (!this.options.allowMultiSelect) this.selectedRows.each(unfocus);
+		if (!this.selectedRows.contains(row)) {
+			this.selectedRows.push(row);
+			row.addClass(this.options.classRowSelected);
+			this.fireEvent('rowFocus', [row, this.selectedRows]);
+		} else {
+			unfocus(row);
+		}
+		return false;
+	},
+
+	selectAll: function(status){
+		status = $pick(status, true);
+		if (!this.options.allowMultiSelect && status) return;
+		if (!status) this.selectedRows.removeClass(this.options.classRowSelected).empty();
+		else this.selectedRows.combine(this.body.rows).addClass(this.options.classRowSelected);
+		return this;
+	},
+
+	selectNone: function(){
+		return this.selectAll(false);
+	}
+
+});
+/*
+---
+
+script: Keyboard.js
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - core:1.2.4/Function
+ - /Keyboard.Extras
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+	/*
+		shortcut should be in the format of:
+		{
+			'keys': 'shift+s', // the default to add as an event.
+			'description': 'blah blah blah', // a brief description of the functionality.
+			'handler': function(){} // the event handler to run when keys are pressed.
+		}
+	*/
+	addShortcut: function(name, shortcut) {
+		this.shortcuts = this.shortcuts || [];
+		this.shortcutIndex = this.shortcutIndex || {};
+		
+		shortcut.getKeyboard = $lambda(this);
+		shortcut.name = name;
+		this.shortcutIndex[name] = shortcut;
+		this.shortcuts.push(shortcut);
+		if(shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+		return this;
+	},
+
+	addShortcuts: function(obj){
+		for(var name in obj) this.addShortcut(name, obj[name]);
+		return this;
+	},
+
+	getShortcuts: function(){
+		return this.shortcuts || [];
+	},
+
+	getShortcut: function(name){
+		return (this.shortcutIndex || {})[name];
+	}
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+	$splat(shortcuts).each(function(shortcut){
+		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+		shortcut.keys = newKeys;
+		shortcut.getKeyboard().fireEvent('rebound');
+	});
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard) {
+	var activeKBS = [], activeSCS = [];
+	Keyboard.each(keyboard, [].push.bind(activeKBS));
+	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+	return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+	opts = opts || {};
+	var shortcuts = opts.many ? [] : null,
+		set = opts.many ? function(kb){
+				var shortcut = kb.getShortcut(name);
+				if(shortcut) shortcuts.push(shortcut);
+			} : function(kb) { 
+				if(!shortcuts) shortcuts = kb.getShortcut(name);
+			};
+	Keyboard.each(keyboard, set);
+	return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard) {
+	return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+/*
+---
+
+script: Scroller.js
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		area: 20,
+		velocity: 1,
+		onChange: function(x, y){
+			this.element.scrollTo(x, y);
+		},
+		fps: 50
+	},
+
+	initialize: function(element, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.docBody = document.id(this.element.getDocument().body);
+		this.listener = ($type(this.element) != 'element') ?  this.docBody : this.element;
+		this.timer = null;
+		this.bound = {
+			attach: this.attach.bind(this),
+			detach: this.detach.bind(this),
+			getCoords: this.getCoords.bind(this)
+		};
+	},
+
+	start: function(){
+		this.listener.addEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+	},
+
+	stop: function(){
+		this.listener.removeEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+		this.detach();
+		this.timer = $clear(this.timer);
+	},
+
+	attach: function(){
+		this.listener.addEvent('mousemove', this.bound.getCoords);
+	},
+
+	detach: function(){
+		this.listener.removeEvent('mousemove', this.bound.getCoords);
+		this.timer = $clear(this.timer);
+	},
+
+	getCoords: function(event){
+		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+	},
+
+	scroll: function(){
+		var size = this.element.getSize(), 
+			scroll = this.element.getScroll(), 
+			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0}, 
+			scrollSize = this.element.getScrollSize(), 
+			change = {x: 0, y: 0};
+		for (var z in this.page){
+			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0) {
+				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
+			} else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
+				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
+			}
+		}
+		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+	}
+
+});/*
+---
+
+script: Tips.js
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		/*
+		onAttach: $empty(element),
+		onDetach: $empty(element),
+		*/
+		onShow: function(){
+			this.tip.setStyle('display', 'block');
+		},
+		onHide: function(){
+			this.tip.setStyle('display', 'none');
+		},
+		title: 'title',
+		text: function(element){
+			return element.get('rel') || element.get('href');
+		},
+		showDelay: 100,
+		hideDelay: 100,
+		className: 'tip-wrap',
+		offset: {x: 16, y: 16},
+		windowPadding: {x:0, y:0},
+		fixed: false
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, elements: $defined});
+		this.setOptions(params.options);
+		if (params.elements) this.attach(params.elements);
+		this.container = new Element('div', {'class': 'tip'});
+	},
+
+	toElement: function(){
+		if (this.tip) return this.tip;
+
+		return this.tip = new Element('div', {
+			'class': this.options.className,
+			styles: {
+				position: 'absolute',
+				top: 0,
+				left: 0
+			}
+		}).adopt(
+			new Element('div', {'class': 'tip-top'}),
+			this.container,
+			new Element('div', {'class': 'tip-bottom'})
+		).inject(document.body);
+	},
+
+	attach: function(elements){
+		$$(elements).each(function(element){
+			var title = read(this.options.title, element),
+				text = read(this.options.text, element);
+			
+			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
+			element.retrieve('tip:text', text);
+			this.fireEvent('attach', [element]);
+			
+			var events = ['enter', 'leave'];
+			if (!this.options.fixed) events.push('move');
+			
+			events.each(function(value){
+				var event = element.retrieve('tip:' + value);
+				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
+				
+				element.store('tip:' + value, event).addEvent('mouse' + value, event);
+			}, this);
+		}, this);
+		
+		return this;
+	},
+
+	detach: function(elements){
+		$$(elements).each(function(element){
+			['enter', 'leave', 'move'].each(function(value){
+				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+			});
+			
+			this.fireEvent('detach', [element]);
+			
+			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+				var original = element.retrieve('tip:native');
+				if (original) element.set('title', original);
+			}
+		}, this);
+		
+		return this;
+	},
+
+	elementEnter: function(event, element){
+		this.container.empty();
+		
+		['title', 'text'].each(function(value){
+			var content = element.retrieve('tip:' + value);
+			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
+		}, this);
+		
+		$clear(this.timer);
+		this.timer = (function(){
+			this.show(this, element);
+			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+		}).delay(this.options.showDelay, this);
+	},
+
+	elementLeave: function(event, element){
+		$clear(this.timer);
+		this.timer = this.hide.delay(this.options.hideDelay, this, element);
+		this.fireForParent(event, element);
+	},
+
+	fireForParent: function(event, element){
+		element = element.getParent();
+		if (!element || element == document.body) return;
+		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+		else this.fireForParent(event, element);
+	},
+
+	elementMove: function(event, element){
+		this.position(event);
+	},
+
+	position: function(event){
+		if (!this.tip) document.id(this);
+
+		var size = window.getSize(), scroll = window.getScroll(),
+			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+			props = {x: 'left', y: 'top'},
+			obj = {};
+		
+		for (var z in props){
+			obj[props[z]] = event.page[z] + this.options.offset[z];
+			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+		}
+		
+		this.tip.setStyles(obj);
+	},
+
+	fill: function(element, contents){
+		if(typeof contents == 'string') element.set('html', contents);
+		else element.adopt(contents);
+	},
+
+	show: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('show', [this.tip, element]);
+	},
+
+	hide: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('hide', [this.tip, element]);
+	}
+
+});
+
+})();/*
+---
+
+script: Date.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Alfons Sanchez
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Date', {
+
+	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'fa menys d`un minut',
+	minuteAgo: 'fa un minut',
+	minutesAgo: 'fa {delta} minuts',
+	hourAgo: 'fa un hora',
+	hoursAgo: 'fa unes {delta} hores',
+	dayAgo: 'fa un dia',
+	daysAgo: 'fa {delta} dies',
+	lessThanMinuteUntil: 'menys d`un minut des d`ara',
+	minuteUntil: 'un minut des d`ara',
+	minutesUntil: '{delta} minuts des d`ara',
+	hourUntil: 'un hora des d`ara',
+	hoursUntil: 'unes {delta} hores des d`ara',
+	dayUntil: '1 dia des d`ara',
+	daysUntil: '{delta} dies des d`ara'
+
+});/*
+---
+
+script: Date.Czech.js
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan Černý chemiX
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Date', {
+
+	months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+	days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+	AM: 'dop.',
+	PM: 'odp.',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return '.';
+	},
+
+    // TODO : in examples use and fix it
+	lessThanMinuteAgo: 'méně než minutou',
+	minuteAgo: 'přibližně před minutou',
+	minutesAgo: 'před {delta} minutami',
+	hourAgo: 'přibližně před hodinou',
+	hoursAgo: 'před {delta} hodinami',
+	dayAgo: 'před dnem',
+	daysAgo: 'před {delta} dni',
+	lessThanMinuteUntil: 'před méně než minutou',
+	minuteUntil: 'asi před minutou',
+	minutesUntil: ' asi před {delta} minutami',
+	hourUntil: 'asi před hodinou',
+	hoursUntil: 'před {delta} hodinami',
+	dayUntil: 'před dnem',
+	daysUntil: 'před {delta} dni',
+	weekUntil: 'před týdnem',
+	weeksUntil: 'před {delta} týdny',
+	monthUntil: 'před měsícem',
+	monthsUntil: 'před {delta} měsíci',
+	yearUntil: 'před rokem',
+	yearsUntil: 'před {delta} lety'
+
+});
+/*
+---
+
+script: Date.Danish.js
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Danish]
+
+...
+*/
+ 
+MooTools.lang.set('da-DK', 'Date', {
+
+	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d-%m-%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+	  //1st, 2nd, 3rd, etc.
+	  return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mindre end et minut siden',
+	minuteAgo: 'omkring et minut siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omkring en time siden',
+	hoursAgo: 'omkring {delta} timer siden',
+	dayAgo: '1 dag siden',
+	daysAgo: '{delta} dage siden',
+	weekAgo: '1 uge siden',
+	weeksAgo: '{delta} uger siden',
+	monthAgo: '1 måned siden',
+	monthsAgo: '{delta} måneder siden',
+	yearthAgo: '1 år siden',
+	yearsAgo: '{delta} år siden',
+	lessThanMinuteUntil: 'mindre end et minut fra nu',
+	minuteUntil: 'omkring et minut fra nu',
+	minutesUntil: '{delta} minutter fra nu',
+	hourUntil: 'omkring en time fra nu',
+	hoursUntil: 'omkring {delta} timer fra nu',
+	dayUntil: '1 dag fra nu',
+	daysUntil: '{delta} dage fra nu',
+	weekUntil: '1 uge fra nu',
+	weeksUntil: '{delta} uger fra nu',
+	monthUntil: '1 måned fra nu',
+	monthsUntil: '{delta} måneder fra nu',
+	yearUntil: '1 år fra nu',
+	yearsUntil: '{delta} år fra nu'
+
+});
+/*
+---
+
+script: Date.Dutch.js
+
+description: Date messages in Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Date', {
+
+	months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
+	days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: 'e',
+
+	lessThanMinuteAgo: 'minder dan een minuut geleden',
+	minuteAgo: 'ongeveer een minuut geleden',
+	minutesAgo: 'minuten geleden',
+	hourAgo: 'ongeveer een uur geleden',
+	hoursAgo: 'ongeveer {delta} uur geleden',
+	dayAgo: '{delta} dag geleden',
+	daysAgo: 'dagen geleden',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+	lessThanMinuteUntil: 'minder dan een minuut vanaf nu',
+	minuteUntil: 'ongeveer een minuut vanaf nu',
+	minutesUntil: '{delta} minuten vanaf nu',
+	hourUntil: 'ongeveer een uur vanaf nu',
+	hoursUntil: 'ongeveer {delta} uur vanaf nu',
+	dayUntil: '1 dag vanaf nu',
+	daysUntil: '{delta} dagen vanaf nu',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearthAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+
+	weekUntil: 'over een week',
+	weeksUntil: 'over {delta} weken',
+	monthUntil: 'over een maand',
+	monthsUntil: 'over {delta} maanden',
+	yearUntil: 'over een jaar',
+	yearsUntil: 'over {delta} jaar' 
+
+});/*
+---
+
+script: Date.English.GB.js
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.English.GB]
+
+...
+*/
+
+MooTools.lang.set('en-GB', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+	
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M'
+
+}).set('cascade', ['en-US']);/*
+---
+
+script: Date.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Date', {
+
+	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],
+	//culture's date order: MM.DD.YYYY
+	dateOrder: ['month', 'date', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%m.%d.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
+	minuteAgo: 'umbes minut aega tagasi',
+	minutesAgo: '{delta} minutit tagasi',
+	hourAgo: 'umbes tund aega tagasi',
+	hoursAgo: 'umbes {delta} tundi tagasi',
+	dayAgo: '1 päev tagasi',
+	daysAgo: '{delta} päeva tagasi',
+	weekAgo: '1 nädal tagasi',
+	weeksAgo: '{delta} nädalat tagasi',
+	monthAgo: '1 kuu tagasi',
+	monthsAgo: '{delta} kuud tagasi',
+	yearAgo: '1 aasta tagasi',
+	yearsAgo: '{delta} aastat tagasi',
+	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
+	minuteUntil: 'umbes minuti aja pärast',
+	minutesUntil: '{delta} minuti pärast',
+	hourUntil: 'umbes tunni aja pärast',
+	hoursUntil: 'umbes {delta} tunni pärast',
+	dayUntil: '1 päeva pärast',
+	daysUntil: '{delta} päeva pärast',
+	weekUntil: '1 nädala pärast',
+	weeksUntil: '{delta} nädala pärast',
+	monthUntil: '1 kuu pärast',
+	monthsUntil: '{delta} kuu pärast',
+	yearUntil: '1 aasta pärast',
+	yearsUntil: '{delta} aasta pärast'
+
+});/*
+---
+
+script: Date.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Date', {
+
+	months: ['Januar', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+	days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: [ 'date', 'month', 'year', '.'],
+
+	AM: 'vormittags',
+	PM: 'nachmittags',
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '.',
+
+	lessThanMinuteAgo: 'Vor weniger als einer Minute',
+	minuteAgo: 'Vor einer Minute',
+	minutesAgo: 'Vor {delta} Minuten',
+	hourAgo: 'Vor einer Stunde',
+	hoursAgo: 'Vor {delta} Stunden',
+	dayAgo: 'Vor einem Tag',
+	daysAgo: 'Vor {delta} Tagen',
+	weekAgo: 'Vor einer Woche',
+	weeksAgo: 'Vor {delta} Wochen',
+	monthAgo: 'Vor einem Monat',
+	monthsAgo: 'Vor {delta} Monaten',
+	yearAgo: 'Vor einem Jahr',
+	yearsAgo: 'Vor {delta} Jahren',
+	lessThanMinuteUntil: 'In weniger als einer Minute',
+	minuteUntil: 'In einer Minute',
+	minutesUntil: 'In {delta} Minuten',
+	hourUntil: 'In ca. einer Stunde',
+	hoursUntil: 'In ca. {delta} Stunden',
+	dayUntil: 'In einem Tag',
+	daysUntil: 'In {delta} Tagen',
+	weekUntil: 'In einer Woche',
+	weeksUntil: 'In {delta} Wochen',
+	monthUntil: 'In einem Monat',
+	monthsUntil: 'In {delta} Monaten',
+	yearUntil: 'In einem Jahr',
+	yearsUntil: 'In {delta} Jahren'
+});/*
+---
+
+script: Date.German.CH.js
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - /Lang
+ - /Date.German
+
+provides: [Date.German.CH]
+
+...
+*/
+
+MooTools.lang.set('de-CH', 'cascade', ['de-DE']);/*
+---
+
+script: Date.French.js
+
+description: Date messages in French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Date', {
+
+	months: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
+	days: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	getOrdinal: function(dayOfMonth){
+	  return (dayOfMonth > 1) ? '' : 'er';
+	},
+
+	lessThanMinuteAgo: 'il y a moins d\'une minute',
+	minuteAgo: 'il y a une minute',
+	minutesAgo: 'il y a {delta} minutes',
+	hourAgo: 'il y a une heure',
+	hoursAgo: 'il y a {delta} heures',
+	dayAgo: 'il y a un jour',
+	daysAgo: 'il y a {delta} jours',
+	weekAgo: 'il y a une semaine',
+	weeksAgo: 'il y a {delta} semaines',
+	monthAgo: 'il y a 1 mois',
+	monthsAgo: 'il y a {delta} mois',
+	yearthAgo: 'il y a 1 an',
+	yearsAgo: 'il y a {delta} ans',
+	lessThanMinuteUntil: 'dans moins d\'une minute',
+	minuteUntil: 'dans une minute',
+	minutesUntil: 'dans {delta} minutes',
+	hourUntil: 'dans une heure',
+	hoursUntil: 'dans {delta} heures',
+	dayUntil: 'dans un jour',
+	daysUntil: 'dans {delta} jours',
+	weekUntil: 'dans 1 semaine',
+	weeksUntil: 'dans {delta} semaines',
+	monthUntil: 'dans 1 mois',
+	monthsUntil: 'dans {delta} mois',
+	yearUntil: 'dans 1 an',
+	yearsUntil: 'dans {delta} ans'
+
+});
+/*
+---
+
+script: Date.Italian.js
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Date', {
+ 
+	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H.%M',
+
+	/* Date.Extras */
+	ordinal: '&ordm;',
+
+	lessThanMinuteAgo: 'meno di un minuto fa',
+	minuteAgo: 'circa un minuto fa',
+	minutesAgo: 'circa {delta} minuti fa',
+	hourAgo: 'circa un\'ora fa',
+	hoursAgo: 'circa {delta} ore fa',
+	dayAgo: 'circa 1 giorno fa',
+	daysAgo: 'circa {delta} giorni fa',
+	lessThanMinuteUntil: 'tra meno di un minuto',
+	minuteUntil: 'tra circa un minuto',
+	minutesUntil: 'tra circa {delta} minuti',
+	hourUntil: 'tra circa un\'ora',
+	hoursUntil: 'tra circa {delta} ore',
+	dayUntil: 'tra circa un giorno',
+	daysUntil: 'tra circa {delta} giorni'
+
+});/*
+---
+
+script: Date.Norwegian.js
+
+description: Date messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	lessThanMinuteAgo: 'kortere enn et minutt siden',
+	minuteAgo: 'omtrent et minutt siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omtrent en time siden',
+	hoursAgo: 'omtrent {delta} timer siden',
+	dayAgo: '{delta} dag siden',
+	daysAgo: '{delta} dager siden'
+
+});/*
+---
+
+script: Date.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Date', {
+	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+	dateOrder: ['year', 'month', 'date'],
+	AM: 'nad ranem',
+	PM: 'po południu',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mniej niż minute temu',
+	minuteAgo: 'około minutę temu',
+	minutesAgo: '{delta} minut temu',
+	hourAgo: 'około godzinę temu',
+	hoursAgo: 'około {delta} godzin temu',
+	dayAgo: 'Wczoraj',
+	daysAgo: '{delta} dni temu',
+	lessThanMinuteUntil: 'za niecałą minutę',
+	minuteUntil: 'za około minutę',
+	minutesUntil: 'za {delta} minut',
+	hourUntil: 'za około godzinę',
+	hoursUntil: 'za około {delta} godzin',
+	dayUntil: 'za 1 dzień',
+	daysUntil: 'za {delta} dni'
+});/*
+---
+
+script: Date.Portuguese.BR.js
+
+description: Date messages in Portuguese-BR (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Date', {
+
+	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1º, 2º, 3º, etc.
+    	return '&ordm;';
+	},
+
+	lessThanMinuteAgo: 'há menos de um minuto',
+	minuteAgo: 'há cerca de um minuto',
+	minutesAgo: 'há {delta} minutos',
+	hourAgo: 'há cerca de uma hora',
+	hoursAgo: 'há cerca de {delta} horas',
+	dayAgo: 'há um dia',
+	daysAgo: 'há {delta} dias',
+    weekAgo: 'há uma semana',
+	weeksAgo: 'há {delta} semanas',
+	monthAgo: 'há um mês',
+	monthsAgo: 'há {delta} meses',
+	yearAgo: 'há um ano',
+	yearsAgo: 'há {delta} anos',
+	lessThanMinuteUntil: 'em menos de um minuto',
+	minuteUntil: 'em um minuto',
+	minutesUntil: 'em {delta} minutos',
+	hourUntil: 'em uma hora',
+	hoursUntil: 'em {delta} horas',
+	dayUntil: 'em um dia',
+	daysUntil: 'em {delta} dias',
+	weekUntil: 'em uma semana',
+	weeksUntil: 'em {delta} semanas',
+	monthUntil: 'em um mês',
+	monthsUntil: 'em {delta} meses',
+	yearUntil: 'em um ano',
+	yearsUntil: 'em {delta} anos'
+
+});/*
+Script: Date.Russian.js
+	Date messages for Russian.
+
+	License:
+		MIT-style license.
+
+	Authors:
+		Evstigneev Pavel
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Date', {
+
+	months: ['Январь', 'Февраль', 'Март', '�?прель', 'Май', 'Июнь', 'Июль', '�?вгу�?т', 'Сент�?брь', 'Окт�?брь', '�?о�?брь', 'Декабрь'],
+	days: ['Во�?кре�?енье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'П�?тница', 'Суббота'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+
+  /*
+   *  Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+   *
+   *  one -> n mod 10 is 1 and n mod 100 is not 11;
+   *  few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+   *  many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+   *  other -> everything else (example 3.14)
+   */
+
+  pluralize: function (n, one, few, many, other) {
+    var modulo10 = n % 10
+    var modulo100 = n % 100
+
+    if (modulo10 == 1 && modulo100 != 11) {
+      return one;
+    } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return few;
+    } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return many;
+    } else {
+      return other;
+    }
+  },
+
+	/* Date.Extras */
+	ordinal: '',
+	lessThanMinuteAgo: 'меньше минуты назад',
+	minuteAgo: 'минута назад',
+	minutesAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'минута', 'минуты', 'минут') + ' назад'},
+	hourAgo: 'ча�? назад',
+	hoursAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'ча�?', 'ча�?а', 'ча�?ов') + ' назад'},
+	dayAgo: 'вчера',
+	daysAgo: function (delta) { return '{delta} ' + this.pluralize(delta, 'день', 'дн�?', 'дней') + ' назад' },
+	lessThanMinuteUntil: 'меньше минуты назад',
+	minuteUntil: 'через минуту',
+	minutesUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'ча�?', 'ча�?а', 'ча�?ов') + ''},
+	hourUntil: 'через ча�?',
+	hoursUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'ча�?', 'ча�?а', 'ча�?ов') + ''},
+	dayUntil: 'завтра',
+	daysUntil: function (delta) { return 'через {delta} ' + this.pluralize(delta, 'день', 'дн�?', 'дней') + '' }
+
+});/*
+---
+
+script: Date.Spanish.US.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Date', {
+
+	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+	days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'hace menos de un minuto',
+	minuteAgo: 'hace un minuto',
+	minutesAgo: 'hace {delta} minutos',
+	hourAgo: 'hace una hora',
+	hoursAgo: 'hace unas {delta} horas',
+	dayAgo: 'hace un día',
+	daysAgo: 'hace {delta} días',
+	weekAgo: 'hace una semana',
+	weeksAgo: 'hace unas {delta} semanas',
+	monthAgo: 'hace un mes',
+	monthsAgo: 'hace {delta} meses',
+	yearAgo: 'hace un año',
+	yearsAgo: 'hace {delta} años',
+	lessThanMinuteUntil: 'menos de un minuto desde ahora',
+	minuteUntil: 'un minuto desde ahora',
+	minutesUntil: '{delta} minutos desde ahora',
+	hourUntil: 'una hora desde ahora',
+	hoursUntil: 'unas {delta} horas desde ahora',
+	dayUntil: 'un día desde ahora',
+	daysUntil: '{delta} días desde ahora',
+	weekUntil: 'una semana desde ahora',
+	weeksUntil: 'unas {delta} semanas desde ahora',
+	monthUntil: 'un mes desde ahora',
+	monthsUntil: '{delta} meses desde ahora',
+	yearUntil: 'un año desde ahora',
+	yearsUntil: '{delta} años desde ahora'
+
+});/*
+---
+
+script: Date.Swedish.js
+
+description: Date messages for Swedish (SE).
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Date', {
+
+	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+	// culture's date order: YYYY-MM-DD
+	dateOrder: ['year', 'month', 'date'],
+	AM: '',
+	PM: '',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		// Not used in Swedish
+		return '';
+	},
+
+	lessThanMinuteAgo: 'mindre än en minut sedan',
+	minuteAgo: 'ungefär en minut sedan',
+	minutesAgo: '{delta} minuter sedan',
+	hourAgo: 'ungefär en timme sedan',
+	hoursAgo: 'ungefär {delta} timmar sedan',
+	dayAgo: '1 dag sedan',
+	daysAgo: '{delta} dagar sedan',
+	lessThanMinuteUntil: 'mindre än en minut sedan',
+	minuteUntil: 'ungefär en minut sedan',
+	minutesUntil: '{delta} minuter sedan',
+	hourUntil: 'ungefär en timme sedan',
+	hoursUntil: 'ungefär {delta} timmar sedan',
+	dayUntil: '1 dag sedan',
+	daysUntil: '{delta} dagar sedan'
+
+});/*
+---
+
+script: Date.Ukrainian.js
+
+description: Date messages for Ukrainian.
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Ukrainian]
+
+...
+*/
+
+(function(){
+	var pluralize = function(n, one, few, many, other){
+		var d = (n / 10).toInt();
+		var z = n % 10;
+		var s = (n / 100).toInt();
+
+		if(d == 1 && n > 10) return many;
+		if(z == 1) return one;
+		if(z > 0 && z < 5) return few;
+		return many;
+	};
+
+	MooTools.lang.set('uk-UA', 'Date', {
+			months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'Вере�?ень', 'Жовтень', 'Ли�?топад', 'Грудень'],
+			days: ['�?еділ�?', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'�?тниц�?', 'Субота'],
+			//culture's date order: DD/MM/YYYY
+			dateOrder: ['date', 'month', 'year'],
+			AM: 'до полудн�?',
+			PM: 'по полудню',
+
+			shortDate: '%d/%m/%Y',
+			shortTime: '%H:%M',
+
+			/* Date.Extras */
+			ordinal: '',
+			lessThanMinuteAgo: 'меньше хвилини тому',
+			minuteAgo: 'хвилину тому',
+			minutesAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому';
+			},
+			hourAgo: 'годину тому',
+			hoursAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому';
+			},
+			dayAgo: 'вчора',
+			daysAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'день', 'дн�?', 'днів') + ' тому';
+			},
+			weekAgo: 'тиждень тому',
+			weeksAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому';
+			},
+			monthAgo: 'мі�?�?ць тому',
+			monthsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'мі�?�?ць', 'мі�?�?ці', 'мі�?�?ців') + ' тому';
+			},
+			yearAgo: 'рік тому',
+			yearsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому';
+			},
+			lessThanMinuteUntil: 'за мить',
+			minuteUntil: 'через хвилину',
+			minutesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин');
+			},
+			hourUntil: 'через годину',
+			hoursUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин');
+			},
+			dayUntil: 'завтра',
+			daysUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'день', 'дн�?', 'днів');
+			},
+			weekUntil: 'через тиждень',
+			weeksUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів');
+			},
+			monthUntil: 'через мі�?�?ць',
+			monthesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'мі�?�?ць', 'мі�?�?ці', 'мі�?�?ців');
+			},
+			yearUntil: 'через рік',
+			yearsUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років');
+			}
+	});
+
+})();/*
+---
+
+script: Form.Validator.Arabic.js
+
+description: Form.Validator messages in Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Arabic]
+
+...
+*/
+
+MooTools.lang.set('ar', 'Form.Validator', {
+	required:'هذا الحقل مطلوب.',
+	minLength:'رجاءً إدخال {minLength}  أحر�? على الأقل (تم إدخال {length} أحر�?).',
+	maxLength:'الرجاء عدم إدخال أكثر من {maxLength} أحر�? (تم إدخال {length} أحر�?).',
+	integer:'الرجاء إدخال عدد صحيح �?ي هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
+	numeric:'الرجاء إدخال قيم رقمية �?ي هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+	digits:'الرجاء أستخدام قيم رقمية وعلامات ترقيمية �?قط �?ي هذا الحقل (مثال, رقم هات�? مع نقطة أو شحطة)',
+	alpha:'الرجاء أستخدام أحر�? �?قط (ا-ي) �?ي هذا الحقل. أي �?راغات أو علامات غير مسموحة.',
+	alphanum:'الرجاء أستخدام أحر�? �?قط (ا-ي) أو أرقام (0-9) �?قط �?ي هذا الحقل. أي �?راغات أو علامات غير مسموحة.',
+	dateSuchAs:'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+	dateInFormatMDY:'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+	email:'الرجاء إدخال بريد إلكتروني صحيح.',
+	url:'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
+	currencyDollar:'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
+	oneRequired:'الرجاء إدخال قيمة �?ي أحد هذه الحقول على الأقل.',
+	errorPrefix: 'خطأ: ',
+	warningPrefix: 'تحذير: '
+}).set('ar', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Alfons Sanchez
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Form.Validator', {
+
+	required:'Aquest camp es obligatori.',
+	minLength:'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+	maxLength:'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+	integer:'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+	numeric:'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+	alpha:'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	alphanum:'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	dateSuchAs:'Per favor introdueix una data valida com {date}',
+	dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',
+	url:'Per favor introdueix una URL valida com http://www.google.com.',
+	currencyDollar:'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+	oneRequired:'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Avis: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No poden haver espais en aquesta entrada.',
+	reqChkByNode: 'No hi han elements seleccionats.',
+	requiredChk: 'Aquest camp es obligatori.',
+	reqChkByName: 'Per favor selecciona una {label}.',
+	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+	startDate: 'la data de inici',
+	endDate: 'la data de fi',
+	currendDate: 'la data actual',
+	afterDate: 'La data deu ser igual o posterior a {label}.',
+	beforeDate: 'La data deu ser igual o anterior a {label}.',
+	startMonth: 'Per favor selecciona un mes d´orige',
+	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});/*
+---
+
+script: Form.Validator.Czech.js
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan Černý chemiX
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Form.Validator', {
+
+	required:'Tato položka je povinná.',
+	minLength:'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+	maxLength:'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).',
+	integer:'Zadejte prosím celé �?íslo. Desetinná �?ísla (např. 1.25) nejsou povolena.',
+	numeric:'Zadejte jen �?íselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+	digits:'Zadejte prosím pouze �?ísla a interpunk�?ní znaménka(například telefonní �?íslo s poml�?kami nebo te�?kami je povoleno).',
+	alpha:'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+	alphanum:'Zadejte prosím pouze písmena (a-z) nebo �?íslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+	dateSuchAs:'Zadejte prosím platné datum jako {date}',
+	dateInFormatMDY:'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+	email:'Zadejte prosím platnou e-mailovou adresu. Například "fred at domain.com".',
+	url:'Zadejte prosím platnou URL adresu jako http://www.google.com.',
+	currencyDollar:'Zadejte prosím platnou �?ástku. Například $100.00.',
+	oneRequired:'Zadejte prosím alespoň jednu hodnotu pro tyto položky.',
+	errorPrefix: 'Chyba: ',
+	warningPrefix: 'Upozornění: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'V této položce nejsou povoleny mezery',
+	reqChkByNode: 'Nejsou vybrány žádné položky.',
+	requiredChk: 'Tato položka je vyžadována.',
+	reqChkByName: 'Prosím vyberte {label}.',
+	match: 'Tato položka se musí shodovat s položkou {matchName}',
+	startDate: 'datum zahájení',
+	endDate: 'datum ukon�?ení',
+	currendDate: 'aktuální datum',
+	afterDate: 'Datum by mělo být stejné nebo větší než {label}.',
+	beforeDate: 'Datum by mělo být stejné nebo menší než {label}.',
+	startMonth: 'Vyberte po�?áte�?ní měsíc.',
+	sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.',
+    creditcard: 'Zadané �?íslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} �?ísel.'
+
+});
+/*
+---
+
+script: Form.Validator.Chinese.js
+
+description: Form.Validator messages in chinese (both simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - 陈桂军 - guidy <at> ixuer [dot] net
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Chinese]
+
+...
+*/
+
+/*
+In Chinese:
+------------
+需�?指出的是:
+简体中文适用于中国大陆,
+�?体中文适用于香港�?澳门和�?�湾�?。
+简体中文和�?体中文在字体和语法上有很多的�?�?�之处。
+
+我�?�以确�?简体中文语言包的准确性,
+但对于�?体中文,我�?�以�?�?用户�?�以准确的�?�解,但无法�?�?语�?�符�?�他们的阅读习惯。
+如果您�?能确认的�?,�?�以�?�使用简体中文语言包,因为它是最通用的。
+
+In English:
+------------
+It should be noted that:
+Simplified  Chinese apply to mainland Chinese,
+Traditional Chinese apply to Hong Kong, Macao and Taiwan Province.
+There are a lot of different from Simplified  Chinese and Traditional Chinese , Contains font and syntax .
+
+I can assure Simplified Chinese language pack accuracy .
+For Traditional Chinese, I can only guarantee that users can understand, but not necessarily in line with their reading habits.
+If you are unsure, you can only use the simplified Chinese language pack, as it is the most common.
+
+*/
+
+// Simplified Chinese
+MooTools.lang.set('zhs-CN', 'Form.Validator', {
+	required:'这是必填项。',
+	minLength:'请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
+	maxLength:'最多�?�能输入 {maxLength} 个字符 (已输入 {length} 个)。',
+	integer:'请输入一个整数,�?能包�?��?数点。例如:"1", "200"。',
+	numeric:'请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'这里�?�能接�?�数字和标点的输入,标点�?�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'请输入 A-Z 的 26 个字�?,�?能包�?�空格或任何其他字符。',
+	alphanum:'请输入 A-Z 的 26 个字�?或 0-9 的 10 个数字,�?能包�?�空格或任何其他字符。',
+	dateSuchAs:'请输入�?�法的日期格�?,如:{date}。',
+	dateInFormatMDY:'请输入�?�法的日期格�?,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'请输入�?�法的电�?信箱地�?�,例如:"fred at domain.com"。',
+	url:'请输入�?�法的 Url 地�?�,例如:http://www.google.com。',
+	currencyDollar:'请输入�?�法的货�?符�?�,例如:¥',
+	oneRequired:'请至少选择一项。',
+	errorPrefix: '错误:',
+	warningPrefix: '警告:'
+});
+
+// Traditional Chinese
+MooTools.lang.set('zht-CN', 'Form.Validator', {
+	required:'這是必填項。',
+	minLength:'請至少�?�入 {minLength} 個字符(已�?�入 {length} 個)。',
+	maxLength:'最多�?�能�?�入 {maxLength} 個字符(已�?�入 {length} 個)。',
+	integer:'請�?�入一個整數,�?能包�?��?數點。例如:"1", "200"。',
+	numeric:'請�?�入一個數字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'這裡�?�能接�?�數字和標點的�?�入,標點�?�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'請�?�入 A-Z 的 26 個字�?,�?能包�?�空格或任何其他字符。',
+	alphanum:'請�?�入 A-Z 的 26 個字�?或 0-9 的 10 個數字,�?能包�?�空格或任何其他字符。',
+	dateSuchAs:'請�?�入�?�法的日期格�?,如:{date}。',
+	dateInFormatMDY:'請�?�入�?�法的日期格�?,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'請�?�入�?�法的電�?信箱地�?�,例如:"fred at domain.com"。',
+	url:'請�?�入�?�法的 Url 地�?�,例如:http://www.google.com。',
+	currencyYuan:'請�?�入�?�法的貨幣符號,例如:¥',
+	oneRequired:'請至少�?�擇一項。',
+	errorPrefix: '錯誤:',
+	warningPrefix: '警告:'
+});
+
+Form.Validator.add('validate-currency-yuan', {
+	errorMsg: function(){
+		return Form.Validator.getMsg('currencyYuan');
+	},
+	test: function(element) {
+		// [ï¿¥]1[##][,###]+[.##]
+		// [ï¿¥]1###+[.##]
+		// [ï¿¥]0.##
+		// [ï¿¥].##
+		return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+	}
+});
+/*
+---
+
+script: Form.Validator.Dutch.js
+
+description: Form.Validator messages in Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Form.Validator', {
+	required:'Dit veld is verplicht.',
+	minLength:'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+	maxLength:'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+	integer:'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1,25) zijn niet toegestaan.',
+	numeric:'Vul alleen numerieke waarden in (bijvoorbeeld. "1" of "1.1" of "-1" of "-1.1").',
+	digits:'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met een streepje).',
+	alpha:'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+	alphanum:'Vul alleen letters in (a-z) of nummers (0-9). Spaties en andere karakters zijn niet toegestaan.',
+	dateSuchAs:'Vul een geldige datum in, zoals {date}',
+	dateInFormatMDY:'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+	email:'Vul een geldig e-mailadres in. Bijvoorbeeld "fred at domein.nl".',
+	url:'Vul een geldige URL in, zoals http://www.google.nl.',
+	currencyDollar:'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+	oneRequired:'Vul iets in bij minimaal een van de invoervelden.',
+	warningPrefix: 'Waarschuwing: ',
+	errorPrefix: 'Fout: '
+});/*
+---
+
+script: Form.Validator.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Form.Validator', {
+
+	required:'Väli peab olema täidetud.',
+	minLength:'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
+	maxLength:'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
+	integer:'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
+	numeric:'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
+	digits:'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+	alpha:'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
+	alphanum:'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
+	dateSuchAs:'Palun sisestage kehtiv kuupäev kujul {date}',
+	dateInFormatMDY:'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
+	email:'Palun sisestage kehtiv e-maili aadress (näiteks: "fred at domain.com").',
+	url:'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
+	currencyDollar:'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
+	oneRequired:'Palun sisestage midagi vähemalt ühele antud väljadest.',
+	errorPrefix: 'Viga: ',
+	warningPrefix: 'Hoiatus: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Väli ei tohi sisaldada tühikuid.',
+	reqChkByNode: 'Ükski väljadest pole valitud.',
+	requiredChk: 'Välja täitmine on vajalik.',
+	reqChkByName: 'Palun valige üks {label}.',
+	match: 'Väli peab sobima {matchName} väljaga',
+	startDate: 'algkuupäev',
+	endDate: 'lõppkuupäev',
+	currendDate: 'praegune kuupäev',
+	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
+	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
+	startMonth: 'Palun valige algkuupäev.',
+	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'
+
+});/*
+---
+
+script: Form.Validator.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors: 
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Form.Validator', {
+
+	required: 'Dieses Eingabefeld muss ausgef&uuml;llt werden.',
+	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+	maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+	integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.',
+	numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.',
+	digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+	alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+	alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).',
+	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.1999&quot;).',
+	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max at mustermann.de&quot;).',
+	url: 'Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).',
+	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).',
+	oneRequired: 'Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.',
+	errorPrefix: 'Fehler: ',
+	warningPrefix: 'Warnung: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+	requiredChk: 'Dieses Feld muss ausgef&uuml;llt werden.',
+	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+	match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.',
+	startDate: 'Das Anfangsdatum',
+	endDate: 'Das Enddatum',
+	currendDate: 'Das aktuelle Datum',
+	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.',
+	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.',
+	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.',
+	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+});
+/*
+---
+
+script: Form.Validator.German.CH.js
+
+description: Date messages for German (Switzerland).
+ 
+license: MIT-style license
+ 
+authors:
+ - Michael van der Weg
+
+requires:
+ - /Lang
+ - /Form.Validator.German
+
+provides: [Form.Validator.German.CH]
+
+...
+*/
+ 
+MooTools.lang.set('de-CH', 'Form.Validator', {
+ 
+  required:'Dieses Feld ist obligatorisch.',
+  minLength:'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  maxLength:'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  integer:'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+  numeric:'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+  digits:'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+  alpha:'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  alphanum:'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  dateSuchAs:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+  dateInFormatMDY:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+  email:'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria at bernasconi.ch&quot;.',
+  url:'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.',
+  currencyDollar:'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+  oneRequired:'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+  errorPrefix: 'Fehler: ',
+  warningPrefix: 'Warnung: ',
+ 
+  //Form.Validator.Extras
+ 
+  noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+  reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+  requiredChk: 'Dieses Feld ist obligatorisch.',
+  reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+  match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+  startDate: 'Das Anfangsdatum',
+  endDate: 'Das Enddatum',
+  currendDate: 'Das aktuelle Datum',
+  afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+  beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+  startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+  sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.'
+ 
+});/*
+---
+
+script: Form.Validator.French.js
+
+description: Form.Validator messages in French.
+
+license: MIT-style license
+
+authors: 
+ - Miquel Hudin
+ - Nicolas Sorosac <nicolas <dot> sorosac <at> gmail <dot> com>
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Form.Validator', {
+  required:'Ce champ est obligatoire.',
+  minLength:'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  maxLength:'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  integer:'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+  numeric:'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+  digits:'Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d\'union est autoris&eacute;).',
+  alpha:'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  alphanum:'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  dateSuchAs:'Veuillez saisir une date correcte comme {date}',
+  dateInFormatMDY:'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+  email:'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred at domaine.com".',
+  url:'Veuillez saisir une URL, comme http://www.google.com.',
+  currencyDollar:'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
+  oneRequired:'Veuillez s&eacute;lectionner au moins une de ces options.',
+  errorPrefix: 'Erreur : ',
+  warningPrefix: 'Attention : ',
+  
+  //Form.Validator.Extras
+ 
+  noSpace: 'Ce champ n\'accepte pas les espaces.',
+  reqChkByNode: 'Aucun &eacute;l&eacute;ment n\'est s&eacute;lectionn&eacute;.',
+  requiredChk: 'Ce champ est obligatoire.',
+  reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+  match: 'Ce champ doit correspondre avec le champ {matchName}.',
+  startDate: 'date de d&eacute;but',
+  endDate: 'date de fin',
+  currendDate: 'date actuelle',
+  afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+  beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+  startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+  sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'
+ 
+});/*
+---
+
+script: Form.Validator.Italian.js
+
+description: Form.Validator messages in Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Form.Validator', {
+
+	required:'Il campo &egrave; obbligatorio.',
+	minLength:'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+	maxLength:'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+	integer:'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+	numeric:'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+	digits:'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+	alpha:'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+	alphanum:'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+	dateSuchAs:'Inserire una data valida del tipo {date}',
+	dateInFormatMDY:'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+	email:'Inserire un indirizzo email valido. Per esempio "nome at dominio.com".',
+	url:'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
+	currencyDollar:'Inserire un importo valido. Per esempio "$100.00".',
+	oneRequired:'Completare almeno uno dei campi richiesti.',
+	errorPrefix: 'Errore: ',
+	warningPrefix: 'Attenzione: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Non sono consentiti spazi.',
+	reqChkByNode: 'Nessuna voce selezionata.',
+	requiredChk: 'Il campo &egrave; obbligatorio.',
+	reqChkByName: 'Selezionare un(a) {label}.',
+	match: 'Il valore deve corrispondere al campo {matchName}',
+	startDate: 'data d\'inizio',
+	endDate: 'data di fine',
+	currendDate: 'data attuale',
+	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+	startMonth: 'Selezionare un mese d\'inizio',
+	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});/*
+---
+
+script: Form.Validator.Norwegian.js
+
+description: Form.Validator messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Form.Validator', {
+   required:'Dette feltet er påkrevd.',
+   minLength:'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+   maxLength:'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+   integer:'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+   numeric:'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+   digits:'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+   alpha:'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   alphanum:'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   dateSuchAs:'Vennligst skriv inn en gyldig dato, som {date}',
+   dateInFormatMDY:'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+   email:'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen at domene.no".',
+   url:'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
+   currencyDollar:'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .',
+   oneRequired:'Vennligst fyll ut noe i minst ett av disse feltene.',
+   errorPrefix: 'Feil: ',
+   warningPrefix: 'Advarsel: '
+});/*
+---
+
+script: Form.Validator.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Form.Validator', {
+
+	required:'To pole jest wymagane.',
+	minLength:'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+	maxLength:'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})',
+	integer:'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+	numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+	digits:'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+	alpha:'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	alphanum:'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	dateSuchAs:'Prosimy podać prawidłową datę w formacie: {date}',
+	dateInFormatMDY:'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+	email:'Prosimy podać prawidłowy adres e-mail, np. "jan at domena.pl".',
+	url:'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
+	currencyDollar:'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+	oneRequired:'Prosimy wypełnić chociaż jedno z pól.',
+	errorPrefix: 'BÅ‚Ä…d: ',
+	warningPrefix: 'Uwaga: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'W tym polu nie mogą znajdować się spacje.',
+	reqChkByNode: 'Brak zaznaczonych elementów.',
+	requiredChk: 'To pole jest wymagane.',
+	reqChkByName: 'Prosimy wybrać z {label}.',
+	match: 'To pole musi być takie samo jak {matchName}',
+	startDate: 'data poczÄ…tkowa',
+	endDate: 'data końcowa',
+	currendDate: 'aktualna data',
+	afterDate: 'Podana data poinna być taka sama lub po {label}.',
+	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+	startMonth: 'Prosimy wybrać początkowy miesiąc.',
+	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});/*
+---
+
+script: Form.Validator.Portuguese.js
+
+description: Form.Validator messages in Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Portuguese]
+
+...
+*/
+
+MooTools.lang.set('pt-PT', 'Form.Validator', {
+	required:'Este campo é necessário.',
+	minLength:'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+	maxLength:'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+	integer:'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+	numeric:'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits:'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+	alpha:'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+	alphanum:'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+	dateSuchAs:'Digite uma data válida, como {date}',
+	dateInFormatMDY:'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+	email:'Digite um endereço de email válido. Por exemplo "fred at domain.com".',
+	url:'Digite uma URL válida, como http://www.google.com.',
+	currencyDollar:'Digite um valor válido $. Por exemplo $ 100,00. ',
+	oneRequired:'Digite algo para pelo menos um desses insumos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: '
+
+}).set('pt-PT', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Portuguese.BR.js
+
+description: Form.Validator messages in Portuguese-BR.
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - /Lang
+ - /Form.Validator.Portuguese
+
+provides: [Form.Validator.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Form.Validator', {
+
+	required: 'Este campo é obrigatório.',
+	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+	dateSuchAs: 'Digite uma data válida, como {date}',
+	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+	email: 'Digite um endereço de email válido. Por exemplo "nome at dominio.com".',
+	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
+	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+	oneRequired: 'Digite algo para pelo menos um desses campos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Não é possível digitar espaços neste campo.',
+	reqChkByNode: 'Não foi selecionado nenhum item.',
+	requiredChk: 'Este campo é obrigatório.',
+	reqChkByName: 'Por favor digite um {label}.',
+	match: 'Este campo deve ser igual ao campo {matchName}.',
+	startDate: 'a data inicial',
+	endDate: 'a data final',
+	currendDate: 'a data atual',
+	afterDate: 'A data deve ser igual ou posterior a {label}.',
+	beforeDate: 'A data deve ser igual ou anterior a {label}.',
+	startMonth: 'Por favor selecione uma data inicial.',
+	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});/*
+---
+
+script: Form.Validator.Russian.js
+
+description: Form.Validator messages in Russian (utf-8 and cp1251).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Russian]
+
+...
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {
+	required:'Это поле об�?зательно к заполнению.',
+	minLength:'Пожалуй�?та, введите хот�? бы {minLength} �?имволов (Вы ввели {length}).',
+	maxLength:'Пожалуй�?та, введите не больше {maxLength} �?имволов (Вы ввели {length}).',
+	integer:'Пожалуй�?та, введите в �?то поле чи�?ло. Дробные чи�?ла (например 1.25) тут не разрешены.',
+	numeric:'Пожалуй�?та, введите в �?то поле чи�?ло (например "1" или "1.1", или "-1", или "-1.1").',
+	digits:'В �?том поле Вы можете и�?пользовать только цифры и знаки пунктуации (например, телефонный номер �?о знаками дефи�?а или �? точками).',
+	alpha:'В �?том поле можно и�?пользовать только латин�?кие буквы (a-z). Пробелы и другие �?имволы запрещены.',
+	alphanum:'В �?том поле можно и�?пользовать только латин�?кие буквы (a-z) и цифры (0-9). Пробелы и другие �?имволы запрещены.',
+	dateSuchAs:'Пожалуй�?та, введите корректную дату {date}',
+	dateInFormatMDY:'Пожалуй�?та, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
+	email:'Пожалуй�?та, введите корректный емейл-адре�?. Дл�? примера "fred at domain.com".',
+	url:'Пожалуй�?та, введите правильную �?�?ылку вида http://www.google.com.',
+	currencyDollar:'Пожалуй�?та, введите �?умму в долларах. �?апример: $100.00 .',
+	oneRequired:'Пожалуй�?та, выберите хоть что-нибудь в одном из �?тих полей.',
+	errorPrefix: 'Ошибка: ',
+	warningPrefix: 'Внимание: '
+});
+
+//translation in windows-1251 codepage
+MooTools.lang.set('ru-RU', 'Form.Validator', {
+	required:'�?òî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
+	minLength:'�?îæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	maxLength:'�?îæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	integer:'�?îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
+	numeric:'�?îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
+	digits:' ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
+	alpha:'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). �?ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	alphanum:'Â ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). �?ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	dateSuchAs:'�?îæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
+	dateInFormatMDY:'�?îæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃÃ (íàïðèìåð "12/31/1999")',
+	email:'�?îæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred at domain.com".',
+	url:'�?îæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
+	currencyDollar:'�?îæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. �?àïðèìåð: $100.00 .',
+	oneRequired:'�?îæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
+	errorPrefix: 'Îøèáêà: ',
+	warningPrefix: 'Âíèìàíèå: '
+});/*
+---
+
+script: Form.Validator.Spanish.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Form.Validator', {
+
+	required:'Este campo es obligatorio.',
+	minLength:'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+	maxLength:'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+	integer:'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+	numeric:'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+	alpha:'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+	alphanum:'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+	dateSuchAs:'Por favor introduce una fecha v&aacute;lida como {date}',
+	dateInFormatMDY:'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred at domain.com".',
+	url:'Por favor introduce una URL v&aacute;lida como http://www.google.com.',
+	currencyDollar:'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+	oneRequired:'Por favor introduce algo para por lo menos una de estas entradas.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No pueden haber espacios en esta entrada.',
+	reqChkByNode: 'No hay elementos seleccionados.',
+	requiredChk: 'Este campo es obligatorio.',
+	reqChkByName: 'Por favor selecciona una {label}.',
+	match: 'Este campo necesita coincidir con el campo {matchName}',
+	startDate: 'la fecha de inicio',
+	endDate: 'la fecha de fin',
+	currendDate: 'la fecha actual',
+	afterDate: 'La fecha debe ser igual o posterior a {label}.',
+	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+	startMonth: 'Por favor selecciona un mes de origen',
+	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+/*
+---
+
+script: Form.Validator.Swedish.js
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Form.Validator', {
+
+	required:'Fältet är obligatoriskt.',
+	minLength:'Ange minst {minLength} tecken (du angav {length} tecken).',
+	maxLength:'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+	integer:'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
+	numeric:'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+	digits:'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
+	alpha:'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	alphanum:'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	dateSuchAs:'Ange ett giltigt datum som t.ex. {date}',
+	dateInFormatMDY:'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+	email:'Ange en giltig e-postadress. Till exempel "erik at domain.com".',
+	url:'Ange en giltig webbadress som http://www.google.com.',
+	currencyDollar:'Ange en giltig belopp. Exempelvis 100,00.',
+	oneRequired:'Vänligen ange minst ett av dessa alternativ.',
+	errorPrefix: 'Fel: ',
+	warningPrefix: 'Varning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
+	reqChkByNode: 'Inga objekt är valda.',
+	requiredChk: 'Detta är ett obligatoriskt fält.',
+	reqChkByName: 'Välj en {label}.',
+	match: 'Detta fält måste matcha {matchName}',
+	startDate: 'startdatumet',
+	endDate: 'slutdatum',
+	currendDate: 'dagens datum',
+	afterDate: 'Datumet bör vara samma eller senare än {label}.',
+	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
+	startMonth: 'Välj en start månad',
+	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'
+
+});/*
+---
+
+script: Form.Validator.Ukrainian.js
+
+description: Form.Validator messages in Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Ukrainian]
+
+...
+*/
+
+MooTools.lang.set('uk-UA', 'Form.Validator', {
+	required:'Це поле повинне бути заповненим.',
+	minLength:'Введіть хоча б {minLength} �?имволів (Ви ввели {length}).',
+	maxLength:'Кількі�?ть �?имволів не може бути більше {maxLength} (Ви ввели {length}).',
+	integer:'Введіть в це поле чи�?ло. Дробові чи�?ла (наприклад 1.25) не дозволені.',
+	numeric:'Введіть в це поле чи�?ло (наприклад "1" або "1.1", або "-1", або "-1.1").',
+	digits:'В цьому полі ви можете викори�?товувати лише цифри і знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).',
+	alpha:'В цьому полі можна викори�?товувати лише латин�?ькі літери (a-z). Пробіли і інші �?имволи заборонені.',
+	alphanum:'В цьому полі можна викори�?товувати лише латин�?ькі літери (a-z) і цифри (0-9). Пробіли і інші �?имволи заборонені.',
+	dateSuchAs:'Введіть коректну дату {date}.',
+	dateInFormatMDY:'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',
+	email:'Введіть коректну адре�?у електронної пошти (наприклад "name at domain.com").',
+	url:'Введіть коректне інтернет-по�?иланн�? (наприклад http://www.google.com).',
+	currencyDollar:'Введіть �?уму в доларах (наприклад "$100.00").',
+	oneRequired:'Заповніть одне з полів.',
+	errorPrefix: 'Помилка: ',
+	warningPrefix: 'Увага: '
+});
+/*
+---
+
+name: Common
+
+description: Jx namespace with methods and classes common to most Jx widgets
+
+license: MIT-style license.
+
+requires:
+ - Core/Class
+ - Core/Element
+ - Core/Browser
+ - Core/Element.Style
+ - Core/Request
+ - Core/Class.Extras
+ - More/Class.Binds
+ - Core/Array
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - More/Element.Measure
+ - More/Lang
+ - Core/Selectors
+ - Core/Slick.Finder
+ - Core/Slick.Parser
+
+provides: [Jx]
+
+css:
+ - license
+ - reset
+ - common
+
+images:
+ - a_pixel.png
+
+...
+ */
+// $Id: common.js 999 2010-11-28 21:00:43Z jonlb at comcast.net $
+/**
+ * Function: $jx
+ * dereferences a DOM Element to a JxLib object if possible and returns
+ * a reference to the object, or null if not defined.
+ */
+function $jx(id) {
+  var widget = null;
+  id = document.id(id);
+  if (id) {
+    widget = id.retrieve('jxWidget');
+    if (!widget && id != document.body) {
+      widget = $jx(id.getParent());
+    }
+  }
+  return widget;
+}
+
+/**
+ * Class: Jx
+ * Jx is a global singleton object that contains the entire Jx library
+ * within it.  All Jx functions, attributes and classes are accessed
+ * through the global Jx object.  Jx should not create any other
+ * global variables, if you discover that it does then please report
+ * it as a bug
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+/* firebug console supressor for IE/Safari/Opera */
+window.addEvent('load',
+function() {
+    if (! ("console" in window)) {
+        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+        "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"],
+            i;
+
+        window.console = {};
+        for (i = 0; i < names.length; ++i) {
+            window.console[names[i]] = function() {};
+        }
+    }
+});
+
+
+// add mutator that sets jxFamily when creating a class so we can check
+// its type
+Class.Mutators.Family = function(self, name) {
+    if ($defined(name)) {
+        self.jxFamily = name;
+        return self;
+    }
+    else if(!$defined(this.prototype.jxFamily)) {
+        this.implement({
+            'jxFamily': self
+        });
+    }
+};
+
+// this replaces the mootools $unlink method with our own version that
+// avoids infinite recursion on Jx objects.
+function $unlink(object) {
+    if (object && object.jxFamily) {
+        return object;
+    }
+    var unlinked, p, i, l;
+    switch ($type(object)) {
+    case 'object':
+        unlinked = {};
+        for (p in object) unlinked[p] = $unlink(object[p]);
+        break;
+    case 'hash':
+        unlinked = new Hash(object);
+        break;
+    case 'array':
+        unlinked = [];
+        for (i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+        break;
+    default:
+        return object;
+    }
+    return unlinked;
+}
+
+/**
+ * Override of mootools-core 1.3's typeOf operator to prevent infinite recursion
+ * when doing typeOf on JxLib objects.
+ *
+var typeOf = this.typeOf = function(item){
+    if (item == null) return 'null';
+    if (item.jxFamily) return item.jxFamily;
+    if (item.$family) return item.$family();
+
+    if (item.nodeName){
+        if (item.nodeType == 1) return 'element';
+        if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+    } else if (typeof item.length == 'number'){
+        if (item.callee) return 'arguments';
+        if ('item' in item) return 'collection';
+    }
+
+    return typeof item;
+};
+
+this.$type = function(object){
+    var type = typeOf(object);
+    if (type == 'elements') return 'array';
+    return (type == 'null') ? false : type;
+};
+*/
+
+/* Setup global namespace.  It is possible to set the global namespace
+ * prior to including jxlib.  This would typically be required only if
+ * the auto-detection of the jxlib base URL would fail.  For instance,
+ * if you combine jxlib with other javascript libraries into a single file
+ * build and call it something without jxlib in the file name, then the
+ * detection of baseURL would fail.  If this happens to you, try adding
+ * Jx = { baseURL: '/path/to/jxlib/'; }
+ * where the path to jxlib contains a file called a_pixel.png (it doesn't
+ * have to include jxlib, just the a_pixel.png file).
+ */
+if (typeof Jx === 'undefined') {
+  var Jx = {};
+}
+
+/**
+ * APIProperty: {String} baseURL
+ * This is the URL that Jx was loaded from, it is
+ * automatically calculated from the script tag
+ * src property that included Jx.
+ *
+ * Note that this assumes that you are loading Jx
+ * from a js/ or lib/ folder in parallel to the
+ * images/ folder that contains the various images
+ * needed by Jx components.  If you have a different
+ * folder structure, you can define Jx's base
+ * by including the following before including
+ * the jxlib javascript file:
+ *
+ * (code)
+ * Jx = {
+ *    baseURL: 'some/path'
+ * }
+ * (end)
+ */
+if (!$defined(Jx.baseURL)) {
+  (function() {
+    var aScripts = document.getElementsByTagName('SCRIPT'),
+        i, s, n, file;
+    for (i = 0; i < aScripts.length; i++) {
+      s = aScripts[i].src;
+      n = s.lastIndexOf('/');
+      file = s.slice(n+1,s.length-1);
+      if (file.contains('jxlib')) {
+        Jx.baseURL = s.slice(0,n);
+        break;
+      }
+    }
+  })();
+}
+/**
+ * APIProperty: {Image} aPixel
+ * aPixel is a single transparent pixel and is the only image we actually
+ * use directly in JxLib code.  If you want to use your own transparent pixel
+ * image or use it from a different location than the install of jxlib
+ * javascript files, you can manually declare it before including jxlib code
+ * (code)
+ * Jx = {
+ *   aPixel: new Element('img', {
+ *     alt: '',
+ *     title: '',
+ *     width: 1,
+ *     height: 1,
+ *     src: 'path/to/a/transparent.png'
+ *   });
+ * }
+ * (end)
+ */
+if (!$defined(Jx.aPixel)) {
+  Jx.aPixel = new Element('img', {
+    alt:'',
+    title:'',
+    src: Jx.baseURL +'/a_pixel.png'
+  });
+}
+
+/**
+ * APIProperty: {Boolean} isAir
+ * indicates if JxLib is running in an Adobe Air environment.  This is
+ * normally auto-detected but you can manually set it by declaring the Jx
+ * namespace before including jxlib:
+ * (code)
+ * Jx = {
+ *   isAir: true
+ * }
+ * (end)
+ */
+if (!$defined(Jx.isAir)) {
+  (function() {
+    /**
+     * Determine if we're running in Adobe AIR.
+     */
+    var aScripts = document.getElementsByTagName('SCRIPT'),
+        src = aScripts[0].src;
+    if (src.contains('app:')) {
+      Jx.isAir = true;
+    } else {
+      Jx.isAir = false;
+    }
+  })();
+}
+
+/**
+ * APIMethod: setLanguage
+ * set the current language to be used by Jx widgets.  This uses the MooTools
+ * lang module.  If an invalid or missing language is requested, the default
+ * rules of MooTools.lang will be used (revert to en-US at time of writing).
+ *
+ * Parameters:
+ * {String} language identifier, the language to set.
+ */
+Jx.setLanguage = function(lang) {
+  Jx.lang = lang;
+  MooTools.lang.setLanguage(Jx.lang);
+};
+
+/**
+ * APIProperty: {String} lang
+ * Checks to see if Jx.lang is already set. If not, it sets it to the default
+ * 'en-US'. We will then set the Motools.lang language to this setting
+ * automatically.
+ *
+ * The language can be changed on the fly at anytime by calling
+ * Jx.setLanguage().
+ * By default all Jx.Widget subclasses will listen for the langChange event of
+ * the Mootools.lang class. It will then call a method, changeText(), if it
+ * exists on the particular widget. You will be able to disable listening for
+ * these changes by setting the Jx.Widget option useLang to false.
+ */
+if (!$defined(Jx.lang)) {
+  Jx.lang = 'en-US';
+}
+
+Jx.setLanguage(Jx.lang);
+
+/**
+ * APIMethod: applyPNGFilter
+ *
+ * Static method that applies the PNG Filter Hack for IE browsers
+ * when showing 24bit PNG's.  Used automatically for img tags with
+ * a class of png24.
+ *
+ * The filter is applied using a nifty feature of IE that allows javascript to
+ * be executed as part of a CSS style rule - this ensures that the hack only
+ * gets applied on IE browsers.
+ *
+ * The CSS that triggers this hack is only in the ie6.css files of the various
+ * themes.
+ *
+ * Parameters:
+ * object {Object} the object (img) to which the filter needs to be applied.
+ */
+Jx.applyPNGFilter = function(o) {
+    var t = Jx.aPixel.src, 
+        s;
+    if (o.src != t) {
+        s = o.src;
+        o.src = t;
+        o.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + s + "',sizingMethod='scale')";
+    }
+};
+
+/**
+ * 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
+/**
+ * APIMethod: addToImgQueue
+ * Request that an image be set to a DOM IMG element src attribute.  This puts
+ * the image into a queue and there are private methods to manage that queue
+ * and limit image loading to 2 at a time.
+ *
+ * Parameters:
+ * obj - {Object} an object containing an element and src
+ * property, where element is the element to update and src
+ * is the url to the image.
+ */
+Jx.addToImgQueue = function(obj) {
+    if (Jx.imgLoaded[obj.src]) {
+        //if this image was already requested (i.e. it's in cache) just set it directly
+        obj.element.src = obj.src;
+    } else {
+        //otherwise stick it in the queue
+        Jx.imgQueue.push(obj);
+        Jx.imgLoaded[obj.src] = true;
+    }
+    //start the queue management process
+    Jx.checkImgQueue();
+};
+
+/**
+ * APIMethod: checkImgQueue
+ *
+ * An internal method that ensures no more than 2 images are loading at a
+ * time.
+ */
+Jx.checkImgQueue = function() {
+    while (Jx.imagesLoading < 2 && Jx.imgQueue.length > 0) {
+        Jx.loadNextImg();
+    }
+};
+
+/**
+ * Method: loadNextImg
+ *
+ * An internal method actually populate the DOM element with the image source.
+ */
+Jx.loadNextImg = function() {
+    var obj = Jx.imgQueue.shift();
+    if (obj) {
+        ++Jx.imagesLoading;
+        obj.element.onload = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.onerror = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.src = obj.src;
+    }
+};
+
+/**
+ * APIMethod: getNumber
+ * safely parse a number and return its integer value.  A NaN value
+ * returns 0.  CSS size values are also parsed correctly.
+ *
+ * Parameters:
+ * n - {Mixed} the string or object to parse.
+ *
+ * Returns:
+ * {Integer} the integer value that the parameter represents
+ */
+Jx.getNumber = function(n, def) {
+    var result = n === null || isNaN(parseInt(n, 10)) ? (def || 0) : parseInt(n, 10);
+    return result;
+};
+
+/**
+ * APIMethod: getPageDimensions
+ * return the dimensions of the browser client area.
+ *
+ * Returns:
+ * {Object} an object containing a width and height property
+ * that represent the width and height of the browser client area.
+ */
+Jx.getPageDimensions = function() {
+    return {
+        width: window.getWidth(),
+        height: window.getHeight()
+    };
+};
+
+/**
+ * APIMethod: type
+ * safely return the type of an object using the mootools type system
+ *
+ * Returns:
+ * {Object} an object containing a width and height property
+ * that represent the width and height of the browser client area.
+ */
+Jx.type = function(obj) {
+  return typeof obj == 'undefined' ? false : obj.jxFamily || $type(obj);
+};
+
+(function($) {
+    // Wrapper for document.id
+
+    /**
+     * Class: Element
+     *
+     * Element is a global object provided by the mootools library.  The
+     * functions documented here are extensions to the Element object provided
+     * by Jx to make cross-browser compatibility easier to achieve.  Most of
+     * the methods are measurement related.
+     *
+     * While the code in these methods has been converted to use MooTools
+     * methods, there may be better MooTools methods to use to accomplish
+     * these things.
+     * Ultimately, it would be nice to eliminate most or all of these and find
+     * the MooTools equivalent or convince MooTools to add them.
+     *
+     * NOTE: Many of these methods can be replaced with mootools-more's
+     * Element.Measure
+     */
+    Element.implement({
+        /**
+         * APIMethod: getBoxSizing
+         * return the box sizing of an element, one of 'content-box' or
+         *'border-box'.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the box sizing of.
+         *
+         * Returns:
+         * {String} the box sizing of the element.
+         */
+        getBoxSizing: function() {
+            var result = 'content-box',
+                cm,
+                sizing;
+            if (Browser.Engine.trident || Browser.Engine.presto) {
+                cm = document["compatMode"];
+                if (cm == "BackCompat" || cm == "QuirksMode") {
+                    result = 'border-box';
+                } else {
+                    result = 'content-box';
+                }
+            } else {
+                if (arguments.length === 0) {
+                    node = document.documentElement;
+                }
+                sizing = this.getStyle("-moz-box-sizing");
+                if (!sizing) {
+                    sizing = this.getStyle("box-sizing");
+                }
+                result = (sizing ? sizing: 'content-box');
+            }
+            return result;
+        },
+        /**
+         * APIMethod: getContentBoxSize
+         * return the size of the content area of an element.  This is the
+         * size of the element less margins, padding, and borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the content size of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the content area of the measured element.
+         */
+        getContentBoxSize: function() {
+            var s = this.getSizes(['padding', 'border']);
+            return {
+                width: this.offsetWidth - s.padding.left - s.padding.right - s.border.left - s.border.right,
+                height: this.offsetHeight - s.padding.bottom - s.padding.top - s.border.bottom - s.border.top
+            };
+        },
+        /**
+         * APIMethod: getBorderBoxSize
+         * return the size of the border area of an element.  This is the size
+         * of the element less margins.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the border sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the border area of the measured element.
+         */
+        getBorderBoxSize: function() {
+            return {
+                width: this.offsetWidth,
+                height: this.offsetHeight
+            };
+        },
+
+        /**
+         * APIMethod: getMarginBoxSize
+         * return the size of the margin area of an element.  This is the size
+         * of the element plus margins.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the margin sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the margin area of the measured element.
+         */
+        getMarginBoxSize: function() {
+            var s = this.getSizes(['margin']);
+            return {
+                width: this.offsetWidth + s.margin.left + s.margin.right,
+                height: this.offsetHeight + s.margin.top + s.margin.bottom
+            };
+        },
+        /**
+         * APIMethod: 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 = {},
+                e,
+                n;
+            which.each(function(style) {
+                result[style] = {};
+                edges.each(function(edge) {
+                    e = (style == 'border') ? edge + '-width': edge;
+                    n = this.getStyle(style + '-' + e);
+                    result[style][edge] = n === null || isNaN(parseInt(n, 10)) ? 0: parseInt(n, 10);
+                },
+                this);
+            },
+            this);
+            return result;
+        },
+        /**
+         * APIMethod: setContentBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the content
+         * area of the element is the requested size and the resulting
+         * size of the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to set the content area of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setContentBoxSize: function(size) {
+            var m,
+                width,
+                height;
+            if (this.getBoxSizing() == 'border-box') {
+                m = this.measure(function() {
+                    return this.getSizes(['padding', 'border']);
+                });
+                if ($defined(size.width)) {
+                    width = size.width + m.padding.left + m.padding.right + m.border.left + m.border.right;
+                    if (width < 0) {
+                        width = 0;
+                    }
+                    this.setStyle('width', width);
+                }
+                if ($defined(size.height)) {
+                    height = size.height + m.padding.top + m.padding.bottom + m.border.top + m.border.bottom;
+                    if (height < 0) {
+                        height = 0;
+                    }
+                    this.setStyle('height', height);
+                }
+            } else {
+                if ($defined(size.width) && size.width >= 0) {
+                  this.setStyle('width', width);
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                  this.setStyle('height', height);
+                }
+            }
+        },
+        /**
+         * APIMethod: setBorderBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the border
+         * size of the element is the requested size and the resulting
+         * content areaof the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to set the border size of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setBorderBoxSize: function(size) {
+            var m, 
+                width, 
+                height;
+            if (this.getBoxSizing() == 'content-box') {
+                m = this.measure(function() {
+                    return this.getSizes();
+                });
+
+                if ($defined(size.width)) {
+                    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.setStyle('width', width);
+                }
+                if ($defined(size.height)) {
+                    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.setStyle('height', height);
+                }
+            } else {
+                if ($defined(size.width) && size.width >= 0) {
+                  this.setStyle('width', width);
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                  this.setStyle('height', height);
+                }
+            }
+        },
+
+        /**
+         * APIMethod: descendantOf
+         * determines if the element is a descendent of the reference node.
+         *
+         * Parameters:
+         * node - {HTMLElement} the reference node
+         *
+         * Returns:
+         * {Boolean} true if the element is a descendent, false otherwise.
+         */
+        descendantOf: function(node) {
+            var parent = document.id(this.parentNode);
+            while (parent != node && parent && parent.parentNode && parent.parentNode != parent) {
+                parent = document.id(parent.parentNode);
+            }
+            return parent == node;
+        },
+
+        /**
+         * APIMethod: findElement
+         * search the parentage of the element to find an element of the given
+         * tag name.
+         *
+         * Parameters:
+         * type - {String} the tag name of the element type to search for
+         *
+         * Returns:
+         * {HTMLElement} the first node (this one or first parent) with the
+         * requested tag name or false if none are found.
+         */
+        findElement: function(type) {
+            var o = this,
+                tagName = o.tagName;
+            while (o.tagName != type && o && o.parentNode && o.parentNode != o) {
+                o = document.id(o.parentNode);
+            }
+            return o.tagName == type ? o: false;
+        }
+    });
+    /**
+     * Class: Array
+     * Extensions to the javascript array object
+     */
+    Array.implement({
+        /**
+         * APIMethod: 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 = this[a];
+            this[a] = this[b];
+            this[b] = temp;
+        }
+    });
+})(document.id || $);
+// End Wrapper for document.id
+/*
+---
+
+name: Jx.Styles
+
+description: A singleton object useful for dynamically creating and manipulating CSS styles
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Styles]
+
+...
+ */
+/**
+ * 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
+ *
+ * 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(),
+    /**
+     * APIMethod: getCssRule
+     * retrieve a reference to a CSS rule in a specific style sheet based on
+     * its selector.  If the rule does not exist, create it.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * 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),
+            rule = null,
+            i;
+        if (ss.indicies) {
+            i = ss.indicies.indexOf(selector);
+            if (i == -1) {
+                rule = this.insertCssRule(selector, '', styleSheetName);
+            } else {
+                if (Browser.Engine.trident) {
+                    rule = ss.sheet.rules[i];
+                } else {
+                    rule = ss.sheet.cssRules[i];
+                }
+            }
+        }
+        return rule;
+    },
+    /**
+     * APIMethod: insertCssRule
+     * insert a new dynamic rule into the given stylesheet.  If no name is
+     * given for the stylesheet then the default stylesheet is used.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * declaration - <String> CSS-formatted rules to include.  May be empty,
+     * in which case you may want to use the returned rule object to
+     * manipulate styles
+     * 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),
+            rule,
+            text = selector + " {" + declaration + "}",
+            index;
+        if (Browser.Engine.trident) {
+            if (declaration == '') {
+                //IE requires SOME text for the declaration. Passing '{}' will
+                //create an empty rule.
+                declaration = '{}';
+            }
+            index = ss.styleSheet.addRule(selector,declaration);
+            rule = ss.styleSheet.rules[index];
+        } else {
+            ss.sheet.insertRule(text, ss.indicies.length);
+            rule = ss.sheet.cssRules[ss.indicies.length];
+        }
+        ss.indicies.push(selector);
+        return rule;
+    },
+    /**
+     * APIMethod: removeCssRule
+     * removes a CSS rule from the named stylesheet.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * styleSheetName - <String> the name of the sheet to remove the rule
+     * from,  or empty to remove them from the default sheet.
+     *
+     * Returns:
+     * <Boolean> true if the rule was removed, false if it was not.
+     */
+    removeCssRule: function (selector, styleSheetName) {
+        var ss = this.getDynamicStyleSheet(styleSheetName),
+            i = ss.indicies.indexOf(selector),
+            result = false;
+        ss.indicies.splice(i, 1);
+        if (Browser.Engine.trident) {
+            ss.removeRule(i);
+            result = true;
+        } else {
+            ss.sheet.deleteRule(i);
+            result = true;
+        }
+        return result;
+    },
+    /**
+     * APIMethod: 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').inject(document.head);
+            sheet.indicies = [];
+            this.dynamicStyleMap.set(name, sheet);
+        }
+        return this.dynamicStyleMap.get(name);
+    },
+    /**
+     * APIMethod: enableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to enable
+     */
+    enableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = false;
+    },
+    /**
+     * APIMethod: disableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to disable
+     */
+    disableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = true;
+    },
+    /**
+     * APIMethod: removeStyleSheet
+     * Removes a style sheet
+     *
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    removeStyleSheet: function (name) {
+      this.disableStyleSheet(name);
+      this.getDynamicStyleSheet(name).dispose();
+      this.dynamicStyleMap.erase(name);
+    },
+    /**
+     * APIMethod: isStyleSheetDefined
+     * Determined if the passed in name is a defined dynamic style sheet.
+     *
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    isStyleSheetDefined: function (name) {
+      return this.dynamicStyleMap.has(name);
+    }
+}))();/*
+---
+
+name: Jx.Object
+
+description: Base class for all other object in the JxLib framework.
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Object]
+
+...
+ */
+// $Id: object.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * Class: Jx.Object
+ * Base class for all other object in the JxLib framework. This class
+ * implements both mootools mixins Events and Options so the rest of the
+ * classes don't need to.
+ *
+ * The Initialization Pipeline:
+ * Jx.Object provides a default initialize method to construct new instances
+ * of objects that inherit from it.  No sub-class should override initialize
+ * unless you know exactly what you're doing.  Instead, the initialization
+ * pipeline provides an init() method that is intended to be overridden in
+ * sub-classes to provide class-specific initialization as part of the
+ * initialization pipeline.
+ *
+ * The basic initialization pipeline for a Jx.Object is to parse the
+ * parameters provided to initialize(), separate out options from other formal
+ * parameters based on the parameters property of the class, call init() and
+ * initialize plugins.
+ *
+ * Parsing Parameters:
+ * Because each sub-class no longer has an initialize method, it no longer has
+ * direct access to parameters passed to the constructor.  Instead, a
+ * sub-class is expected to provide a parameters attribute with an array of
+ * parameter names in the order expected.  Jx.Object will enumerate the
+ * attributes passed to its initialize method and automatically place them
+ * in the options object under the appropriate key (the value from the
+ * array).  Parameters not found will not be present or will be null.
+ *
+ * The default parameters are a single options object which is merged with
+ * the options attribute of the class.
+ *
+ * Calling Init:
+ * Jx.Object fires the event 'preInit' before calling the init() method,
+ * calls the init() method, then fires the 'postInit' event.  It is expected
+ * that most sub-class specific initialization will happen in the init()
+ * method.  A sub-class may hook preInit and postInit events to perform tasks
+ * in one of two ways.
+ *
+ * First, simply send onPreInit and onPostInit functions via the options
+ * object as follows (they could be standalone functions or functions of
+ * another object setup using .bind())
+ *
+ * (code)
+ * var preInit = function () {}
+ * var postInit = function () {}
+ *
+ * var options = {
+ *   onPreInit: preInit,
+ *   onPostInit: postInit,
+ *   ...other options...
+ * };
+ *
+ * var dialog = new Jx.Dialog(options);
+ * (end)
+ *
+ * The second method you can use is to override the initialize method
+ *
+ * (code)
+ * var MyClass = new Class({
+ *   Family: 'MyClass',
+ *   initialize: function() {
+ *     this.addEvent('preInit', this.preInit.bind(this));
+ *     this.addEvent('postInit', this.postInit.bind(this));
+ *     this.parent.apply(this, arguments);
+ *   },
+ *   preInit: function() {
+ *     // something just before init() is called
+ *   },
+ *   postInit: function() {
+ *     // something just after init() is called
+ *   },
+ *   init: function() {
+ *     this.parent();
+ *     // initialization code here
+ *   }
+ * });
+ * (end)
+ *
+ * When the object finishes initializing itself (including the plugin
+ * initialization) it will fire off the initializeDone event. You can hook
+ * into this event in the same way as the events mentioned above.
+ *
+ * Plugins:
+ * Plugins provide pieces of additional, optional, functionality. They are not
+ * necessary for the proper function of an object. All plugins should be
+ * located in the Jx.Plugin namespace and they should be further segregated by
+ * applicable object. While all objects can support plugins, not all of them
+ * have the automatic instantiation of applicable plugins turned on. In order
+ * to turn this feature on for an object you need to set the pluginNamespace
+ * property of the object. The following is an example of setting the
+ * property:
+ *
+ * (code)
+ * var MyClass = new Class({
+ *   Extends: Jx.Object,
+ *   pluginNamespace: 'MyClass'
+ * };
+ * (end)
+ *
+ * The absence of this property does not mean you cannot attach a plugin to an
+ * object. It simply means that you can't have Jx.Object create the
+ * plugin for you.
+ *
+ * There are four ways to attach a plugin to an object. First, simply
+ * instantiate the plugin yourself and call its attach() method (other class
+ * options left out for the sake of simplicity):
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid();
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * APlugin.attach(MyGrid);
+ * (end)
+ *
+ * Second, you can instantiate the plugin first and pass it to the object
+ * through the plugins array in the options object.
+ *
+ * (code)
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * var MyGrid = new Jx.Grid({plugins: [APlugin]});
+ * (end)
+ *
+ * The third way is to pass the information needed to instantiate the plugin
+ * in the plugins array of the options object:
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: [{
+ *      name: 'Selector',
+ *      options: {}    //options needed to create this plugin
+ *   },{
+ *      name: 'Sorter',
+ *      options: {}
+ *   }]
+ * });
+ * (end)
+ *
+ * The final way, if the plugin has no options, is to pass the name of the
+ * plugin as a simple string in the plugins array.
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: ['Selector','Sorter']
+ * });
+ * (end)
+ *
+ * Part of the process of initializing plugins is to call prePluginInit() and
+ * postPluginInit(). These events provide you access to the object just before
+ * and after the plugins are initialized and/or attached to the object using
+ * methods 2 and 3 above. You can hook into these in the same way that you
+ * hook into the preInit() and postInit() events.
+ *
+ * Destroying Jx.Object Instances:
+ * Jx.Object provides a destroy method that cleans up potential memory leaks
+ * when you no longer need an object.  Sub-classes are expected to implement
+ * a cleanup() method that provides specific cleanup code for each
+ * sub-class.  Remember to call this.parent() when providing a cleanup()
+ * method. Destroy will also fire off 2 events: preDestroy and postDestroy.
+ * You can hook into these methods in the same way as the init or plugin
+ * events.
+ *
+ * The Family Attribute:
+ * the Family attribute of a class is used internally by JxLib to identify Jx
+ * objects within mootools.  The actual value of Family is unimportant to Jx.
+ * If you do not provide a Family, a class will inherit it's base class family
+ * up to Jx.Object.  Family is useful when debugging as you will be able to
+ * identify the family in the firebug inspector, but is not as useful for
+ * coding purposes as it does not allow for inheritance.
+ *
+ * Events:
+ *
+ * preInit
+ * postInit
+ * prePluginInit
+ * postPluginInit
+ * initializeDone
+ * preDestroy
+ * postDestroy
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Object = new Class({
+    Family: "Jx.Object",
+    Implements: [Options, Events],
+    plugins: null,
+    pluginNamespace: 'Other',
+    /**
+     * Constructor: Jx.Object
+     * create a new instance of Jx.Object
+     *
+     * Parameters:
+     * options - {Object} optional parameters for creating an object.
+     */
+    parameters: ['options'],
+
+    options: {
+      /**
+       * Option: useLang
+       * Turns on this widget's ability to react to changes in
+       * the default language. Handy for changing text out on the fly.
+       *
+       * TODO: Should this be enabled or disabled by default?
+       */
+      useLang: true,
+      /**
+       * Option: plugins
+       * {Array} an array of plugins to add to the object.
+       */
+      plugins: null
+    },
+
+    bound: null,
+
+    initialize: function(){
+        this.plugins = new Hash();
+        this.bound = {};
+        //normalize arguments
+        var numArgs = arguments.length,
+            options = {},
+            parameters = this.parameters,
+            numParams,
+            index;
+
+        if (numArgs > 0) {
+            if (numArgs === 1
+                    && (Jx.type(arguments[0])==='object' || Jx.type(arguments[0])==='Hash')
+                    && parameters.length === 1
+                    && parameters[0] === 'options') {
+                options = arguments[0];
+            } else {
+                numParams = parameters.length;
+                index;
+                if (numParams <= numArgs) {
+                    index = numParams;
+                } else {
+                    index = numArgs;
+                }
+                for (var i = 0; i < index; i++) {
+                    if (parameters[i] === 'options') {
+                        $extend(options, arguments[i]);
+                    } else {
+                        options[parameters[i]] = arguments[i];
+                    }
+                }
+            }
+        }
+
+        this.setOptions(options);
+
+        this.bound.changeText = this.changeText.bind(this);
+        if (this.options.useLang) {
+            MooTools.lang.addEvent('langChange', this.bound.changeText);
+        }
+
+        this.fireEvent('preInit');
+        this.init();
+        this.fireEvent('postInit');
+        this.fireEvent('prePluginInit');
+        this.initPlugins();
+        this.fireEvent('postPluginInit');
+        this.fireEvent('initializeDone');
+    },
+
+    /**
+     * Method: initPlugins
+     * internal function to initialize plugins on object creation
+     */
+    initPlugins: function () {
+        var p;
+        // 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
+                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()])) {
+                        p = new Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                      } else {
+                        p = new Jx.Adaptor[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                      }
+                        p.attach(this);
+                    } else if (Jx.type(plugin) === 'string') {
+                        //this is a name for a plugin.
+                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.capitalize()])) {
+                        p = new Jx.Plugin[this.pluginNamespace][plugin.capitalize()]();
+                      } else {
+                        p = new Jx.Adaptor[this.pluginNamespace][plugin.capitalize()]();
+                      }
+                        p.attach(this);
+                    }
+                }, this);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     * destroy a Jx.Object, safely cleaning up any potential memory
+     * leaks along the way.  Uses the cleanup method of an object to
+     * actually do the cleanup.
+     * Emits the preDestroy event before cleanup and the postDestroy event
+     * after cleanup.
+     */
+    destroy: function () {
+        this.fireEvent('preDestroy');
+        this.cleanup();
+        this.fireEvent('postDestroy');
+    },
+
+    /**
+     * Method: cleanup
+     * to be implemented by subclasses to do the actual work of destroying
+     * an object.
+     */
+    cleanup: function () {
+        //detach plugins
+        if (this.plugins.getLength > 0) {
+            this.plugins.each(function (plugin) {
+                plugin.detach();
+                plugin.destroy();
+            }, this);
+        }
+        this.plugins.empty();
+        if (this.options.useLang && $defined(this.bound.changeText)) {
+            MooTools.lang.removeEvent('langChange', this.bound.changeText);
+        }
+        this.bound = null;
+    },
+
+    /**
+     * Method: init
+     * virtual initialization method to be implemented by sub-classes
+     */
+    init: $empty,
+
+    /**
+     * APIMethod: registerPlugin
+     * This method is called by a plugin that has its attach method
+     * called.
+     *
+     * Parameters:
+     * plugin - the plugin to register with this object
+     */
+    registerPlugin: function (plugin) {
+        if (!this.plugins.has(plugin.name)) {
+            this.plugins.set(plugin.name,  plugin);
+        }
+    },
+    /**
+     * APIMethod: deregisterPlugin
+     * his method is called by a plugin that has its detach method
+     * called.
+     *
+     * Parameters:
+     * plugin - the plugin to deregister.
+     */
+    deregisterPlugin: function (plugin) {
+        if (this.plugins.has(plugin.name)) {
+            this.plugins.erase(plugin.name);
+        }
+    },
+
+    /**
+     * APIMethod: getPlugin
+     * Allows a developer to get a reference to a plugin with only the
+     * name of the plugin.
+     *
+     * Parameters:
+     * name - the name of the plugin as defined in the plugin's name property
+     */
+    getPlugin: function (name) {
+        if (this.plugins.has(name)) {
+            return this.plugins.get(name);
+        }
+    },
+
+    /**
+     * APIMethod: getText
+     *
+     * returns the text for a jx.widget used in a label.
+     *
+     * Parameters:
+     * val - <String> || <Function> || <Object> = { set: '', key: ''[, value: ''] } for a MooTools.lang object
+     */
+    getText: function(val) {
+      var result = '';
+      if (Jx.type(val) == 'string' || Jx.type(val) == 'number') {
+        result = val;
+      } else if (Jx.type(val) == 'function') {
+        result = val();
+      } else if (Jx.type(val) == 'object' && $defined(val.set) && $defined(val.key)) {
+        // COMMENT: just an idea how a localization object could be stored to the instance if needed somewhere else and options change?
+        this.i18n = val;
+        if($defined(val.value)) {
+          result = MooTools.lang.get(val.set, val.key)[val.value];
+        }else{
+          result = MooTools.lang.get(val.set, val.key);
+        }
+      }
+      return result;
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText : $empty,
+
+    /**
+     * Method: generateId
+     * Used to generate a unique ID for Jx Objects.
+     */
+    generateId: function(prefix){
+        prefix = (prefix) ? prefix : 'jx-';
+        var uid = $uid(this);
+        delete this.uid;
+        return prefix + uid;
+    }
+});
+/*
+---
+
+name: Locale.English.US
+
+description: Default translations of text strings used in JX for US english (en-US)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.English.US]
+
+...
+ */
+MooTools.lang.set('en-US', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Working ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'close this notice'
+	},
+	progressbar: {
+		messageText: 'Loading...',
+		progressText: '{progress} of {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Browse...'
+	},
+	'formatter.boolean': {
+		'true': 'Yes',
+		'false': 'No'
+	},
+	'formatter.currency': {
+		sign: '$'
+	},
+	'formatter.number': {
+		decimalSeparator: '.',
+    thousandsSeparator: ','
+	},
+	splitter: {
+		barToolTip: 'drag this bar to resize'
+	},
+  panelset: {
+    barToolTip: 'drag this bar to resize'
+  },
+	panel: {
+		collapseTooltip: 'Collapse/Expand Panel',
+    collapseLabel: 'Collapse',
+    expandLabel: 'Expand',
+    maximizeTooltip: 'Maximize Panel',
+    maximizeLabel: 'Maximize',
+    restoreTooltip: 'Restore Panel',
+    restoreLabel: 'Restore',
+    closeTooltip: 'Close Panel',
+    closeLabel: 'Close'
+	},
+	confirm: {
+		affirmativeLabel: 'Yes',
+    negativeLabel: 'No'
+	},
+	dialog: {
+		resizeToolTip: 'Resize dialog'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Cancel'
+	},
+	upload: {
+		buttonText: 'Upload Files'
+	},
+	'plugin.resize': {
+	  tooltip: 'Drag to resize, double click to auto-size.'
+	},
+  'plugin.editor': {
+    submitButton: 'Save',
+    cancelButton: 'Cancel'
+  }
+});/*
+---
+
+name: Jx.Widget
+
+description: Base class for all widgets (visual classes) in the JxLib Framework.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Stack
+ - Locale.English.US
+
+provides: [Jx.Widget]
+
+css:
+ - chrome
+
+images:
+ - spinner_16.gif
+ - spinner_24.gif
+
+optional:
+ - More/Spinner
+
+...
+ */
+// $Id: widget.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * 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
+ *
+ * o using an existing element, by object reference
+ *
+ * o using an HTML string
+ *
+ * o using a URL to get the content remotely
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Busy States:
+ *
+ * Any widget can be set as temporarily busy by calling the setBusy(true)
+ * method and then as idle by calling setBusy(false).  By default, busy
+ * widgets display an event mask that prevents them from being clicked and
+ * a spinner image with a message.  By default, there are two configurations
+ * for the spinner image and message, one for 'small' widgets like buttons
+ * and inputs, and one for larger widgets like panels and dialogs.  The
+ * framework automatically chooses the most appropriate configuration so you
+ * don't need to worry about it unless you want to customize it.
+ *
+ * You can disable this behaviour entirely by setting busyMask: false in the
+ * widget options when creating the widget.
+ *
+ * The mask and spinner functionality is provided by the MooTools Spinner
+ * class.  You can use any options documented for Spinner or Mask by setting
+ * the maskOptions option when creating a widget.
+ *
+ * Events:
+ * Jx.Widget has several events called during it's lifetime (in addition to
+ * the ones for its base class <Jx.Object>).
+ *
+ * preRender - called before rendering begins
+ * postRender - called after rendering is done
+ * deferRender - called when the deferRender option is set to true. The first
+ *      two events (pre- and post- render will NOT be called if deferRender is
+ *      set to true).
+ * contentLoaded - called after content has been loaded successfully
+ * contentLoadFailed - called if content can not be loaded for some reason
+ * addTo - called when a widget is added to another element or widget
+ * busy - called just before the busy mask is rendered/removed
+ *
+ * MooTools.Lang Keys:
+ * widget.busyMessage - sets the message of the waiter component when used
+ */
+Jx.Widget = new Class({
+    Family: "Jx.Widget",
+    Extends: Jx.Object,
+
+    options: {
+        /* Option: id
+         * (optional) {String} an HTML ID to assign to the widget
+         */
+        id: null,
+        /**
+         * 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,
+        /**
+         * Option: loadOnDemand
+         * {boolean} ajax content will only be loaded if the action is requested
+         * (like loading the content into a tab when activated)
+         */
+        loadOnDemand : false,
+        /**
+         * Option: cacheContent
+         * {boolean} determine whether the content should be loaded every time
+         * or if it's being cached
+         */
+        cacheContent : true,
+        /**
+         * Option: template
+         * the default HTML structure of this widget.  The default template
+         * is just a div with a class of jxWidget in the base class
+         */
+        template: '<div class="jxWidget"></div>',
+        /**
+         * Option: busyClass
+         * {String} a CSS class name to apply to busy mask when a widget is
+         * set as busy.  The default is 'jxBusy'.
+         */
+        busyClass: 'jxBusy',
+        /**
+         * Option: busyMask
+         * {Object} an object of options to pass to the MooTools Spinner
+         * when masking a busy object.  Set to false if you do not want
+         * to use the busy mask.
+         */
+        busyMask: {
+          'class': 'jxSpinner jxSpinnerLarge',
+          img: {'class':'jxSpinnerImage'},
+          content: {'class':'jxSpinnerContent'},
+          messageContainer: {'class':'jxSpinnerMessage'},
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          },
+          fx: true
+        },
+        /**
+         * Option: deferRender
+         * Used to defer rendering of a widget to a later time. Useful when
+         * we need data or other information not at hand at the moment
+         * of Widget instantiation. If set to true, the user will need to call
+         * render() at some later time. The only drawback to doing so will be
+         * the loss of preRender and postRender events.
+         */
+        deferRender: false
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxWidget'
+    }),
+
+    /**
+     * Property: busy
+     * {Boolean} is the widget currently busy?  This should be considered
+     * an internal property, use the API methods <Jx.Widget::setBusy> and
+     * <Jx.Widget::isBusy> to manage the busy state of a widget.
+     */
+    busy: false,
+
+    /**
+     * Property: domObj
+     * The HTMLElement that represents this widget.
+     */
+    domObj: null,
+
+    /**
+     * Property: contentIsLoaded
+     * {Boolean} 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,
+
+    /**
+     * Method: init
+     * sets up the base widget code and runs the render function.  Called
+     * by the Jx.Object framework for object initialization, should not be
+     * called directly.
+     */
+    init: function(){
+        if (!this.options.deferRender) {
+            this.fireEvent('preRender');
+            this.render();
+            this.fireEvent('postRender');
+        } else {
+            this.fireEvent('deferRender');
+        }
+    },
+
+    /**
+     * APIMethod: loadContent
+     *
+     * triggers loading of content based on options set for the current
+     * object.
+     *
+     * Parameters:
+     * element - {Object} the element to insert the content into
+     *
+     * Events:
+     *
+     * ContentLoader adds the following events to an object.  You can
+     * register for these events using the addEvent method or by providing
+     * callback functions via the on{EventName} properties in the options
+     * object
+     *
+     * contentLoaded - called when the content has been loaded.  If the
+     *     content is not asynchronous then this is called before loadContent
+     *     returns.
+     * contentLoadFailed - called if the content fails to load, primarily
+     *     useful when using the contentURL method of loading content.
+     */
+    loadContent: function(element) {
+        var c,
+            options = this.options,
+            timeout;
+        element = document.id(element);
+        if (options.content) {
+            if (options.content.domObj) {
+                c = document.id(options.content.domObj);
+            } else {
+                c = document.id(options.content);
+            }
+            if (c) {
+                if (options.content.addTo) {
+                    options.content.addTo(element);
+                } else {
+                    element.appendChild(c);
+                }
+                this.contentIsLoaded = true;
+            } else {
+                element.innerHTML = options.content;
+                this.contentIsLoaded = true;
+            }
+        } else if (options.contentURL) {
+            this.contentIsLoaded = false;
+            this.req = new Request({
+                url: options.contentURL,
+                method:'get',
+                evalScripts:true,
+                onRequest:(function() {
+                  if(options.loadOnDemand) {
+                      this.setBusy(true);
+                  }
+                }).bind(this),
+                onSuccess:(function(html) {
+                    element.innerHTML = html;
+                    this.contentIsLoaded = true;
+                    if (Jx.isAir){
+                        $clear(this.reqTimeout);
+                    }
+                    this.setBusy(false);
+                    this.fireEvent('contentLoaded', this);
+                }).bind(this),
+                onFailure: (function(){
+                    this.contentIsLoaded = true;
+                    this.fireEvent('contentLoadFailed', this);
+                    this.setBusy(false);
+                }).bind(this),
+                headers: {'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'}
+            });
+            this.req.send();
+            if (Jx.isAir) {
+                timeout = $defined(options.timeout) ? options.timeout : 10000;
+                this.reqTimeout = this.checkRequest.delay(timeout, this);
+            }
+        } else {
+            this.contentIsLoaded = true;
+        }
+        if (options.contentId) {
+            element.id = this.options.contentId;
+        }
+        if (this.contentIsLoaded) {
+            this.fireEvent('contentLoaded', this);
+        }
+    },
+
+    /**
+     * APIMethod: position
+     * positions an element relative to another element
+     * based on the provided options.  Positioning rules are
+     * a string with two space-separated values.  The first value
+     * references the parent element and the second value references
+     * the thing being positioned.  In general, multiple rules can be
+     * considered by passing an array of rules to the horizontal and
+     * vertical options.  The position method will attempt to position
+     * the element in relation to the relative element using the rules
+     * specified in the options.  If the element does not fit in the
+     * viewport using the rule, then the next rule is attempted.  If
+     * all rules fail, the last rule is used and element may extend
+     * outside the viewport.  Horizontal and vertical rules are
+     * processed independently.
+     *
+     * Horizontal Positioning:
+     * Horizontal values are 'left', 'center', 'right', and numeric values.
+     * Some common rules are:
+     * o 'left left' is interpreted as aligning the left
+     * edge of the element to be positioned with the left edge of the
+     * reference element.
+     * o 'right right' aligns the two right edges.
+     * o 'right left' aligns the left edge of the element to the right of
+     * the reference element.
+     * o 'left right' aligns the right edge of the element to the left
+     * edge of the reference element.
+     *
+     * Vertical Positioning:
+     * Vertical values are 'top', 'center', 'bottom', and numeric values.
+     * Some common rules are:
+     * o 'top top' is interpreted as aligning the top
+     * edge of the element to be positioned with the top edge of the
+     * reference element.
+     * o 'bottom bottom' aligns the two bottom edges.
+     * o 'bottom top' aligns the top edge of the element to the bottom of
+     * the reference element.
+     * o 'top bottom' aligns the bottom edge of the element to the top
+     * edge of the reference element.
+     *
+     * Parameters:
+     * element - the element to position
+     * relative - the element to position relative to
+     * options - the positioning options, see list below.
+     *
+     * Options:
+     * horizontal - the horizontal positioning rule to use to position the
+     *    element.  Valid values are 'left', 'center', 'right', and a numeric
+     *    value.  The default value is 'center center'.
+     * vertical - the vertical positioning rule to use to position the
+     *    element.  Valid values are 'top', 'center', 'bottom', and a numeric
+     *    value.  The default value is 'center center'.
+     * offsets - an object containing numeric pixel offset values for the
+     *    object being positioned as top, right, bottom and left properties.
+     */
+    position: function(element, relative, options) {
+        element = document.id(element);
+        relative = document.id(relative);
+        var hor = $splat(options.horizontal || ['center center']),
+            ver = $splat(options.vertical || ['center center']),
+            offsets = $merge({top:0,right:0,bottom:0,left:0}, options.offsets || {}),
+            coords = relative.getCoordinates(), //top, left, width, height,
+            page, 
+            scroll,
+            size,
+            left,
+            rigbht,
+            top,
+            bottom,
+            n,
+            parts;
+        if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
+            page = Jx.getPageDimensions();
+            scroll = document.id(document.body).getScroll();
+        } else {
+            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
+            // appear in the right part of the page.
+            coords.left += scroll.x;
+            coords.top += scroll.y;
+        } else if (element.parentNode == relative) {
+            // if the element is opening *inside* its relative, we want
+            // it to position correctly within it so top/left becomes
+            // the reference system.
+            coords.left = 0;
+            coords.top = 0;
+        }
+        size = element.getMarginBoxSize(); //width, height
+        if (!hor.some(function(opt) {
+            parts = opt.split(' ');
+            if (parts.length != 2) {
+                return false;
+            }
+            if (!isNaN(parseInt(parts[0],10))) {
+                n = parseInt(parts[0],10);
+                if (n>=0) {
+                    left = n;
+                } else {
+                    left = coords.left + coords.width + n;
+                }
+            } else {
+                switch(parts[0]) {
+                    case 'right':
+                        left = coords.left + coords.width;
+                        break;
+                    case 'center':
+                        left = coords.left + Math.round(coords.width/2);
+                        break;
+                    case 'left':
+                    default:
+                        left = coords.left;
+                        break;
+                }
+            }
+            if (!isNaN(parseInt(parts[1],10))) {
+                n = parseInt(parts[1],10);
+                if (n<0) {
+                    right = left + n;
+                    left = right - size.width;
+                } else {
+                    left += n;
+                    right = left + size.width;
+                }
+                right = coords.left + coords.width + parseInt(parts[1],10);
+                left = right - size.width;
+            } else {
+                switch(parts[1]) {
+                    case 'left':
+                        left -= offsets.left;
+                        right = left + size.width;
+                        break;
+                    case 'right':
+                        left += offsets.right;
+                        right = left;
+                        left = left - size.width;
+                        break;
+                    case 'center':
+                    default:
+                        left = left - Math.round(size.width/2);
+                        right = left + size.width;
+                        break;
+                }
+            }
+            return (left >= scroll.x && right <= scroll.x + page.width);
+        })) {
+            // all failed, snap the last position onto the page as best
+            // we can - can't do anything if the element is wider than the
+            // space available.
+            if (right > page.width) {
+                left = scroll.x + page.width - size.width;
+            }
+            if (left < 0) {
+                left = 0;
+            }
+        }
+        element.setStyle('left', left);
+
+        if (!ver.some(function(opt) {
+          parts = opt.split(' ');
+          if (parts.length != 2) {
+            return false;
+          }
+          if (!isNaN(parseInt(parts[0],10))) {
+            top = parseInt(parts[0],10);
+          } else {
+            switch(parts[0]) {
+              case 'bottom':
+                top = coords.top + coords.height;
+                break;
+              case 'center':
+                top = coords.top + Math.round(coords.height/2);
+                break;
+              case 'top':
+              default:
+                top = coords.top;
+                break;
+            }
+          }
+          if (!isNaN(parseInt(parts[1],10))) {
+              var n = parseInt(parts[1],10);
+              if (n>=0) {
+                  top += n;
+                  bottom = top + size.height;
+              } else {
+                  bottom = top + n;
+                  top = bottom - size.height;
+              }
+          } else {
+              switch(parts[1]) {
+                  case 'top':
+                      top -= offsets.top;
+                      bottom = top + size.height;
+                      break;
+                  case 'bottom':
+                      top += offsets.bottom;
+                      bottom = top;
+                      top = top - size.height;
+                      break;
+                  case 'center':
+                  default:
+                      top = top - Math.round(size.height/2);
+                      bottom = top + size.height;
+                      break;
+              }
+          }
+          return (top >= scroll.y && bottom <= scroll.y + page.height);
+      })) {
+          // all failed, snap the last position onto the page as best
+          // we can - can't do anything if the element is higher than the
+          // space available.
+          if (bottom > page.height) {
+              top = scroll.y + page.height - size.height;
+          }
+          if (top < 0) {
+              top = 0;
+          }
+      }
+      element.setStyle('top', top);
+
+      /* update the jx layout if necessary */
+      var jxl = element.retrieve('jxLayout');
+      if (jxl) {
+          jxl.options.left = left;
+          jxl.options.top = top;
+      }
+    },
+
+    /**
+     * Method: makeChrome
+     * create chrome on an element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to put the chrome on.
+     */
+    makeChrome: function(element) {
+        var c = new Element('div', {
+                'class':'jxChrome',
+                events: {
+                  contextmenu: function(e) { e.stop(); }
+                }
+              }),
+            src;
+
+        /* add to element so we can get the background image style */
+        element.adopt(c);
+
+        /* pick up any offset because of chrome, set
+         * through padding on the chrome object.  Other code can then
+         * make use of these offset values to fix positioning.
+         */
+        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 */
+        /* the app: protocol check is for adobe air support */
+        src = c.getStyle('backgroundImage');
+        if (src != null) {
+          if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
+              src = null;
+          } else {
+              src = src.slice(4,-1);
+              /* this only seems to be IE and Opera, but they add quotes
+               * around the url - yuck
+               */
+              if (src.charAt(0) == '"') {
+                  src = src.slice(1,-1);
+              }
+
+              /* and remove the background image */
+              c.setStyle('backgroundImage', 'none');
+
+              /* make chrome */
+              ['TR','TL','BL','BR'].each(function(s){
+                  c.adopt(
+                      new Element('div',{
+                          'class':'jxChrome'+s
+                      }).adopt(
+                      new Element('img',{
+                          'class':'png24',
+                          src:src,
+                          alt: '',
+                          title: ''
+                      })));
+              }, this);
+          }
+        }
+        /* create a shim so selects don't show through the chrome */
+        if ($defined(window.IframeShim)) {
+          this.shim = new IframeShim(c, {className: 'jxIframeShim'});
+        }
+
+        /* remove from DOM so the other resizing logic works as expected */
+        c.dispose();
+        this.chrome = c;
+    },
+
+    /**
+     * APIMethod: showChrome
+     * show the chrome on an element.  This creates the chrome if necessary.
+     * If the chrome has been previously created and not removed, you can
+     * call this without an element and it will just resize the chrome within
+     * its existing element.  You can also pass in a different element from
+     * which the chrome was previously attached to and it will move the chrome
+     * to the new element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to show the chrome on.
+     */
+    showChrome: function(element) {
+        element = document.id(element) || document.id(this);
+        if (element) {
+            if (!this.chrome) {
+                this.makeChrome(element);
+                element.addClass('jxHasChrome');
+            }
+            this.resizeChrome(element);
+            if (this.shim) {
+              this.shim.show();
+            }
+            if (element && this.chrome.parentNode !== element) {
+                element.adopt(this.chrome);
+                this.chrome.setStyle('z-index',-1);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: hideChrome
+     * removes the chrome from the DOM.  If you do this, you can't
+     * call showChrome with no arguments.
+     */
+    hideChrome: function() {
+        if (this.chrome) {
+            if (this.shim) {
+              this.shim.hide();
+            }
+            this.chrome.parentNode.removeClass('jxHasChrome');
+            this.chrome.dispose();
+        }
+    },
+
+    /**
+     * APIMethod: resizeChrome
+     * manually resize the chrome on an element.
+     *
+     * Parameters:
+     * element: {DOMElement} the element to resize the chrome for
+     */
+    resizeChrome: function(o) {
+        if (this.chrome && Browser.Engine.trident4) {
+            this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
+            if (this.shim) {
+              this.shim.position();
+            }
+        }
+    },
+
+    /**
+     * APIMethod: addTo
+     * adds the object to the DOM relative to another element.  If you use
+     * 'top' or 'bottom' then the element is added to the relative
+     * element (becomes a child node).  If you use 'before' or 'after'
+     * then the element is inserted adjacent to the reference node.
+     *
+     * Parameters:
+     * reference - {Object} the DOM element or id of a DOM element
+     * to append the object relative to
+     * where - {String} where to append the element in relation to the
+     * reference node.  Can be 'top', 'bottom', 'before' or 'after'.
+     * The default is 'bottom'.
+     *
+     * Returns:
+     * the object itself, which is useful for chaining calls together
+     */
+    addTo: function(reference, where) {
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            if (reference instanceof Jx.Widget && $defined(reference.add)) {
+                reference.add(el);
+            } else {
+                ref = document.id(reference);
+                el.inject(ref,where);
+            }
+            this.fireEvent('addTo',this);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: toElement
+     * return a DOM element reference for this widget, by default this
+     * returns the local domObj reference.  This is used by the mootools
+     * framework with the document.id() or $() methods allowing you to
+     * manipulate a Jx.Widget sub class as if it were a DOM element.
+     *
+     * (code)
+     * var button = new Jx.Button({label: 'test'});
+     * $(button).inject('someElement');
+     * (end)
+     */
+    toElement: function() {
+        return this.domObj;
+    },
+
+    /**
+     * 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(),
+            element,
+            el;
+        if ($defined(container)){
+            element = container.set('html',template);
+        } else {
+            element = new Element('div',{html:template});
+        }
+        classes.each(function(klass){
+            el = element.getElement('.'+klass);
+            if ($defined(el)){
+                h.set(klass,el);
+            }
+        });
+        return h;
+    },
+
+    /**
+     * APIMethod: dispose
+     * remove the widget from the DOM
+     */
+    dispose: function(){
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            el.dispose();
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the widget and clean up any potential memory leaks
+     */
+    cleanup: function(){
+        if ($defined(this.domObj)) {
+            this.domObj.eliminate('jxWidget');
+            this.domObj.destroy();
+        }
+        if ($defined(this.addable)) {
+            this.addable.destroy();
+        }
+        if ($defined(this.domA)) {
+            this.domA.destroy();
+        }
+        if ($defined(this.classes)) {
+          this.classes.each(function(v, k) {
+            this[k] = null;
+          }, this);
+        }
+        this.elements.empty();
+        this.elements = null;
+        this.parent();
+    },
+
+    /**
+     * Method: render
+     * render the widget, internal function called by the framework.
+     */
+    render: function() {
+        this.elements = this.processElements(this.options.template,
+            this.classes);
+        if ($defined(this.domObj)) {
+          if ( $defined(this.options.id)) {
+            this.domObj.set('id', this.options.id);
+          }
+          //TODO: Should we autogenerate an id when one is not provided? like so...
+          // this.domObj.set('id',this.generateId());
+          this.domObj.store('jxWidget', this);
+        }
+    },
+
+    /**
+     * Property: elements
+     * a hash of elements extracted by processing the widget template
+     */
+    elements: null,
+
+    /**
+     * Method: processElements
+     * process the template of the widget and populate the elements hash
+     * with any objects.  Also set any object references based on the classes
+     * hash.
+     */
+    processElements: function(template, classes) {
+        var keys = classes.getValues();
+        elements = this.processTemplate(template, keys);
+        classes.each(function(value, key) {
+            if (key != 'elements' && elements.get(value)) {
+                this[key] = elements.get(value);
+            }
+        }, this);
+        return elements;
+    },
+
+    /**
+     * APIMethod: isBusy
+     * indicate if the widget is currently busy or not
+     *
+     * Returns:
+     * {Boolean} true if busy, false otherwise.
+     */
+    isBusy: function() {
+      return this.busy;
+    },
+
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy         - {Boolean} true to set the widget as busy, false to set it as idle.
+     * message      - {String||Jx Localized Object} (Optional) set a custom message directly
+     *                next to the loading icon. Default is {set:'Jx',key:'widget',value:'busyMessage'}
+     * forceMessage - {Boolean} force displaying a message for larger areas than 60px of height
+     */
+    setBusy: function(state, message, forceMessage) {
+      if (this.busy == state) {
+        return;
+      }
+      var options = this.options,
+          z,
+          size,
+          opts,
+          domObj = this.domObj;
+      message = $defined(message) ? message : {
+        set:'Jx',
+        key:'widget',
+        value:'busyMessage'
+      };
+      forceMessage = $defined(forceMessage) ? forceMessage : false;
+      this.busy = state;
+      this.fireEvent('busy', state);
+      if (state) {
+        if (options.busyClass) {
+          domObj.addClass(options.busyClass);
+        }
+        if (options.busyMask && domObj.spin) {
+          /* put the spinner above the element in the z-index */
+          z = Jx.getNumber(domObj.getStyle('z-index'));
+          opts = {
+            style: {
+              'z-index': z+1
+            }
+          };
+          /* switch to the small size if the element is less than
+           * 60 pixels high
+           */
+          size = domObj.getBorderBoxSize();
+          if (size.height < 60 || forceMessage) {
+            opts['class'] = 'jxSpinner jxSpinnerSmall';
+            opts.img = null;
+            opts.message = new Element('p',{
+              'class':'jxSpinnerMessage',
+              html: '<span class="jxSpinnerImage"></span>'+this.getText(message)
+            });
+          }
+          opts = $merge(options.busyMask, opts);
+          domObj.get('spinner', opts).show(!options.busyMask.fx);
+        }
+      } else {
+        if (options.busyClass) {
+          domObj.removeClass(options.busyClass);
+        }
+        if (options.busyMask && this.domObj.unspin) {
+          domObj.get('spinner').hide(!options.busyMask.fx);
+        }
+      }
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - {string} the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    },
+
+    /**
+     * APIMethod: stack
+     * stack this widget in the z-index of the DOM relative to other stacked
+     * objects.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to stack.  By default, the
+     * element to stack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    stack: function(el) {
+      Jx.Stack.stack(el || document.id(this));
+    },
+
+    /**
+     * APIMethod: unstack
+     * remove this widget from the stack.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to unstack.  By default, the
+     * element to unstack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    unstack: function(el) {
+      Jx.Stack.unstack(el = el || document.id(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.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);
+            }
+        }
+    });
+}/*
+---
+
+name: Jx.Selection
+
+description: A class to manage selection across multiple list objects
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Selection]
+
+...
+ */
+// $Id: selection.js 976 2010-09-02 18:57:12Z pagameba $
+/**
+ * Class: Jx.Selection
+ *
+ * Manage selection of objects.
+ *
+ * Example:
+ * (code)
+ * var selection = new Jx.Selection();
+ * (end)
+ *
+ * Events:
+ * select - fired when an item is added to the selection.  This event may be
+ *    changed by passing the eventToFire option when creating the selection
+ *    object.
+ * unselect - fired when an item is removed from the selection.  This event
+ *    may be changed by passing the eventToFire option when creating the
+ *    selection object.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+
+Jx.Selection = new Class({
+    Family: 'Jx.Selection',
+    Extends: Jx.Object,
+    options: {
+        /**
+         * Option: eventToFire
+         * Allows the developer to change the event that is fired in case one
+         * object is using multiple selectionManager instances.  The default
+         * is to use 'select' and 'unselect'.  To modify the event names,
+         * pass different values:
+         * (code)
+         * new Jx.Selection({
+         *   eventToFire: {
+         *     select: 'newSelect',
+         *     unselect: 'newUnselect'
+         *   }
+         * });
+         * (end)
+         */
+        eventToFire: {
+            select: 'select',
+            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
+    },
+
+    /**
+     * Property: selection
+     * {Array} an array holding the current selection
+     */
+    selection: null,
+
+    /**
+     * Constructor: Jx.Selection
+     * create a new instance of Jx.Selection
+     *
+     * Parameters:
+     * options - {Object} options for the new instance
+     */
+    init: function () {
+        this.selection = [];
+        this.parent();
+    },
+
+    cleanup: function() {
+      this.selection = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: defaultSelect
+     * select an item if the selection does not yet contain the minimum
+     * number of selected items.  Uses <Jx.Selection::select> to select
+     * the item, so the same criteria is applied to the item if it is
+     * to be selected.
+     */
+    defaultSelect: function(item) {
+        if (this.selection.length < this.options.minimumSelection) {
+            this.select(item);
+        }
+    },
+
+    /**
+     * APIMethod: select
+     * select an item.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    select: function (item) {
+        var options = this.options,
+            selection = this.selection;
+        item = document.id(item);
+        if (options.selectMode === 'multiple') {
+            if (selection.contains(item)) {
+                this.unselect(item);
+            } else {
+                document.id(item).addClass(options.selectClass);
+                selection.push(item);
+                this.fireEvent(options.eventToFire.select, item);
+            }
+        } else if (options.selectMode == 'single') {
+            if (!this.selection.contains(item)) {
+                document.id(item).addClass(options.selectClass);
+                selection.push(item);
+                if (selection.length > 1) {
+                    this.unselect(selection[0]);
+                }
+                this.fireEvent(options.eventToFire.select, item);
+            } else {
+                if (options.selectToggle) {
+                  this.unselect(item);
+                }
+            }
+        }
+    },
+
+    /**
+     * APIMethod: unselect
+     * remove an item from the selection.  The item must already be in the
+     * selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    unselect: function (item) {
+        var selection = this.selection,
+            options = this.options;
+        if (selection.contains(item) &&
+            selection.length > options.minimumSelection) {
+            document.id(item).removeClass(options.selectClass);
+            selection.erase(item);
+            this.fireEvent(options.eventToFire.unselect, [item, this]);
+        }
+    },
+
+    /**
+     * APIMethod: selected
+     * returns the items in the current selection.
+     *
+     * Returns:
+     * {Array} an array of DOM elements in the current selection
+     */
+    selected: function () {
+        return this.selection;
+    },
+
+    /**
+     * APIMethod: isSelected
+     * test if an item is in the current selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     *
+     * Returns:
+     * {Boolean} true if the current selection contains the item, false
+     * otherwise
+     */
+    isSelected: function(item) {
+        return this.selection.contains(item);
+    }
+});/*
+---
+
+name: Jx.List
+
+description: A class that is used to manage lists of DOM elements
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Selection
+
+provides: [Jx.List]
+
+...
+ */
+// $Id: list.js 976 2010-09-02 18:57:12Z pagameba $
+/**
+ * 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)
+ * var list = new Jx.List('container',{
+ *   hover: true,
+ *   select: true,
+ *   onSelect: function(el) {
+ *     alert(el.get('html'));
+ *   }
+ * });
+ * list.add(new Element('li', {html:'1'}));
+ * list.add(new Element('li', {html:'2'}));
+ * list.add(new Element('li', {html:'3'}));
+ *
+ * (end)
+ *
+ * Events:
+ * 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,
+    /**
+     * Constructor: Jx.List
+     * create a new instance of Jx.List
+     *
+     * Parameters:
+     * container - {Mixed} an element reference or id of an element that will
+     * contain the items in the list
+     * options - {Object} an object containing optional parameters
+     * selection - {<Jx.Selection>} null or a Jx.Selection object. If the
+     * select option is set to true, then list will use this selection object
+     * to track selections or create its own if no selection object is
+     * supplied.
+     */
+    parameters: ['container', 'options', 'selection'],
+    /* does this object own the selection object (and should clean it up) */
+    ownsSelection: false,
+    /**
+     * APIProperty: container
+     * 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: {
+        /**
+         * Option: items
+         * an array of items to add to the list right away
+         */
+        items: null,
+        /**
+         * Option: hover
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined hoverClass if set and mouseenter/mouseleave
+         * events will be emitted when the user hovers over and out of elements
+         */
+        hover: false,
+        /**
+         * Option: hoverClass
+         * the CSS class name to add to the wrapper element when the mouse is
+         * over an item
+         */
+        hoverClass: 'jxHover',
+
+        /**
+         * Option: press
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined pressClass if set and mousedown/mouseup
+         * events will be emitted when the user clicks on elements
+         */
+        press: false,
+        /**
+         * Option: 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 false.  If set to true, the wrapper element will
+         * obtain the defined selectClass if set and select/unselect events
+         * will be emitted when items are selected and unselected.  For other
+         * selection objects, see <Jx.Selection>
+         */
+        select: false
+    },
+
+    /**
+     * Method: init
+     * internal method to initialize this object
+     */
+    init: function() {
+        this.container = document.id(this.options.container);
+        this.container.store('jxList', this);
+
+        var target = this,
+            options = this.options,
+            isEnabled = function(el) {
+                var item = el.retrieve('jxListTargetItem') || el;
+                return !item.hasClass('jxDisabled');
+            },
+            isSelectable = function(el) {
+                var item = el.retrieve('jxListTargetItem') || el;
+                return !item.hasClass('jxUnselectable');
+            };
+        this.bound = $merge(this.bound, {
+            mousedown: function() {
+                if (isEnabled(this)) {
+                    this.addClass(options.pressClass);
+                    target.fireEvent('mousedown', this, target);
+                }
+            },
+            mouseup: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(options.pressClass);
+                    target.fireEvent('mouseup', this, target);
+                }
+            },
+            mouseenter: function() {
+                if (isEnabled(this)) {
+                    this.addClass(options.hoverClass);
+                    target.fireEvent('mouseenter', this, target);
+                }
+            },
+            mouseleave: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(options.hoverClass);
+                    target.fireEvent('mouseleave', this, target);
+                }
+            },
+            keydown: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.addClass('jxPressed');
+                }
+            },
+            keyup: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.removeClass('jxPressed');
+                }
+            },
+            click: function (e) {
+                if (target.selection &&
+                    isEnabled(this) &&
+                    isSelectable(this)) {
+                    target.selection.select(this, target);
+                }
+                target.fireEvent('click', this, target);
+            },
+            select: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('select', itemTarget);
+                }
+            },
+            unselect: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('unselect', itemTarget);
+                }
+            },
+            contextmenu: function(e) {
+              var cm = this.retrieve('jxContextMenu');
+              if (cm) {
+                cm.show(e);
+                this.removeClass(options.pressClass);
+              }
+              e.stop();
+            }
+        });
+
+        if (options.selection) {
+            this.setSelection(options.selection);
+            options.select = true;
+        } else if (options.select) {
+            this.selection = new Jx.Selection(options);
+            this.ownsSelection = true;
+        }
+
+        if ($defined(options.items)) {
+            this.add(options.items);
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the list and release anything it references
+     */
+    cleanup: function() {
+        this.container.getChildren().each(function(item){
+            this.remove(item);
+        }, this);
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents();
+            this.selection.destroy();
+        }
+        this.setSelection(null);
+        this.container.eliminate('jxList');
+        var bound = this.bound;
+        bound.mousedown=null;
+        bound.mouseup=null;
+        bound.mouseenter=null;
+        bound.mouseleave=null;
+        bound.keydown=null;
+        bound.keyup=null;
+        bound.click=null;
+        bound.select=null;
+        bound.unselect=null;
+        bound.contextmenu=null;
+        this.parent();
+    },
+
+    /**
+     * 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 (Jx.type(item) == 'array') {
+            item.each(function(what){
+              this.add(what, position);
+            }.bind(this) );
+            return;
+        }
+        /* the element being wrapped */
+        var el = document.id(item),
+            target = el.retrieve('jxListTarget') || el,
+            bound = this.bound,
+            options = this.options,
+            container = this.container;
+        if (target) {
+            target.store('jxListTargetItem', el);
+            target.addEvents({
+              contextmenu: this.bound.contextmenu
+            });
+            if (options.press && options.pressClass) {
+                target.addEvents({
+                    mousedown: bound.mousedown,
+                    mouseup: bound.mouseup,
+                    keyup: bound.keyup,
+                    keydown: bound.keydown
+                });
+            }
+            if (options.hover && options.hoverClass) {
+                target.addEvents({
+                    mouseenter: bound.mouseenter,
+                    mouseleave: bound.mouseleave
+                });
+            }
+            if (this.selection) {
+                target.addEvents({
+                    click: bound.click
+                });
+            }
+            if ($defined(position)) {
+                if ($type(position) == 'number') {
+                    if (position < container.childNodes.length) {
+                        el.inject(container.childNodes[position],'before');
+                    } else {
+                        el.inject(container, 'bottom');
+                    }
+                } else if (container.hasChild(position)) {
+                    el.inject(position,'after');
+                }
+                this.fireEvent('add', item, this);
+            } else {
+                el.inject(container, 'bottom');
+                this.fireEvent('add', item, this);
+            }
+            if (this.selection) {
+                this.selection.defaultSelect(el);
+            }
+        }
+    },
+    /**
+     * APIMethod: 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) {
+        var el = document.id(item),
+            target;
+        if (el && this.container.hasChild(el)) {
+            this.unselect(el, true);
+            el.dispose();
+            target = el.retrieve('jxListTarget') || el;
+            target.removeEvents(this.bound);
+            this.fireEvent('remove', item, this);
+            return item;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: 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
+     * context - {object} the context to execute the function in, null by
+     * default.
+     */
+    each: function(f, context) {
+        $A(this.container.childNodes).each(f, context);
+    },
+    /**
+     * 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);
+    },
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object that this list will use for selection
+     * events.
+     *
+     * Parameters:
+     * {<Jx.Selection>} the selection object, or null to remove it.
+     */
+    setSelection: function(selection) {
+        var sel = this.selection;
+        if (sel == selection) return;
+
+        if (sel) {
+            sel.removeEvents(this.bound);
+            if (this.ownsSelection) {
+                sel.destroy();
+                this.ownsSelection = false;
+            }
+        }
+
+        this.selection = selection;
+        if (selection) {
+            selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+    }
+
+});/*
+---
+
+name: Jx.Stack
+
+description: A singleton object for managing a global z-index stack for widgets that need to order themselves in the z-index of the page relative to other such widgets.
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Stack]
+
+...
+ */
+/**
+ * Class: Jx.Stack
+ * Manage the zIndex of widgets
+ *
+ * This is a singleton and should be called directly, like so:
+ *
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010 Paul Spencer
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Stack = new(new Class({
+  /**
+   * Property: els
+   * {Array} the elements in the stack
+   */
+  els: [],
+
+  /**
+   * Property: base
+   * {Integer} the base z-index value of the first element in the stack
+   */
+  base: 1000,
+
+  /**
+   * Property: increment
+   * {Integer} the amount to increment the z-index between elements of the
+   * stack
+   */
+  increment: 100,
+
+  /**
+   * APIMethod: stack
+   * push an element onto the stack and set its z-index appropriately
+   *
+   * Parameters:
+   * el - {DOMElement} a DOM element to push on the stack
+   */
+  stack: function(el) {
+    this.unstack(el);
+    this.els.push(el);
+    this.setZIndex(el, this.els.length-1);
+  },
+
+  /**
+   * APIMethod: unstack
+   * pull an element off the stack and reflow the z-index of the remaining
+   * elements in the stack if necessary
+   *
+   * Parameters:
+   * el - {DOMElement} the DOM element to pull off the stack
+   */
+  unstack: function(el) {
+    var elements = this.els;
+    if (elements.contains(el)) {
+      el.setStyle('z-index', '');
+      var idx = elements.indexOf(el);
+      elements.erase(el);
+      for (var i=idx; i<elements.length; i++) {
+        this.setZIndex(elements[i], i);
+      }
+    }
+  },
+
+  /**
+   * Method: setZIndex
+   * set the z-index of an element based on its position in the stack
+   *
+   * Parameters:
+   * el - {DOMElement} the element to set the z-index for
+   * idx - {Integer} optional, the index to assume for this object
+   */
+  setZIndex: function(obj, idx) {
+    idx = idx || this.els.indexOf(obj);
+    if (idx !== false) {
+      document.id(obj).setStyle('z-index', this.base + (idx*this.increment));
+    }
+  }
+
+}))();/*
+name: Locale.German
+
+description: Default translations of text strings used in JX for German (Germany) (de-DE)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.German]
+
+...
+ */
+
+MooTools.lang.set('de-DE', 'Date', {
+  // need to overwrite 'M&auml;rz' to 'März' for jx.select fields
+  months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
+});
+
+MooTools.lang.set('de-DE', 'Jx', {
+
+	'widget': {
+		busyMessage: 'Arbeite ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'Notiz schließen'
+	},
+	progressbar: {
+		messageText: 'Lade...',
+		progressText: '{progress} von {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Durchsuchen...'
+	},
+	'formatter.boolean': {
+		'true': 'Ja',
+		'false': 'Nein'
+	},
+	'formatter.currency': {
+		sign: '€'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: '.'
+	},
+	splitter: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panelset: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panel: {
+        collapseTooltip: 'Panel ein-/ausklappen', //colB
+        collapseLabel: 'Einklappen',  //colM
+        expandLabel: 'Ausklappen', //colM
+        maximizeTooltip: 'Panel maximieren',
+        maximizeLabel: 'maximieren',
+        restoreTooltip: 'Panel wieder herstellen', //maxB
+        restoreLabel: 'wieder herstellen', //maxM
+        closeTooltip: 'Panel schließen', //closeB
+        closeLabel: 'Schließen' //closeM
+	},
+	confirm: {
+		affirmativeLabel: 'Ja',
+    negativeLabel: 'Nein'
+	},
+	dialog: {
+		label: 'Neues Fenster'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Abbrechen'
+	},
+	upload: {
+		buttonText: 'Dateien hochladen'
+	},
+	'plugin.resize': {
+	  tooltip: 'Klicken um Größe zu verändern. Doppelklick für automatische Anpassung.'
+	},
+  'plugin.editor': {
+    submitButton: 'Speichern',
+    cancelButton: 'Abbrechen'
+  }
+});/*
+---
+
+name: Locale.Russian
+
+description: Default translations of text strings used in JX for Russia (Russia) (ru-RU)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.Russian]
+
+...
+ */
+MooTools.lang.set('ru-RU-unicode', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Обработка...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'закрыть �?то �?ообщение'
+	},
+	progressbar: {
+		messageText: 'Загрузка...',
+		progressText: '{progress} из {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Выбрать...'
+	},
+	'formatter.boolean': {
+		'true': 'Да',
+		'false': '�?ет'
+	},
+	'formatter.currency': {
+		sign: 'Ñ€.'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: ' '
+	},
+	splitter: {
+		barToolTip: 'пот�?ни, чтобы изменить размер'
+	},
+	panelset: {
+		barToolTip: 'пот�?ни, чтобы изменить размер'
+	},
+	panel: {
+		collapseTooltip: 'Свернуть/Развернуть Панель',
+    collapseLabel: 'Свернуть',
+    expandLabel: 'Развернуть',
+    maximizeTooltip: 'Увеличить Панель',
+    maximizeLabel: 'Увеличить',
+    restoreTooltip: 'Во�?�?тановить Панель',
+    restoreLabel: 'Во�?�?тановить',
+    closeTooltip: 'Закрыть Панель',
+    closeLabel: 'Закрыть'
+	},
+	confirm: {
+		affirmativeLabel: 'Да',
+    negativeLabel: '�?ет'
+	},
+	dialog: {
+		resizeToolTip: 'Изменить размер'
+	},
+	message: {
+		okButton: 'Ок'
+	},
+	prompt: {
+		okButton: 'Ок',
+		cancelButton: 'Отмена'
+	},
+	upload: {
+		buttonText: 'Загрузка файла'
+	},
+	'plugin.resize': {
+	  tooltip: 'Пот�?ни, чтобы изменить, двойной щелчок дл�? авто размера.'
+	},
+  'plugin.editor': {
+    submitButton: 'Сохранить',
+    cancelButton: 'Отмена'
+  }
+});/*
+---
+
+name: Jx.Record
+
+description: The basic record implementation. A store uses records to handle and manipulate data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Record]
+
+...
+ */
+// $Id: record.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Record
+ *
+ * Extends: <Jx.Object>
+ *
+ * This class is used as a representation (or container) for a single row
+ * of data in a <Jx.Store>. It is not usually directly instantiated by the
+ * developer but rather by the store itself.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Record = new Class({
+
+    Extends: Jx.Object,
+    Family: 'Jx.Record',
+
+    options: {
+        /**
+         * Option: separator
+         * The separator to pass to the comparator
+         * constructor (<Jx.Compare>) - defaults to '.'
+         */
+        separator : '.',
+
+        primaryKey: null
+    },
+    /**
+     * Property: data
+     * The data for this record
+     */
+    data: null,
+    /**
+     * Property: state
+     * used to determine the state of this record. When not null (meaning no
+     * changes were made) this should be one of
+     *
+     * - Jx.Record.UPDATE
+     * - Jx.Record.DELETE
+     * - Jx.Record.INSERT
+     */
+    state: null,
+    /**
+     * Property: columns
+     * Holds a reference to the columns for this record. These are usually
+     * passed to the record from the store. This should be an array of objects
+     * where the objects represent the columns. The object should take the form:
+     *
+     * (code)
+     * {
+     *     name: <column name>,
+     *     type: <column type>,
+     *     ..additional options required by the record implementation...
+     * }
+     * (end)
+     *
+     * The type of the column should be one of alphanumeric, numeric, date,
+     * boolean, or currency.
+     */
+    columns: null,
+
+    parameters: ['store', 'columns', 'data', 'options'],
+
+    init: function () {
+        this.parent();
+        if ($defined(this.options.columns)) {
+            this.columns = this.options.columns;
+        }
+
+        if ($defined(this.options.data)) {
+            this.processData(this.options.data);
+        } else {
+            this.data = new Hash();
+        }
+
+        if ($defined(this.options.store)) {
+            this.store = this.options.store;
+        }
+
+    },
+    /**
+     * APIMethod: get
+     * returns the value of the requested column. Can be programmed to handle
+     * pseudo-columns (such as the primaryKey column implemented in this base
+     * record).
+     *
+     * Parameters:
+     * column - the string, index, or object of the requested column
+     */
+    get: function (column) {
+        var type = Jx.type(column);
+        if (type !== 'object') {
+            if (column === 'primaryKey') {
+                column = this.resolveCol(this.options.primaryKey);
+            } else {
+                column = this.resolveCol(column);
+            }
+        }
+        if (this.data.has(column.name)) {
+            return this.data.get(column.name);
+        } else {
+            return null;
+        }
+    },
+    /**
+     * APIMethod: set
+     * Sets a given value into the requested column.
+     *
+     *  Parameters:
+     *  column - the object, index, or string name of the target column
+     *  data - the data to add to the column
+     */
+    set: function (column, data) {
+        var type = Jx.type(column),
+            oldValue;
+        if (type !== 'object') {
+            column = this.resolveCol(column);
+        }
+
+        if (!$defined(this.data)) {
+            this.data = new Hash();
+        }
+
+        oldValue = this.get(column);
+        this.data.set(column.name, data);
+        this.state = Jx.Record.UPDATE;
+        return [column.name, oldValue, data];
+        //this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);
+
+    },
+    /**
+     * APIMethod: equals
+     * Compares the value of a particular column with a given value
+     *
+     * Parameters:
+     * column - the column to compare with (either column name or index)
+     * value - the value to compare to.
+     *
+     * Returns:
+     * True | False depending on the outcome of the comparison.
+     */
+    equals: function (column, value) {
+        if (column === 'primaryKey') {
+            column = this.resolveCol(this.options.primaryKey);
+        } else {
+            column = this.resolveCol(column);
+        }
+        if (!this.data.has(column.name)) {
+            return null;
+        } else {
+            if (!$defined(this.comparator)) {
+                this.comparator = new Jx.Compare({
+                    separator : this.options.separator
+                });
+            }
+            var fn = this.comparator[column.type].bind(this.comparator);
+            return (fn(this.get(column), value) === 0);
+        }
+    },
+    /**
+     * Method: processData
+     * This method takes the data passed in and puts it into the form the
+     * record needs it in. This default implementation does nothing but
+     * assign the data to the data property but it can be overridden in
+     * subclasses to massge the data in any way needed.
+     *
+     * Parameters:
+     * data - the data to process
+     */
+    processData: function (data) {
+        this.data = $H(data);
+    },
+
+    /**
+     * Method: resolveCol
+     * Determines which column is being asked for and returns it.
+     *
+     * Parameters:
+     * col - a number referencing a column in the store
+     *
+     * Returns:
+     * the column object referred to
+     */
+    resolveCol : function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.columns[col];
+        } else if (t === 'string') {
+            this.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;
+    },
+    /**
+     * APIMethod: asHash
+     * Returns the data for this record as a Hash
+     */
+    asHash: function() {
+        return this.data;
+    }
+});
+
+Jx.Record.UPDATE = 1;
+Jx.Record.DELETE = 2;
+Jx.Record.INSERT = 3;/*
+---
+
+name: Jx.Store
+
+description: An implementation of a basic data store.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Record
+
+provides: [Jx.Store]
+
+...
+ */
+// $Id: store.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store
+ *
+ * Extends: <Jx.Object>
+ *
+ * This class is the  store. It keeps track of data. It
+ * allows adding, deleting, iterating, sorting etc...
+ *
+ * For the most part the store is pretty "dumb" meaning it
+ * starts with very limited functionality. Actually, it can't
+ * even load data by itself. Instead, it needs to have protocols,
+ * strategies, and a record class passed to it that it can use.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store = new Class({
+
+    Family: 'Jx.Store',
+    Extends: Jx.Object,
+
+    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: protocol
+         * The protocol to use for communication in this store. The store
+         * itself doesn't actually use it but it is accessed by the strategies
+         * to do their work. This option is required and the store won't work
+         * without it.
+         */
+        protocol: null,
+        /**
+         * Option: strategies
+         * This is an array of instantiated strategy objects that will work
+         * on this store. They provide many services such as loading data,
+         * paging data, saving, and sorting (and anything else you may need
+         * can be written). If none are passed in it will use the default
+         * Jx.Store.Strategy.Full
+         */
+        strategies: null,
+        /**
+         * Option: record
+         * This is a Jx.Store.Record instance or one of its subclasses. This is
+         * the class that will be used to hold each individual record in the
+         * store. Don't pass in a instance of the class but rather the class
+         * name itself. If none is passed in it will default to Jx.Record
+         */
+        record: null,
+        /**
+         * Option: recordOptions
+         * Options to pass to each record as it's created.
+         */
+        recordOptions: {
+            primaryKey: null
+        }
+    },
+
+    /**
+     * 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,
+    /**
+     * APIProperty: id
+     * The id of this store.
+     */
+    id : null,
+    /**
+     * Property: loaded
+     * Tells whether the store has been loaded or not
+     */
+    loaded: false,
+    /**
+     * Property: ready
+     * Used to determine if the store is completely initialized.
+     */
+    ready: false,
+    
+    /**
+     * Property: deleted
+     * track deleted records before they are purged
+     */
+    deleted: null,
+
+    /**
+     * Method: init
+     * initialize the store, should be called by sub-classes
+     */
+    init: function () {
+        this.parent();
+
+        this.deleted = [];
+        
+        if ($defined(this.options.id)) {
+            this.id = this.options.id;
+        }
+
+        if (!$defined(this.options.protocol)) {
+            this.ready = false;
+            return;
+        } else {
+            this.protocol = this.options.protocol;
+        }
+
+        this.strategies = new Hash();
+
+        if ($defined(this.options.strategies)) {
+            this.options.strategies.each(function(strategy){
+                this.addStrategy(strategy);
+            },this);
+        } else {
+            var strategy = new Jx.Store.Strategy.Full();
+            this.addStrategy(strategy);
+        }
+
+        if ($defined(this.options.record)) {
+            this.record = this.options.record;
+        } else {
+            this.record = Jx.Record;
+        }
+
+
+    },
+
+    /**
+     * Method: cleanup
+     * avoid memory leaks when a store is destroyed, should be called
+     * by sub-classes if overridden
+     */
+    cleanup: function () {
+        this.strategies.each(function(strategy){
+            strategy.destroy();
+        },this);
+        this.strategies = null;
+        this.protocol.destroy();
+        this.protocol = null;
+        this.record = null;
+    },
+    /**
+     * APIMethod: getStrategy
+     * returns the named strategy if it is present, null otherwise.
+     *
+     * Parameters:
+     * name - the name of the strategy we're looking for
+     */
+    getStrategy: function (name) {
+        if (this.strategies.has(name)) {
+            return this.strategies.get(name);
+        }
+        return null;
+    },
+    /**
+     * APIMethod: addStrategy
+     * Allows the addition of strategies after store initialization. Handy to
+     * have if some other class needs a strategy that is not present.
+     *
+     * Parameters:
+     * strategy - the strategy to add to the store
+     */
+    addStrategy: function (strategy) {
+        this.strategies.set(strategy.name, strategy);
+        strategy.setStore(this);
+        strategy.activate();
+    },
+    /**
+     * APIMethod: load
+     * used to load the store. It simply fires an event that the strategies
+     * are listening for.
+     *
+     * Parameters:
+     * params - a hash of parameters passed to the strategy for determining
+     *     what records to load.
+     */
+    load: function (params) {
+        this.fireEvent('storeLoad', params);
+    },
+    /**
+     * APIMethod: empty
+     * Clears the store of data
+     */
+    empty: function () {
+        if ($defined(this.data)) {
+            this.data.empty();
+        }
+    },
+
+    /**
+     * 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)) {
+            return this.index < this.data.length - 1;
+        }
+        return null;
+    },
+
+    /**
+     * APIMethod: hasPrevious
+     * Determines if there are records before the current
+     * one.
+     *
+     * Returns: true | false
+     */
+    hasPrevious : function () {
+        if ($defined(this.data)) {
+            return this.index > 0;
+        }
+        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) && $defined(this.data[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.length) {
+                this.index = this.data.length - 1;
+            }
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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);
+            return true;
+        } 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);
+            return true;
+        } 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.length - 1;
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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.length;
+        }
+        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)) {
+            return this.index;
+        }
+        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.length) {
+            this.index = index;
+            this.fireEvent('storeMove', this);
+            return true;
+        } else if (!$defined(this.data)) {
+            return null;
+        } else {
+            return false;
+        }
+    },
+    /**
+     * APIMethod: each
+     * allows iteration through the store's records.
+     * NOTE: this function is untested
+     *
+     * Parameters:
+     * fn - the function to execute for each record
+     * bind - the scope of the function
+     * ignoreDeleted - flag that tells the function whether to ignore records
+     *                  marked as deleted.
+     */
+    each: function (fn, bind, ignoreDeleted) {
+        if ($defined(this.data)) {
+          var data;
+          if (ignoreDeleted) {
+              data = this.data.filter(function (record) {
+                  return record.state !== Jx.Record.DELETE;
+              }, this);
+          } else {
+              data = this.data;
+          }
+          data.each(fn, bind);
+        }
+    },
+    /**
+     * APIMethod: get
+     * gets the data for the specified column
+     *
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of
+     *          the column) or an integer (the index of the column in the
+     *          record).
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    get: function (column, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        return this.data[index].get(column);
+    },
+    /**
+     * APIMethod: set
+     * Sets the passed data for a particular column on the indicated record.
+     *
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of
+     *          the column) or an integer (the index of the column in the
+     *          record).
+     * data - the data to set in the column of the record
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    set: function (column, data, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var ret = this.data[index].set(column, data);
+        ret.reverse();
+        ret.push(index);
+        ret.reverse();
+        //fire event with array [index, column, oldvalue, newValue]
+        this.fireEvent('storeColumnChanged', ret);
+    },
+    /**
+     * APIMethod: refresh
+     * Simply fires the storeRefresh event for strategies to listen for.
+     */
+    refresh: function () {
+        this.fireEvent('storeRefresh', this);
+    },
+    /**
+     * APIMethod: addRecord
+     * Adds given data to the end of the current store.
+     *
+     * Parameters:
+     * data - The data to use in creating a record. This should be in whatever
+     *        form Jx.Store.Record, or the current subclass, needs it in.
+     * position - whether the record is added to the 'top' or 'bottom' of the
+     *      store.
+     * insert - flag whether this is an "insert"
+     */
+    addRecord: function (data, position, insert) {
+        if (!$defined(this.data)) {
+            this.data = [];
+        }
+
+        position = $defined(position)? position : 'bottom';
+
+        var record = data;
+        if (!(data instanceof Jx.Record)) {
+            record = new (this.record)(this, this.options.columns, data, this.options.recordOptions);
+        }
+        if (insert) {
+            record.state = Jx.Record.INSERT;
+        }
+        if (position === 'top') {
+            //some literature claims that .shift() and .unshift() don't work reliably in IE
+            //so we do it this way.
+            this.data.reverse();
+            this.data.push(record);
+            this.data.reverse();
+        } else {
+            this.data.push(record);
+        }
+        this.fireEvent('storeRecordAdded', [this, record, position]);
+    },
+    /**
+     * APIMethod: addRecords
+     * Used to add multiple records to the store at one time.
+     *
+     * Parameters:
+     * data - an array of data to add.
+     * position - 'top' or 'bottom'. Indicates whether to add at the top or
+     * the bottom of the store
+     */
+    addRecords: function (data, position) {
+        var def = $defined(data),
+            type = Jx.type(data);
+        if (def && type === 'array') {
+            this.fireEvent('storeBeginAddRecords', this);
+            //if position is top, reverse the array or we'll add them in the
+            // wrong order.
+            if (position === 'top') {
+                data.reverse();
+            }
+            data.each(function(d){
+                this.addRecord(d, position);
+            },this);
+            this.fireEvent('storeEndAddRecords', this);
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * APIMethod: getRecord
+     * Returns the record at the given index or the current store index
+     *
+     * Parameters:
+     * index - the index from which to return the record. Optional. Defaults
+     * to the current store index
+     */
+    getRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+
+        if (Jx.type(index) === 'number') {
+            if ($defined(this.data) && $defined(this.data[index])) {
+                return this.data[index];
+            }
+        } else {
+            //Not sure what the point of this part is. It compares the
+            //record to the index directly as if we passed in the record which
+            //means we already have the record... huh???
+            var r;
+            this.data.each(function(record){
+                if (record === index) {
+                    r = record;
+                }
+            },this);
+            return r;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: replaceRecord
+     * Replaces the record at an existing index with a new record containing
+     * the passed in data.
+     *
+     * Parameters:
+     * data - the data to use in creating the new record
+     * index - the index at which to place the new record. Optional.
+     *          defaults to the current store index.
+     */
+    replace: function(data, index) {
+        if ($defined(data)) {
+            if (!$defined(index)) {
+                index = this.index;
+            }
+            var record = new this.record(this.options.columns,data),
+            oldRecord = this.data[index];
+            this.data[index] = record;
+            this.fireEvent('storeRecordReplaced', [oldRecord, record]);
+            return true;
+        }
+        return false;
+    },
+    /**
+     * APIMethod: deleteRecord
+     * Marks a record for deletion and removes it from the regular array of
+     * records. It adds it to a special holding array so it can be disposed
+     * of later.
+     *
+     * Parameters:
+     * index - the index at which to place the new record. Optional.
+     *          defaults to the current store index.
+     */
+    deleteRecord: function(index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var record = this.data[index];
+        record.state = Jx.Record.DELETE;
+        // Set to Null or slice it out and compact the array???
+        //this.data[index] = null;
+        this.data.splice(index,1);
+        // TODO: I moved this to a property that is always an array so I don't
+        // get an error in the save strategy.
+        // if (!$defined(this.deleted)) {
+        //     this.deleted = [];
+        // }
+        this.deleted.push(record);
+        this.fireEvent('storeRecordDeleted', [this, record]);
+    },
+    /**
+     * APIMethod: insertRecord
+     * Shortcut to addRecord which facilitates marking a record as inserted.
+     *
+     * Parameters:
+     * data - the data to use in creating this inserted record. Should be in
+     *          whatever form the current implementation of Jx.Record needs
+     * position - where to place the record. Should be either 'top' or
+     *    'bottom'.
+     */
+    insertRecord: function (data, position) {
+        this.addRecord(data, position, true);
+    },
+
+    /**
+     * 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 (or index) of the column to search by
+     * value - the value to look for
+     */
+    findByColumn: function (column, value) {
+        if (typeof StopIteration === "undefined") {
+            StopIteration = new Error("StopIteration");
+        }
+
+        var index;
+        try {
+            this.data.each(function(record, idx){
+                if (record.equals(column, value)) {
+                    index = idx;
+                    throw StopIteration;
+                }
+            },this);
+        } catch (error) {
+            if (error !== StopIteration) {
+                throw error;
+            }
+            return index;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: removeRecord
+     * removes (but does not mark for deletion) a record at the given index
+     * or the current store index if none is passed in.
+     *
+     * Parameters:
+     * index - Optional. The store index of the record to remove.
+     */
+    removeRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        this.data.splice(index,1);
+        this.fireEvent('storeRecordRemoved', [this, index])
+    },
+    /**
+     * APIMethod: removeRecords
+     * Used to remove multiple contiguous records from a store.
+     *
+     * Parameters:
+     * first - where to start removing records (zero-based)
+     * last - where to stop removing records (zero-based, inclusive)
+     */
+    removeRecords: function (first, last) {
+        for (var i = first; i <= last; i++) {
+            this.removeRecord(first);
+        }
+        this.fireEvent('storeMultipleRecordsRemoved', [this, first, last]);
+    },
+
+    /**
+   * APIMethod: parseTemplate
+   * parses the provided template to determine which store columns are
+   * required to complete it.
+   *
+   * Parameters:
+   * template - the template to parse
+   */
+  parseTemplate: function (template) {
+      //we parse the template based on the columns in the data store looking
+      //for the pattern {column-name}. If it's in there we add it to the
+      //array of ones to look fo
+      var arr = [],
+          s;
+      this.options.columns.each(function (col) {
+          s = '{' + col.name + '}';
+          if (template.contains(s)) {
+              arr.push(col.name);
+          }
+      }, this);
+      return arr;
+  },
+
+  /**
+   * APIMethod: fillTemplate
+   * Actually does the work of getting the data from the store
+   * and creating a single item based on the provided template
+   *
+   * Parameters:
+   * index - the index of the data in the store to use in populating the
+   *          template.
+   * template - the template to fill
+   * columnsNeeded - the array of columns needed by this template. should be
+   *      obtained by calling parseTemplate().
+     * obj - an object with some prefilled keys to use in substituting.
+     *      Ones that are also in the store will be overwritten.
+   */
+  fillTemplate: function (index, template, columnsNeeded, obj) {
+      var record = null,
+          itemObj;
+      if ($defined(index)) {
+          if (index instanceof Jx.Record) {
+              record = index;
+          } else {
+              record = this.getRecord(index);
+          }
+        } else {
+            record = this.getRecord(this.index);
+        }
+
+      //create the item
+      itemObj = $defined(obj) ? obj : {};
+      columnsNeeded.each(function (col) {
+          itemObj[col] = record.get(col);
+      }, this);
+      return template.substitute(itemObj);
+  }
+});/*
+---
+
+name: Jx.Compare
+
+description: Class that provides functions for comparing various data types. Used by the Jx.Sort class and it's descendants
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - More/Date.Extras
+
+provides: [Jx.Compare]
+
+...
+ */
+// $Id: compare.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Compare
+ *
+ * Extends: <Jx.Object>
+ *
+ * Class that holds functions for doing comparison operations.
+ * This class requires the mootools-more Date() extensions.
+ *
+ * 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({
+    Family: 'Jx.Compare',
+    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') {
+            var neg = false;
+            if (val.substr(0,1) == '-') {
+                neg = true;
+            }
+            val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g, "$1").replace(new RegExp("[^\\\d" + this.options.separator + "]", "g"), '').replace(/,/, '.')) || 0;
+            if (neg) {
+                val = val * -1;
+            }
+        }
+        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),
+            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;
+    }
+
+});/*
+---
+
+name: Jx.Sort
+
+description: Base class for the sort algorithm implementations
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Compare
+
+provides: [Jx.Sort]
+
+...
+ */
+// $Id: sort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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;
+    }
+});
+/*
+---
+
+name: Jx.Sort.Mergesort
+
+description: An implementation of the merge sort algorithm
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Mergesort]
+
+...
+ */
+// $Id: mergesort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Mergesort',
+    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,
+            left = arr.slice(0, middle),
+            right = arr.slice(middle),
+            result;
+        left = this.mergeSort(left);
+        right = this.mergeSort(right);
+        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;
+    }
+
+});
+/*
+---
+
+name: Jx.Sort.Heapsort
+
+description: An implementation of the heap sort algorithm
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Heapsort]
+
+...
+ */
+// $Id: heapsort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Heapsort',
+    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,
+            end;
+
+        if (count === 1) {
+            return this.data;
+        }
+
+        if (count > 2) {
+            this.heapify(count);
+
+            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,
+            child;
+
+        while (root * 2 <= end) {
+            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;
+            }
+        }
+    }
+
+});
+/*
+---
+
+name: Jx.Sort.Quicksort
+
+description: An implementation of the quick sort algorithm.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Quicksort]
+
+...
+ */
+// $Id: quicksort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Quicksort',
+    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,
+            pivotValue = (this.data[pivotIndex]).get(this.col),
+            index = left,
+            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,
+            shift = 1,
+            endIndex,
+            medianIndex;
+        while (shift <= (right - left)) {
+            for (i = left; i <= right; i += shift * 5) {
+                endIndex = (i + shift * 5 - 1 < right) ? i + shift * 5 - 1 : right;
+                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),
+            k = Math.round(left + groups / 2 * shift),
+            i,
+            minIndex,
+            v,
+            minValue,
+            j;
+        if (k > this.data.length - 1) {
+            k = this.data.length - 1;
+        }
+        for (i = left; i < k; i += shift) {
+            minIndex = i;
+            v = this.data[minIndex];
+            minValue = v.get(this.col);
+
+            for (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;
+    }
+});
+/*
+---
+
+name: Jx.Sort.Nativesort
+
+description: An implementation of the Javascript native sorting with the Jx.Sort interface
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Nativesort]
+
+...
+ */
+// $Id: nativesort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Sort.Nativesort',
+    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;
+    }
+
+});
+/*
+---
+
+name: Jx.Store.Response
+
+description: The object used to return response information to strategies.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Response]
+
+...
+ */
+// $Id: response.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Response
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is used by the protocol to send information back to the calling 
+ * strategy (or other caller).
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Response = new Class({
+
+    Family: 'Jx.Store.Response',
+    Extends: Jx.Object,
+
+    /**
+     * Property: code
+     * This is the success/failure code
+     */
+    code: null,
+    /**
+     * Property: data
+     * The data passed received by the protocol.
+     */
+    data: null,
+    /**
+     * Property: meta
+     * The metadata received by the protocol
+     */
+    meta: null,
+    /**
+     * Property: requestType
+     * one of 'read', 'insert', 'delete', or 'update'
+     */
+    requestType: null,
+    /**
+     * Property: requestParams
+     * The parameters passed to the method that created this response
+     */
+    requestParams: null,
+    /**
+     * Property: request
+     * the mootools Request object used in this operation (if one is actually
+     * used)
+     */
+    request: null,
+    /**
+     * Property: error
+     * the error data received from the called page if any.
+     */
+    error: null,
+    /**
+     * APIMethod: success
+     * determines if this response represents a successful response
+     */
+    success: function () {
+        return this.code > 0;
+    }
+});
+
+Jx.Store.Response.WAITING = 2;
+Jx.Store.Response.SUCCESS = 1;
+Jx.Store.Response.FAILURE = 0;
+/*
+---
+
+name: Jx.Store.Protocol
+
+description: Base class for all store protocols.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Response
+
+provides: [Jx.Store.Protocol]
+
+...
+ */
+// $Id: protocol.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store.Protocol
+ *
+ * Extends: <Jx.Object>
+ *
+ * Base class for all protocols. Protocols are used for communication, primarily,
+ * in Jx.Store. It may be possible to adapt them to be used in other places but
+ * that is not their intended function.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol = new Class({
+
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Protocol',
+
+    parser: null,
+
+    options: {
+      combine: {
+        insert: false,
+        update: false,
+        'delete': false
+      }
+    },
+
+    init: function () {
+        this.parent();
+
+        if ($defined(this.options.parser)) {
+            this.parser = this.options.parser;
+        }
+    },
+
+    cleanup: function () {
+        this.parser = null;
+        this.parent();
+    },
+
+    /**
+     * APIMethod: read
+     * Supports reading data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * options - optional options for configuring the request
+     */
+    read: $empty,
+    /**
+     * APIMethod: insert
+     * Supports inserting data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to use in creating the record in the form of one or more
+     *        Jx.Store.Record instances
+     * options - optional options for configuring the request
+     */
+    insert: $empty,
+    /**
+     * APIMethod: update
+     * Supports updating data at a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    update: $empty,
+    /**
+     * APIMethod: delete
+     * Supports deleting data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    "delete": $empty,
+    /**
+     * APIMethod: abort
+     * used to abort any of the above methods (where practical). Abstract method
+     * that subclasses should implement.
+     */
+    abort: $empty,
+    /**
+     * APIMethod: combineRequests
+     * tests whether the protocol supports combining multiple records for a given operation
+     * 
+     * Parameter:
+     * operation - {String} the operation to test for multiple record support
+     * 
+     * Returns {Boolean} true if the operation supports it, false otherwise
+     */
+    combineRequests: function(op) {
+      return $defined(this.options.combine[op]) ? this.options.combine[op] : false;
+    }
+});/*
+---
+
+name: Jx.Store.Protocol.Local
+
+description: Store protocol used to load data that is already present in a page as an object.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Protocol
+
+provides: [Jx.Store.Protocol.Local]
+
+...
+ */
+// $Id: protocol.local.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Store.Protocol.Local
+ * 
+ * Extends: Jx.Store.Protocol
+ * 
+ * Based on the Protocol base class, the local protocol uses data that it is
+ * handed upon instantiation to process requests.
+ * 
+ * Constructor Parameters:
+ * data - The data to use 
+ * options - any options for the base protocol class
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * inspired by the openlayers.org implementation of a similar system
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Local = new Class({
+    
+    Extends: Jx.Store.Protocol,
+    
+    parameters: ['data', 'options'],
+    /**
+     * Property: data
+     * The data passed to the protocol
+     */
+    data: null,
+    
+    init: function () {
+        this.parent();
+        
+        if ($defined(this.options.data)) {
+            this.data = this.parser.parse(this.options.data);
+        }
+    },
+    /**
+     * APIMethod: read
+     * process requests for data and sends the appropriate response via the
+     * dataLoaded event.
+     * 
+     * Parameters: 
+     * options - options to use in processing the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response(),
+            page = options.data.page,
+            itemsPerPage = options.data.itemsPerPage,
+            start,
+            end,
+            data = this.data;
+
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+        
+        
+        if ($defined(data)) {
+            if (page <= 1 && itemsPerPage === -1) {
+                //send them all
+                resp.data = data;
+                resp.meta = { count: data.length };
+            } else {
+                start = (page - 1) * itemsPerPage;
+                end = start + itemsPerPage;
+                resp.data = data.slice(start, end);
+                resp.meta = { 
+                    page: page, 
+                    itemsPerPage: itemsPerPage,
+                    totalItems: data.length,
+                    totalPages: Math.ceil(data.length/itemsPerPage)
+                };
+            }
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        } else {
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        }                        
+    }
+    
+    /**
+     * The following methods are not implemented as they make no sense for a
+     * local protocol:
+     * - create
+     * - update 
+     * - delete
+     * - commit
+     * - abort
+     */
+});/*
+---
+
+name: Jx.Store.Protocol.Ajax
+
+description: Store protocol used to load data from a remote data source via Ajax.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Protocol
+ - more/Request.Queue
+
+provides: [Jx.Store.Protocol.Ajax]
+
+...
+ */
+// $Id: protocol.ajax.js 1006 2011-01-01 22:43:42Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Protocol.Ajax
+ *
+ * Extends: <Jx.Store.Protocol>
+ *
+ * This protocol is used to send and receive data via AJAX. It also has the
+ * capability to use a REST-style API.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Ajax = new Class({
+
+    Extends: Jx.Store.Protocol,
+
+    options: {
+        /**
+         * Option: requestOptions
+         * Options to pass to the mootools Request class
+         */
+        requestOptions: {
+            method: 'get'
+        },
+        /**
+         * Option: rest
+         * Flag indicating whether this protocol is operating against a RESTful
+         * web service
+         */
+        rest: false,
+        /**
+         * Option: urls
+         * This is a hash of the urls to use for each method. If the rest
+         * option is set to true the only one needed will be the urls.rest.
+         * These can be overridden if needed by passing an options object into
+         * the various methods with the appropriate urls.
+         */
+        urls: {
+            rest: null,
+            insert: null,
+            read: null,
+            update: null,
+            'delete': null
+        },
+        /**
+         * Option: queue
+         * an object containing options suitable for <Request.Queue>.
+         * By default, autoAdvance is set to true and concurrent is set to 1.
+         */
+        queue: {
+          autoAdvance: true,
+          concurrent: 1
+        }
+    },
+    
+    queue: null,
+
+    init: function() {
+        if (!$defined(Jx.Store.Protocol.Ajax.UniqueId)) {
+          Jx.Store.Protocol.Ajax.UniqueId = 1;
+        }
+      
+        this.queue = new Request.Queue({
+          autoAdvance: this.options.queue.autoAdvance,
+          concurrent: this.options.queue.concurrent
+        });
+        this.parent();
+    },
+    /**
+     * APIMethod: read
+     * Send a read request via AJAX
+     *
+     * Parameters:
+     * options - the options to pass to the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response(),
+            temp = {},
+            opts,
+            req,
+            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+
+
+        // set up options
+        if (this.options.rest) {
+            temp.url = this.options.urls.rest;
+        } else {
+            temp.url = this.options.urls.read;
+        }
+
+        opts = $merge(this.options.requestOptions, temp, options);
+        opts.onSuccess = this.handleResponse.bind(this,resp);
+
+        req = new Request(opts);
+        resp.request = req;
+        
+        this.queue.addRequest(uniqueId, req);
+        req.send();
+
+        resp.code = Jx.Store.Response.WAITING;
+
+        return resp;
+
+    },
+    /**
+     * Method: handleResponse
+     * Called as an event handler for a returning request. Parses the request's
+     * response into the actual response object.
+     *
+     * Parameters:
+     * response - the response related to teh returning request.
+     */
+    handleResponse: function (response) {
+        var req = response.request,
+            str = req.xhr.responseText,
+            data = this.parser.parse(str);
+        if ($defined(data)) {
+            if ($defined(data.success) && data.success) {
+                if ($defined(data.data)) {
+                    response.data = data.data;
+                }
+                if ($defined(data.meta)) {
+                    response.meta = data.meta;
+                }
+                response.code = Jx.Store.Response.SUCCESS;
+            } else {
+                response.code = Jx.Store.Response.FAILURE;
+                response.error = $defined(data.error) ? data.error : null;
+            }
+        } else {
+            response.code = Jx.Store.Response.FAILURE;
+        }
+        this.fireEvent('dataLoaded', response);
+    },
+    /**
+     * APIMethod: insert
+     * Takes a Jx.Record instance and saves it
+     *
+     * Parameters:
+     * record - a Jx.Store.Record or array of them
+     * options - options to pass to the request
+     */
+    insert: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+        } else {
+            options = $merge({url: this.options.urls.insert},options);
+        }
+        this.options.requestOptions.method = 'POST';
+        return this.run(record, options, "insert");
+    },
+    /**
+     * APIMethod: update
+     * Takes a Jx.Record and updates it via AJAX
+     *
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    update: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'PUT';
+        } else {
+            options = $merge({url: this.options.urls.update},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "update");
+    },
+    /**
+     * APIMethod: delete
+     * Takes a Jx.Record and deletes it via AJAX
+     *
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    "delete": function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'DELETE';
+        } else {
+            options = $merge({url: this.options.urls['delete']},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "delete");
+    },
+    /**
+     * APIMethod: abort
+     * aborts the request related to the passed in response.
+     *
+     * Parameters:
+     * response - the response with the request to abort
+     */
+    abort: function (response) {
+        response.request.cancel();
+
+    },
+    /**
+     * Method: run
+     * called by update, delete, and insert methods that actually does the work
+     * of kicking off the request.
+     *
+     * Parameters:
+     * record - The Jx.Record to work with
+     * options - Options to pass to the request
+     * method - The name of the method calling this function
+     */
+    run: function (record, options, method) {
+        var resp = new Jx.Store.Response(),
+            opts,
+            req,
+            data,
+            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();
+        
+        if (Jx.type(record) == 'array') {
+          if (!this.combineRequests(method)) {
+            record.each(function(r) {
+              this.run(r, options, method);
+            }, this);
+          } else {
+            data = [];
+            record.each(function(r) {
+              data.push(this.parser.encode(r));
+            }, this);
+          }
+        } else {
+          data = this.parser.encode(record);
+        }
+
+        this.options.requestOptions.data = $merge(this.options.requestOptions.data, {
+          data: data
+        });
+
+        resp.requestType = method;
+        resp.requestParams = [record, options, method];
+
+        //set up options
+        opts = $merge(this.options.requestOptions, options);
+        opts.onSuccess = this.handleResponse.bind(this,resp);
+        req = new Request(opts);
+        resp.request = req;
+        this.queue.addRequest(uniqueId, req);
+        req.send();
+
+        resp.code = Jx.Store.Response.WAITING;
+
+        return resp;
+    }
+    
+});
+/**
+ * Method: uniqueId
+ * returns a unique identifier to be used with queued requests
+ */
+Jx.Store.Protocol.Ajax.UniqueId = (function() {
+  var uniqueId = 1;
+  return function() {
+    return 'req-'+(uniqueId++);
+  };
+})();
+/*
+---
+
+name: Jx.Store.Strategy
+
+description: Base class for all store strategies.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Strategy]
+
+
+...
+ */
+// $Id: strategy.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all Jx.Store strategies
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Strategy',
+    /**
+     * APIProperty: store
+     * The store this strategy is associated with
+     */
+    store: null,
+    /**
+     * APIProperty: active
+     * whether this strategy has been activated or not.
+     */
+    active: null,
+    
+    /**
+     * Method: init
+     * initialize the strategy, should be called by subclasses
+     */
+    init: function () {
+        this.parent();
+        this.active = false;
+    },
+    /**
+     * APIMethod: setStore
+     * Associates this strategy with a particular store.
+     */
+    setStore: function (store) {
+        if (store instanceof Jx.Store) {
+            this.store = store;
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if (!this.active) {
+            this.active = true;
+            return true;
+        }
+        return false;
+    },
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if (this.active) {
+            this.active = false;
+            return true;
+        }
+        return false;
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Full
+
+description: Strategy for loading the full data set from a source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Full]
+
+...
+ */
+// $Id: strategy.full.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Full
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * This is a strategy for loading all of the data from a source at one time.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Strategy.Full = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'full',
+    
+    options:{},
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+        
+    },
+    /**
+     * APIMethod: load
+     * Called as the eventhandler for the store load method. Can also
+     * be called independently to load data into the current store.
+     * 
+     * Parameters:
+     * params - a hash of parameters to use in loading the data.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        var opts = {}
+        if ($defined(params)) {
+            opts.data = params;
+        } else {
+            opts.data = {};
+        }
+        opts.data.page = 0;
+        opts.data.itemsPerPage = -1;
+        this.store.protocol.read(opts);
+    },
+    
+    /**
+     * Method: loadStore
+     * Called as the event handler for the protocol's dataLoaded event. Checks
+     * the response for success and loads the data into the store if needed.
+     * 
+     * Parameters:
+     * resp - the response from the protocol
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            this.store.empty();
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.store.addRecords(resp.data);
+            this.store.loaded = true;
+            this.store.fireEvent('storeDataLoaded',this.store);
+        } else {
+            this.store.loaded = false;
+            this.store.fireEvent('storeDataLoadFailed', [this.store, resp]);
+        }
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the meta property of the response object and puts the data 
+     * where it belongs.
+     * 
+     * Parameters:
+     * meta - the meta data object from the response.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Paginate
+
+description: Strategy for loading data in pages and moving between them. This strategy makes sure the store only contains the current page's data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Paginate]
+
+
+...
+ */
+// $Id: strategy.paginate.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Paginate
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Store strategy for paginating results in a store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Paginate = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'paginate',
+    
+    options: {
+        /**
+         * Option: getPaginationParams
+         * a function that returns an object that holds the parameters
+         * necessary for getting paginated data from a protocol.
+         */
+        getPaginationParams: function () {
+            return {
+                page: this.page,
+                itemsPerPage: this.itemsPerPage
+            };
+        },
+        /**
+         * Option: startingItemsPerPage
+         * Used to set the intial itemsPerPage for the strategy. the pageSize 
+         * can be changed using the setPageSize() method.
+         */
+        startingItemsPerPage: 25,
+        /**
+         * Option: startingPage
+         * The page to start on. Defaults to 1 but can be set to any other 
+         * page.
+         */
+        startingPage: 1,
+        /**
+         * Option: expirationInterval
+         * The interval, in milliseconds (1000 = 1 sec), to hold a page of
+         * data before it expires. If the page is expired, the next time the
+         * page is accessed it must be retrieved again. Default is 5 minutes
+         * (1000 * 60 * 5)
+         */
+        expirationInterval: (1000 * 60 * 5),
+        /**
+         * Option: ignoreExpiration
+         * Set to TRUE to ignore the expirationInterval setting and never
+         * expire pages.
+         */
+        ignoreExpiration: false
+    },
+    /**
+     * Property: data
+     * holds the pages of data keyed by page number.
+     */
+    data: null,
+    /**
+     * property: cacheTimer
+     * holds one or more cache timer ids - one per page. Each page is set to 
+     * expire after a certain amount of time.
+     */
+    cacheTimer: null,
+    /**
+     * Property: page
+     * Tracks the page the store currently holds.
+     */
+    page: null,
+    /**
+     * Property: itemsPerPage
+     * The number of items on each page
+     */
+    itemsPerPage: null,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.data = new Hash();
+        this.cacheTimer = new Hash();
+        //set up bindings that we need here
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+        this.itemsPerPage = this.options.startingItemsPerPage;
+        this.page = this.options.startingPage;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+    },
+    /**
+     * APIMethod: load
+     * Called to load data into the store
+     * 
+     * Parameters:
+     * params - a Hash of parameters to use in getting data from the protocol.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        this.params = params;
+        var opts = {
+            data: $merge(params, this.options.getPaginationParams.apply(this))
+        };
+        this.store.protocol.read(opts);
+    },
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.data.set(this.page,resp.data);
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.empty();
+        this.store.loaded = false;
+        if (!this.options.ignoreExpiration) {
+            var id = this.expirePage.delay(this.options.expirationInterval, this, this.page);
+            this.cacheTimer.set(this.page,id);
+        }
+        this.store.addRecords(data);
+        this.store.loaded = true;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the metadata returned from the protocol and places it in the
+     * appropriate Vplaces.
+     * 
+     * Parameters:
+     * meta - the meta data object returned from the protocol.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.totalItems)) {
+            this.totalItems = meta.totalItems;
+        }
+        if ($defined(meta.totalPages)) {
+            this.totalPages = meta.totalPages;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+            
+    },
+    /**
+     * Method: expirePage
+     * Is called when a pages cache timer expires. Will expire the page by 
+     * erasing the page and timer. This will force a reload of the data the 
+     * next time the page is accessed.
+     * 
+     * Parameters:
+     * page - the page number to expire.
+     */
+    expirePage: function (page) {
+        this.data.erase(page);
+        this.cacheTimer.erase(page);
+    },
+    /**
+     * APIMethod: setPage
+     * Allows a caller (i.e. a paging toolbar) to move to a specific page.
+     * 
+     * Parameters:
+     * page - the page to move to. Can be any absolute page number, any number
+     *        prefaced with '-' or '+' (i.e. '-1', '+3'), 'first', 'last', 
+     *        'next', or 'previous'
+     */
+    setPage: function (page) {
+        if (Jx.type(page) === 'string') {
+            switch (page) {
+                case 'first':
+                    this.page = 1;
+                    break;
+                case 'last':
+                    this.page = this.totalPages;
+                    break;
+                case 'next':
+                    this.page++;
+                    break;
+                case 'previous':
+                    this.page--;
+                    break;
+                default:
+                    this.page = this.page + Jx.getNumber(page);
+                    break;
+            }
+        } else {
+            this.page = page;
+        }
+        if (this.cacheTimer.has(this.page)) {
+            $clear(this.cacheTimer.get(this.page));
+            this.cacheTimer.erase(this.page);
+        }
+        if (this.data.has(this.page)){
+            this.loadData(this.data.get(this.page));
+        } else {
+            this.load(this.params);
+        }
+    },
+    /**
+     * APIMethod: getPage
+     * returns the current page
+     */
+    getPage: function () {
+        return this.page;
+    },
+    /**
+     * APIMethod: getNumberOfPages
+     * returns the total number of pages.
+     */
+    getNumberOfPages: function () {
+        return this.totalPages;
+    },
+    /**
+     * APIMethod: setPageSize
+     * sets the current size of the pages. Calling this will expire every page 
+     * and force the current one to reload with the new size.
+     */
+    setPageSize: function (size) {
+        //set the page size 
+        this.itemsPerPage = size;
+        //invalidate all pages cached and reload the current one only
+        this.cacheTimer.each(function(val){
+            $clear(val);
+        },this);
+        this.cacheTimer.empty();
+        this.data.empty();
+        this.load();
+    },
+    /**
+     * APIMethod: getPageSize
+     * returns the current page size
+     */
+    getPageSize: function () {
+        return this.itemsPerPage;
+    },
+    /**
+     * APIMethod: getTotalCount
+     * returns the total number of items as received from the protocol.
+     */
+    getTotalCount: function () {
+        return this.totalItems;
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Progressive
+
+description: Strategy based on Strategy.Paginate but loads data progressively without removing old or curent data from the store.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy.Paginate
+
+provides: [Jx.Store.Strategy.Progressive]
+
+...
+ */
+/**
+ * Class: Jx.Store.Strategy.Progressive
+ *
+ * Extends: <Jx.Store.Strategy.Paginate>
+ *
+ * Store strategy for progressively obtaining results in a store. You can
+ * continually call nextPage() to get the next page and the store will retain
+ * all current data. You can set a maximum number of records the store should
+ * hold and whether it should dropRecords when that max is hit.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Progressive = new Class({
+    
+    Extends: Jx.Store.Strategy.Paginate,
+    
+    name: 'progressive',
+    
+    options: {
+        /**
+         * Option: maxRecords
+         * The maximum number of records we want in the store at any one time.
+         */
+        maxRecords: 1000,
+        /**
+         * Option: dropRecords
+         * Whether the strategy should drop records when the maxRecords limit 
+         * is reached. if this is false then maxRecords is ignored and data is
+         * always added to the bottom of the store. 
+         */
+        dropRecords: true
+    },
+    /**
+     * Property: startingPage
+     */
+    startingPage: 0,
+    /**
+     * Property: maxPages
+     */
+    maxPages: null,
+    /**
+     * Property: loadedPages
+     */
+    loadedPages: 0,
+    /**
+     * Property: loadAt
+     * Options are 'top' or 'bottom'. Defaults to 'bottom'.
+     */
+    loadAt: 'bottom',
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        if (this.options.dropPages) {
+            this.maxPages = Math.ceil(this.options.maxRecords/this.itemsPerPage);
+        }
+    },
+    
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.loaded = false;
+        this.store.addRecords(data, this.loadAt);
+        this.store.loaded = true;
+        this.loadedPages++;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    
+    /**
+     * APIMethod: nextPage
+     * Allows a caller (i.e. a paging toolbar) to load more data to the end of 
+     * the store
+     * 
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    nextPage: function (params) {
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.options.dropRecords && this.totalPages > this.startingPage + this.loadedPages) {
+            this.loadAt = 'bottom';
+            if (this.loadedPages >= this.maxPages) {
+                //drop records before getting more
+                this.startingPage++;
+                this.store.removeRecords(0,this.itemsPerPage - 1);
+                this.loadedPages--;
+            }
+        }
+        this.page = this.startingPage + this.loadedPages + 1;
+        this.load($merge(this.params, params));
+    },
+    /**
+     * APIMethod: previousPage
+     * Allows a caller to move back to the previous page.
+     *
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    previousPage: function (params) {
+        //if we're not dropping pages there's nothing to do
+        if (!this.options.dropRecords) {
+            return;
+        }
+        
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.startingPage > 0) {
+            this.loadAt = 'top';
+            if (this.loadedPages >= this.maxPages) {
+                //drop off end before loading previous pages
+                this.startingPage--;
+                this.store.removeRecords(this.options.maxRecords - this.itemsPerPage, this.options.maxRecords);
+                this.loadedPages--;
+            }
+            this.page = this.startingPage;
+            this.load($merge(this.params, params));
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Save
+
+description: Strategy used for saving data back to a source. Can be called manually or setup to automatically save on every change.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Save]
+
+...
+ */
+// $Id: strategy.save.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Save 
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * A Store strategy class for saving data via protocols
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Save = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'save',
+    
+    options: {
+        /**
+         * Option: autoSave
+         * Whether the strategy should be watching the store to save changes
+         * automatically. Set to True to watch events, set it to a number of 
+         * milliseconds to have the strategy save every so many seconds
+         */
+        autoSave: false
+    },
+    /**
+     * Property: failedChanges
+     * an array holding all failed requests
+     */
+    failedChanges: [],
+    /**
+     * Property: successfulChanges
+     * an array holding all successful requests
+     */
+    successfulChanges: [],
+    /**
+     * Property: totalChanges
+     * The total number of changes being processed. Used to determine
+     * when to fire off the storeChangesCompleted event on the store
+     */
+    totalChanges: 0,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.bound.save = this.saveRecord.bind(this);
+        this.bound.update = this.updateRecord.bind(this);
+        this.bound.completed = this.onComplete.bind(this);
+        this.parent();
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        if (Jx.type(this.options.autoSave) === 'number') {
+            this.periodicalId = this.save.periodical(this.options.autoSave, this);
+        } else if (this.options.autoSave) {
+            this.store.addEvent('storeRecordAdded', this.bound.save);
+            this.store.addEvent('storeColumnChanged', this.bound.update);
+            this.store.addEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        if ($defined(this.periodicalId)) {
+            $clear(this.periodicalId);
+        } else if (this.options.autoSave) {
+            this.store.removeEvent('storeRecordAdded', this.bound.save);
+            this.store.removeEvent('storeColumnChanged', this.bound.update);
+            this.store.removeEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: updateRecord
+     * called by event handlers when store data is updated
+     *
+     * Parameters:
+     * index - {Integer} the row that was affected
+     * column - {String} the column that was affected
+     * oldValue - {Mixed} the previous value
+     * newValue - {Mixed} the new value
+     */
+    updateRecord: function(index, column, oldValue, newValue) {
+      var resp = this.saveRecord(this.store, this.store.getRecord(index));
+      // no response if updating or record state not set
+      if (resp) {
+        resp.index = index;
+      }
+    },
+    /**
+     * APIMethod: saveRecord
+     * Called by event handlers when a store record is added, or deleted. 
+     * If deleted, the record will be removed from the deleted array.
+     * 
+     * Parameters:
+     * record - The Jx.Record instance that was changed
+     * store - The instance of the store
+     */
+    saveRecord: function (store, record) {
+        //determine the status and route based on that
+        if (!this.updating && $defined(record.state)) {
+            if (this.totalChanges === 0) {
+                store.protocol.addEvent('dataLoaded', this.bound.completed);
+            }
+            this.totalChanges++;
+            var ret;
+            switch (record.state) {
+                case Jx.Record.UPDATE:
+                    ret = store.protocol.update(record);
+                    break;
+                case Jx.Record.DELETE:
+                    ret = store.protocol['delete'](record);
+                    break;
+                case Jx.Record.INSERT:
+                    ret = store.protocol.insert(record);
+                    break;
+                default:
+                  break;
+            }
+            return ret;
+        }
+    },
+    /**
+     * APIMethod: save
+     * Called manually when the developer wants to save all data changes 
+     * in one shot. It will empty the deleted array and reset all other status 
+     * flags
+     */
+    save: function () {
+        //go through all of the data and figure out what needs to be acted on
+        if (this.store.loaded) {
+            var records = [];
+            records[Jx.Record.UPDATE] = [];
+            records[Jx.Record.INSERT] = [];
+            
+            this.store.data.each(function (record) {
+                if ($defined(record) && $defined(record.state)) {
+                    records[record.state].push(record);
+                }
+            }, this);
+            records[Jx.Record.DELETE] = this.store.deleted;
+            
+            if (!this.updating) {
+              if (this.totalChanges === 0) {
+                  store.protocol.addEvent('dataLoaded', this.bound.completed);
+              }
+              this.totalChanges += records[Jx.Record.UPDATE].length + 
+                                   records[Jx.Record.INSERT].length +
+                                   records[Jx.Record.DELETE].length;
+              if (records[Jx.Record.UPDATE].length) {
+                this.store.protocol.update(records[Jx.Record.UPDATE]);
+              }
+              if (records[Jx.Record.INSERT].length) {
+                this.store.protocol.insert(records[Jx.Record.INSERT]);
+              }
+              if (records[Jx.Record.DELETE].length) {
+                this.store.protocol['delete'](records[Jx.Record.DELETE]);
+              }
+            }
+            
+            // records.flatten().each(function (record) {
+            //     this.saveRecord(this.store, record);
+            // }, this);
+        }
+        
+    },
+    /**
+     * Method: onComplete
+     * Handles processing of the response(s) from the protocol. Each 
+     * update/insert/delete will have an individual response. If any responses 
+     * come back failed we will hold that response and send it to the caller
+     * via the fired event. This method is responsible for updating the status
+     * of each record as it returns and on inserts, it updates the primary key
+     * of the record. If it was a delete it will remove it permanently from
+     * the store's deleted array (provided it returns successful - based on
+     * the success attribute of the meta object). When all changes have been 
+     * accounted for the method fires a finished event and passes all of the 
+     * failed responses to the caller so they can be handled appropriately.
+     * 
+     * Parameters:
+     * response - the response returned from the protocol
+     */
+    onComplete: function (response) {
+        if (!response.success() || ($defined(response.meta) && !response.meta.success)) {
+            this.failedChanges.push(response);
+        } else {
+            //process the response
+            var records = [response.requestParams[0]].flatten(),
+                responseData = $defined(response.data) ? [response.data].flatten() : null;
+            records.each(function(record, index) {
+              if (response.requestType === 'delete') {
+                  this.store.deleted.erase(record);
+              } else { 
+                  if (response.requestType === 'insert' || response.requestType == 'update') {
+                      if (responseData && $defined(responseData[index])) {
+                          this.updating = true;
+                          $H(responseData[index]).each(function (val, key) {
+                              var d = record.set(key, val);
+                              if (d[1] != val) {
+                                d.unshift(index);
+                                record.store.fireEvent('storeColumnChanged', d);
+                              }
+                          });
+                          this.updating = false;
+                      }
+                  }
+                  record.state = null;
+              } 
+              this.totalChanges--;
+          }, this);
+          this.successfulChanges.push(response);
+        }
+        if (this.totalChanges === 0) {
+            this.store.protocol.removeEvent('dataLoaded', this.bound.completed);
+            this.store.fireEvent('storeChangesCompleted', {
+                successful: this.successfulChanges,
+                failed: this.failedChanges
+            });
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Sort
+
+description: Strategy used for sorting results in a store after they are loaded.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+ - Jx.Sort.Mergesort
+ - Jx.Compare
+
+provides: [Jx.Store.Strategy.Sort]
+...
+ */
+// $Id: strategy.sort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Sort
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Strategy used for sorting stores. It can either be called manually or it
+ * can listen for specific events from the store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Sort = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'sort',
+    
+    options: {
+        /**
+         * Option: sortOnStoreEvents
+         * an array of events this strategy should listen for on the store and
+         * sort when it sees them.
+         */
+        sortOnStoreEvents: ['storeColumnChanged','storeDataLoaded'],
+        /**
+         * 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 : []
+    },
+    
+    /**
+     * Property: sorters
+     * an object listing the different sorters available
+     */
+    sorters : {
+        quick : "Quicksort",
+        merge : "Mergesort",
+        heap : "Heapsort",
+        'native' : "Nativesort"
+    },
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.sort = this.sort.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.addEvent(ev, this.bound.sort);
+            },this);
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.removeEvent(ev, this.bound.sort);
+            },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.store.count()) {
+            this.store.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.store.data = this.doSort(c[0], sort, this.store.data, true);
+        
+            if (c.length > 1) {
+                this.store.data = this.subSort(this.store.data, 0, 1);
+            }
+        
+            if ($defined(dir) && dir === 'desc') {
+                this.store.data.reverse();
+            }
+        
+            this.store.fireEvent('storeSortFinished', 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: resolveCol
+     * resolves the given column identifier and resolves it to the 
+     * actual column object in the store.
+     * 
+     * Parameters:
+     * col - the name or index of the required column.
+     */
+    resolveCol: function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.store.options.columns[col];
+        } else if (t === 'string') {
+            this.store.options.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;   
+    }
+});/*
+---
+
+name: Jx.Store.Parser
+
+description: Base class for all data parsers. Parsers are used by protocols to get data received or sent in the proper formats.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Parser]
+
+...
+ */
+// $Id: parser.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Parser
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all parsers
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Parser = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Parser',
+    
+    /**
+     * APIMethod: parse
+     * Reads data passed to it by a protocol and parses it into a specific
+     * format needed by the store/record.
+     * 
+     * Parameters:
+     * data - string of data to parse
+     */
+    parse: $empty,
+    /**
+     * APIMethod: encode
+     * Takes an Jx.Record object and encodes it into a format that can be transmitted 
+     * by a protocol.
+     * 
+     * Parameters:
+     * object - an object to encode
+     */
+    encode: $empty
+});/*
+---
+
+name: Jx.Store.Parser.JSON
+
+description: Parser for reading and writting JSON formatted data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Parser
+ - Core/JSON
+
+provides: [Jx.Store.Parser.JSON]
+
+...
+ */
+// $Id: parser.json.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Store.Parser.JSON
+ *
+ * Extends: <Jx.Store.Parser>
+ *
+ * A Parser that handles encoding and decoding JSON strings
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Parser.JSON = new Class({
+
+    Extends: Jx.Store.Parser,
+
+    options: {
+        /**
+         * Option: secure
+         * Whether to use secure decoding. When using secure decoding the
+         * parser will return null if any invalid JSON characters are in the
+         * passed in string. Defaults to false.
+         */
+        secure: false
+    },
+    /**
+     * APIMethod: parse
+     * Turns a string into a JSON object if possible.
+     *
+     * Parameters:
+     * data - the string representation of the data we're parsing
+     */
+    parse: function (data) {
+        var type = Jx.type(data);
+
+        if (type === 'string') {
+            return JSON.decode(data, this.options.secure);
+        }
+        //otherwise just return the data object
+        return data;
+    },
+
+    /**
+     * APIMethod: encode
+     * Takes an object and turns it into JSON.
+     *
+     * Parameters:
+     * object - the object to encode
+     */
+    encode: function (object) {
+        var data;
+        if (object instanceof Jx.Record) {
+            data = object.asHash();
+        } else {
+            data = object;
+        }
+
+        return JSON.encode(data);
+    }
+});/*
+---
+
+name: Jx.Button
+
+description: Jx.Button creates a clickable element that can be added to a web page.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+optional:
+ - Core/Drag
+
+provides: [Jx.Button]
+
+css:
+ - button
+
+images:
+ - button.png
+
+...
+ */
+// $Id: button.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button
+ *
+ * Extends: <Jx.Widget>
+ *
+ * Jx.Button creates a clickable element that can be added to a web page.
+ * When the button is clicked, it fires a 'click' event.
+ *
+ * When you construct a new instance of Jx.Button, the button does not
+ * automatically get inserted into the web page.  Typically a button
+ * is used as part of building another capability such as a Jx.Toolbar.
+ * However, if you want to manually insert the button into your application,
+ * you may use the <Jx.Button::addTo> method to append or insert the button into the
+ * page.
+ *
+ * There are two modes for a button, normal and toggle.  A toggle button
+ * has an active state analogous to a checkbox.  A toggle button generates
+ * different events (down and up) from a normal button (click).  To create
+ * a toggle button, pass toggle: true to the Jx.Button constructor.
+ *
+ * To use a Jx.Button in an application, you should to register for the
+ * 'click' event.  You can pass a function in the 'onClick' option when
+ * constructing a button or you can call the addEvent('click', myFunction)
+ * method.  The addEvent method can be called several times, allowing more
+ * than one function to be called when a button is clicked.  You can use the
+ * removeEvent('click', myFunction) method to stop receiving click events.
+ *
+ * Example:
+ *
+ * (code)
+ * var button = new Jx.Button(options);
+ * button.addTo('myListItem'); // the id of an LI in the page.
+ * (end)
+ *
+ * (code)
+ * Example:
+ * var options = {
+ *     imgPath: 'images/mybutton.png',
+ *     tooltip: 'click me!',
+ *     label: 'click me',
+ *     onClick: function() {
+ *         alert('you clicked me');
+ *     }
+ * };
+ * var button = new Jx.Button(options);
+ * button.addEvent('click', anotherFunction);
+ *
+ * function anotherFunction() {
+ *   alert('a second alert for a single click');
+ * }
+ * (end)
+ *
+ * Events:
+ * click - the button was pressed and released (only if type is not 'toggle').
+ * down - the button is down (only if type is 'toggle')
+ * up - the button is up (only if the type is 'toggle').
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button = new Class({
+    Family: 'Jx.Button',
+    Extends: Jx.Widget,
+
+    options: {
+        /* 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
+         * 16.  If not provided, then the button will have no icon.
+         */
+        image: '',
+        /* Option: tooltip
+         * optional.  A string value to use as the alt/title attribute of the
+         * <A> tag that wraps the button, resulting in a tooltip that appears
+         * when the user hovers the mouse over a button in most browsers.  If
+         * not provided, the button will have no tooltip.
+         */
+        tooltip: '',
+        /* Option: label
+         * optional, default is no label.  A string value that is used as a
+         * label on the button. - use an object for localization: { set: 'Examples', key: 'lanKey', value: 'langValue' }
+         * see widget.js for details
+         */
+        label: '',
+        /* Option: toggle
+         * default true, whether the button is a toggle button or not.
+         */
+        toggle: false,
+        /* Option: toggleClass
+         * A class to apply to the button if it is a toggle button,
+         * 'jxButtonToggle' by default.
+         */
+        toggleClass: 'jxButtonToggle',
+        /* Option: pressedClass
+         * A class to apply to the button when it is pressed,
+         * 'jxButtonPressed' by default.
+         */
+        pressedClass: 'jxButtonPressed',
+        /* Option: activeClass
+         * A class to apply to the buttonwhen it is active,
+         * 'jxButtonActive' by default.
+         */
+        activeClass: 'jxButtonActive',
+
+        /* Option: active
+         * optional, default false.  Controls the initial state of toggle
+         * buttons.
+         */
+        active: false,
+        /* Option: enabled
+         * whether the button is enabled or not.
+         */
+        enabled: true,
+        /* Option: href
+         * set an href on the button's action object, typically an <a> tag.
+         * Default is javascript:void(0) and use onClick.
+         */
+        href: 'javascript:void(0);',
+        /* Option: target
+         * for buttons that have an href, allow setting the target
+         */
+        target: '',
+        /* 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.
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * used to auto-populate this object with element references when
+     * processing templates
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * create a new button.
+     */
+    render: function() {
+        this.parent();
+        var options = this.options,
+            hasFocus,
+            mouseDown;
+        /* is the button toggle-able? */
+        if (options.toggle) {
+            this.domObj.addClass(options.toggleClass);
+        }
+
+        // the clickable part of the button
+        if (this.domA) {
+            this.domA.set({
+                target: options.target,
+                href: options.href,
+                title: this.getText(options.tooltip),
+                alt: this.getText(options.tooltip)
+            });
+            this.domA.addEvents({
+                click: this.clicked.bindWithEvent(this),
+                drag: (function(e) {e.stop();}).bindWithEvent(this),
+                mousedown: (function(e) {
+                    this.domA.addClass(options.pressedClass);
+                    hasFocus = true;
+                    mouseDown = true;
+                    this.focus();
+                }).bindWithEvent(this),
+                mouseup: (function(e) {
+                    this.domA.removeClass(options.pressedClass);
+                    mouseDown = false;
+                }).bindWithEvent(this),
+                mouseleave: (function(e) {
+                    this.domA.removeClass(options.pressedClass);
+                }).bindWithEvent(this),
+                mouseenter: (function(e) {
+                    if (hasFocus && mouseDown) {
+                        this.domA.addClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keydown: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.addClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keyup: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.removeClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                blur: function() { hasFocus = false; }
+            });
+
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if (this.domImg) {
+            if (options.image || !options.label) {
+                this.domImg.set({
+                    title: this.getText(options.tooltip),
+                    alt: this.getText(options.tooltip)
+                });
+                if (options.image && options.image.indexOf(Jx.aPixel.src) == -1) {
+                    this.domImg.setStyle('backgroundImage',"url("+options.image+")");
+                }
+                if (options.imageClass) {
+                    this.domImg.addClass(options.imageClass);
+                }
+            } else {
+                //remove the image if we don't need it
+                this.domImg.setStyle('display','none');
+            }
+        }
+
+        if (this.domLabel) {
+            if (options.label || this.domA.hasClass('jxDiscloser')) {
+                this.setLabel(options.label);
+            } else {
+                //this.domLabel.removeClass('jx'+this.type+'Label');
+                this.domLabel.setStyle('display','none');
+            }
+        }
+
+        if (options.id) {
+            this.domObj.set('id', options.id);
+        }
+
+        //update the enabled state
+        this.setEnabled(options.enabled);
+
+        //update the active state if necessary
+        if (options.active) {
+            options.active = false;
+            this.setActive(true);
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     *
+     * Parameters:
+     * evt - {Event} the user click event
+     */
+    clicked : function(evt) {
+        var options = this.options;
+        if (options.enabled && !this.isBusy()) {
+            if (options.toggle) {
+                this.setActive(!options.active);
+            } else {
+                this.fireEvent('click', {obj: this, event: evt});
+            }
+        }
+        //return false;
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the button is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the button is enabled or not
+     */
+    isEnabled: function() {
+        return this.options.enabled;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the button.
+     *
+     * Parameters:
+     * enabled - {Boolean} the new enabled state of the button
+     */
+    setEnabled: function(enabled) {
+        this.options.enabled = enabled;
+        if (enabled) {
+            this.domObj.removeClass('jxDisabled');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    /**
+     * APIMethod: isActive
+     * For toggle buttons, this returns true if the toggle button is
+     * currently active and false otherwise.
+     *
+     * Returns:
+     * {Boolean} the active state of a toggle button
+     */
+    isActive: function() {
+        return this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the button
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the button
+     */
+    setActive: function(active) {
+        var options = this.options;
+        if (options.enabled && !this.isBusy()) {
+          if (options.active == active) {
+              return;
+          }
+          options.active = active;
+          if (this.domA) {
+              if (options.active) {
+                  this.domA.addClass(options.activeClass);
+              } else {
+                  this.domA.removeClass(options.activeClass);
+              }
+          }
+          this.fireEvent(active ? 'down':'up', this);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this button to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this button
+     */
+    setImage: function(path) {
+        this.options.image = path;
+        if (this.domImg) {
+            this.domImg.setStyle('backgroundImage',
+                                 "url("+path+")");
+            this.domImg.setStyle('display', path ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     * sets the text of the button.
+     *
+     * Parameters:
+     * label - {String} the new label for the button
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html', this.getText(label));
+            this.domLabel.setStyle('display', label || this.domA.hasClass('jxDiscloser') ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     * returns the text of the button.
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the button
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.domA) {
+            this.domA.set({
+                'title':this.getText(tooltip),
+                'alt':this.getText(tooltip)
+            });
+        }
+        //need to account for the tooltip on the image as well
+        if (this.domImg) {
+            //check if title and alt are set...
+            var t = this.domImg.get('title');
+            if ($defined(t)) {
+                //change it...
+                this.domImg.set({
+                    'title':this.getText(tooltip),
+                    'alt':this.getText(tooltip)
+                });
+            }
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this button
+     */
+    focus: function() {
+        if (this.domA) {
+            this.domA.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this button
+     */
+    blur: function() {
+        if (this.domA) {
+            this.domA.blur();
+        }
+    },
+
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the button on langChange Event for
+     * Internationalization
+     */
+    changeText : function(lang) {
+        this.parent();
+        this.setLabel(this.options.label);
+        this.setTooltip(this.options.tooltip);
+    }
+});
+/*
+---
+
+name: Jx.Button.Flyout
+
+description: Flyout buttons expose a panel when the user clicks the button.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+
+provides: [Jx.Button.Flyout]
+
+images:
+ - flyout_chrome.png
+ - emblems.png
+
+...
+ */
+// $Id: flyout.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Flyout
+ *
+ * Extends: <Jx.Button>
+ *
+ * Flyout buttons expose a panel when the user clicks the button.  The
+ * panel can have arbitrary content.  You must provide any necessary
+ * code to hook up elements in the panel to your application.
+ *
+ * When the panel is opened, the 'open' event is fired.  When the panel is
+ * closed, the 'close' event is fired.  You can register functions to handle
+ * these events in the options passed to the constructor (onOpen, onClose).
+ *
+ * The user can close the flyout panel by clicking the button again, by
+ * clicking anywhere outside the panel and other buttons, or by pressing the
+ * 'esc' key.
+ *
+ * Flyout buttons implement <Jx.ContentLoader> which provides the hooks to
+ * insert content into the Flyout element.  Note that the Flyout element
+ * is not appended to the DOM until the first time it is opened, and it is
+ * removed from the DOM when closed.
+ *
+ * It is generally best to specify a width and height for your flyout content
+ * area through CSS to ensure that it works correctly across all browsers.
+ * You can do this for all flyouts using the .jxFlyout CSS selector, or you
+ * can apply specific styles to your content elements.
+ *
+ * A flyout closes other flyouts when it is opened.  It is possible to embed
+ * 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.
+ *
+ * Example:
+ * (code)
+ * var flyout = new Jx.Button.Flyout({
+ *      label: 'flyout',
+ *      content: 'flyoutContent',
+ *      onOpen: function(flyout) {
+ *          console.log('flyout opened');
+ *      },
+ *      onClose: function(flyout) {
+ *          console.log('flyout closed');
+ *      }
+ * });
+ * (end)
+ *
+ * Events:
+ * open - this event is triggered when the flyout is opened.
+ * close - this event is triggered when the flyout is closed.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Flyout = new Class({
+    Family: 'Jx.Button.Flyout',
+    Extends: Jx.Button,
+    Binds: ['keypressHandler', 'clickHandler'],
+    options: {
+        /* Option: template
+         * the HTML structure of the flyout button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',
+        /* Option: contentTemplate
+         * the HTML structure of the flyout content area
+         */
+        contentTemplate: '<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>',
+        /* Option: position
+         * where to position the flyout, see Jx.Widget::position
+         * for details on how to specify this option
+         */
+        position: {
+          horizontal: ['left left', 'right right'],
+          vertical: ['bottom top', 'top bottom']
+        },
+        /* Option: positionElement
+         * the element to position the flyout relative to, by default
+         * it is the domObj of this button and should only be changed
+         * if you really know what you are doing
+         */
+        positionElement: null
+    },
+
+    /**
+     * Property: contentClasses
+     * the classes array for processing the contentTemplate
+     */
+    contentClasses: new Hash({
+        contentContainer: 'jxFlyout',
+        content: 'jxFlyoutContent'
+    }),
+
+    /**
+     * Property: content
+     * the HTML element that contains the flyout content
+     */
+    content: null,
+    /**
+     * Method: render
+     * construct a new instance of a flyout button.
+     */
+    render: function() {
+        var options = this.options;
+        if (!Jx.Button.Flyout.Stack) {
+            Jx.Button.Flyout.Stack = [];
+        }
+        this.parent();
+        this.processElements(options.contentTemplate, this.contentClasses);
+
+        if (options.contentClass) {
+            this.content.addClass(options.contentClass);
+        }
+
+        this.content.store('jxFlyout', this);
+        if(!options.loadOnDemand || options.active) {
+          this.loadContent(this.content);
+        }else{
+          this.addEvent('contentLoaded', function(ev) {
+            this.show(ev);
+          }.bind(this));
+        }
+    },
+    cleanup: function() {
+      this.content.eliminate('jxFlyout');
+      this.content.destroy();
+      this.contentClasses.each(function(v,k){
+        this[k] = null;
+      }, this);
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * Override <Jx.Button::clicked> to hide/show the content area of the
+     * flyout.
+     *
+     * Parameters:
+     * e - {Event} the user event
+     */
+    clicked: function(e) {
+        var options = this.options;
+        if (!options.enabled) {
+            return;
+        }
+        if (this.contentIsLoaded && options.cacheContent) {
+          this.show(e);
+        // load on demand or reload content if caching is disabled
+        } else if (options.loadOnDemand || !options.cacheContent) {
+          this.loadContent(this.content);
+        } else {
+          this.show(e);
+        }
+    },
+   /**
+    * Private Method: show
+    * Shows the Flyout after the content is loaded asynchronously
+    *
+    * Parameters:
+    * e - {Event} - the user or contentLoaded event
+    */
+    show: function(e) {
+        var node,
+            flyout,
+            owner = this.owner,
+            stack = Jx.Button.Flyout.Stack,
+            options = this.options;
+       /* find out what we are contained by if we don't already know */
+        if (!owner) {
+            this.owner = owner = document.body;
+            var node = document.id(this.domObj.parentNode);
+            while (node != document.body && owner == document.body) {
+                var flyout = node.retrieve('jxFlyout');
+                if (flyout) {
+                    this.owner = owner = flyout;
+                    break;
+                } else {
+                    node = document.id(node.parentNode);
+                }
+            }
+        }
+        if (stack[stack.length - 1] == this) {
+            this.hide();
+            return;
+        } else if (owner != document.body) {
+            /* if we are part of another flyout, close any open flyouts
+             * inside the parent and register this as the current flyout
+             */
+            if (owner.currentFlyout == this) {
+                /* if the flyout to close is this flyout,
+                 * hide this and return */
+                this.hide();
+                return;
+            } else if (owner.currentFlyout) {
+                owner.currentFlyout.hide();
+            }
+            owner.currentFlyout = this;
+        } else {
+            /* if we are at the top level, close the entire stack before
+             * we open
+             */
+            while (stack.length) {
+                stack[stack.length - 1].hide();
+            }
+        }
+        // now we go on the stack.
+        stack.push(this);
+        this.fireEvent('beforeOpen');
+
+        options.active = true;
+        this.domA.addClass(options.activeClass);
+        this.contentContainer.setStyle('visibility','hidden');
+        document.id(document.body).adopt(this.contentContainer);
+        this.content.getChildren().each(function(child) {
+            if (child.resize) {
+                child.resize();
+            }
+        });
+        this.showChrome(this.contentContainer);
+
+        var rel = options.positionElement || this.domObj;
+        var pos = $merge(options.position, {
+          offsets: this.chromeOffsets
+        });
+        this.position(this.contentContainer, rel, pos);
+
+        /* 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(document.id(this.content).getMarginBoxSize());
+
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','');
+
+        document.addEvent('keydown', this.keypressHandler);
+        document.addEvent('click', this.clickHandler);
+        this.fireEvent('open', this);
+    },
+
+    /**
+     * APIMethod: hide
+     * Closes the flyout if open
+     */
+    hide: function() {
+        if (this.owner != document.body) {
+            this.owner.currentFlyout = null;
+        }
+        Jx.Button.Flyout.Stack.pop();
+        this.setActive(false);
+        this.contentContainer.dispose();
+        this.unstack(this.contentContainer);
+        document.removeEvent('keydown', this.keypressHandler);
+        document.removeEvent('click', this.clickHandler);
+        this.fireEvent('close', this);
+    },
+    /**
+     * Method: clickHandler
+     * hide flyout if the user clicks outside of the flyout
+     */
+    clickHandler: function(e) {
+        e = new Event(e);
+        var elm = document.id(e.target),
+            flyout = Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1];
+        if (!elm.descendantOf(flyout.content) &&
+            !elm.descendantOf(flyout.domObj)) {
+            flyout.hide();
+        }
+    },
+    /**
+     * Method: keypressHandler
+     * hide flyout if the user presses the ESC key
+     */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
+        }
+    }
+});/*
+---
+
+name: Jx.ColorPalette
+
+description: A Jx.ColorPalette presents a user interface for selecting colors.  This is typically combined with a Jx.Button.Color which embeds the color palette in a flyout.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.ColorPalette]
+
+css:
+ - color
+
+images:
+ - grid.png
+
+...
+ */
+// $Id: colorpalette.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.ColorPalette
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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> suJx.Tooltipbclass is provided
+ * (<Jx.Button.Color>) that embeds a colour panel inside a button for easy use
+ * in toolbars.
+ *
+ * Colour changes are propogated via a change event.  To be notified
+ * of changes in a Jx.ColorPalette, use the addEvent method.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - triggered when the color changes.
+ * click - the user clicked on a color swatch (emitted after a change event)
+ *
+ * MooTools.lang keys:
+ * - colorpalette.alphaLabel
+ * 
+ * 
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ColorPalette = new Class({
+    Family: 'Jx.ColorPalette',
+    Extends: Jx.Widget,
+    /**
+     * Property: {HTMLElement} domObj
+     * the HTML element representing the color panel
+     */
+    domObj: null,
+    options: {
+        /* Option: parent
+         * default null, the DOM element to add the palette to.
+         */
+        parent: null,
+        /* Option: color
+         * default #000000, the initially selected color
+         */
+        color: '#000000',
+        /* Option: alpha
+         * default 100, the initial alpha value
+         */
+        alpha: 1,
+        /* Option: hexColors
+         * an array of hex colors for creating the palette, defaults to a
+         * set of web safe colors.
+         */
+        hexColors: ['00', '33', '66', '99', 'CC', 'FF']
+    },
+    /**
+     * Method: render
+     * initialize a new instance of Jx.ColorPalette
+     */
+    render: function() {
+        this.domObj = new Element('div', {
+            id: this.options.id,
+            'class':'jxColorPalette'
+        });
+
+        var top = new Element('div', {'class':'jxColorBar'});
+        var d = new Element('div', {'class':'jxColorPreview'});
+
+        this.selectedSwatch = new Element('div', {'class':'jxColorSelected'});
+        this.previewSwatch = new Element('div', {'class':'jxColorHover'});
+        d.adopt(this.selectedSwatch);
+        d.adopt(this.previewSwatch);
+
+        top.adopt(d);
+
+        this.colorInputLabel = new Element('label', {
+          'class':'jxColorLabel', 
+          html:'#'
+        });
+        top.adopt(this.colorInputLabel);
+
+        var cc = this.changed.bind(this);
+        this.colorInput = new Element('input', {
+            'class':'jxHexInput',
+            'type':'text',
+            'maxLength':6,
+            events: {
+                'keyup':cc,
+                'blur':cc,
+                'change':cc
+            }
+        });
+
+        top.adopt(this.colorInput);
+
+        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}) });
+        top.adopt(this.alphaLabel);
+
+        this.alphaInput = new Element('input', {
+            'class':'jxAlphaInput',
+            'type':'text',
+            'maxLength':3,
+            events: {
+                'keyup': this.alphaChanged.bind(this)
+            }
+        });
+        top.adopt(this.alphaInput);
+
+        this.domObj.adopt(top);
+
+        var swatchClick = this.swatchClick.bindWithEvent(this);
+        var swatchOver = this.swatchOver.bindWithEvent(this);
+
+        var table = new Element('table', {'class':'jxColorGrid'});
+        var tbody = new Element('tbody');
+        table.adopt(tbody);
+        for (var i=0; i<12; i++) {
+            var tr = new Element('tr');
+            for (var j=-3; j<18; j++) {
+                var bSkip = false;
+                var r, g, b;
+                /* hacky approach to building first three columns
+                 * because I couldn't find a good way to do it
+                 * programmatically
+                 */
+
+                if (j < 0) {
+                    if (j == -3 || j == -1) {
+                        r = g = b = 0;
+                        bSkip = true;
+                    } else {
+                        if (i<6) {
+                            r = g = b = i;
+                        } else {
+                            if (i == 6) {
+                                r = 5; g = 0; b = 0;
+                            } else if (i == 7) {
+                                r = 0; g = 5; b = 0;
+                            } else if (i == 8) {
+                                r = 0; g = 0; b = 5;
+                            } else if (i == 9) {
+                                r = 5; g = 5; b = 0;
+                            } else if (i == 10) {
+                                r = 0; g = 5; b = 5;
+                            } else if (i == 11) {
+                                r = 5; g = 0; b = 5;
+                            }
+                        }
+                    }
+                } else {
+                    /* remainder of the columns are built
+                     * based on the current row/column
+                     */
+                    r = parseInt(i/6,10)*3 + parseInt(j/6,10);
+                    g = j%6;
+                    b = i%6;
+                }
+                var bgColor = '#'+this.options.hexColors[r]+
+                                  this.options.hexColors[g]+
+                                  this.options.hexColors[b];
+
+                var td = new Element('td');
+                if (!bSkip) {
+                    td.setStyle('backgroundColor', bgColor);
+
+                    var a = new Element('a', {
+                        'class': 'colorSwatch ' + (((r > 2 && g > 2) || (r > 2 && b > 2) || (g > 2 && b > 2)) ? 'borderBlack': 'borderWhite'),
+                        'href':'javascript:void(0)',
+                        'title':bgColor,
+                        'alt':bgColor,
+                        events: {
+                            'mouseover': swatchOver,
+                            'click': swatchClick
+                        }
+                    });
+                    a.store('swatchColor', bgColor);
+                    td.adopt(a);
+                } else {
+                    var span = new Element('span', {'class':'emptyCell'});
+                    td.adopt(span);
+                }
+                tr.adopt(td);
+            }
+            tbody.adopt(tr);
+        }
+        this.domObj.adopt(table);
+        this.updateSelected();
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: swatchOver
+     * handle the mouse moving over a colour swatch by updating the preview
+     *
+     * Parameters:
+     * e - {Event} the mousemove event object
+     */
+    swatchOver: function(e) {
+        var a = e.target;
+
+        this.previewSwatch.setStyle('backgroundColor', a.retrieve('swatchColor'));
+    },
+
+    /**
+     * Method: swatchClick
+     * handle mouse click on a swatch by updating the color and hiding the
+     * panel.
+     *
+     * Parameters:
+     * e - {Event} the mouseclick event object
+     */
+    swatchClick: function(e) {
+        var a = e.target;
+
+        this.options.color = a.retrieve('swatchColor');
+        this.updateSelected();
+        this.fireEvent('click', this);
+    },
+
+    /**
+     * Method: changed
+     * handle the user entering a new colour value manually by updating the
+     * selected colour if the entered value is valid HEX.
+     */
+    changed: function() {
+        var color = this.colorInput.value;
+        if (color.substring(0,1) == '#') {
+            color = color.substring(1);
+        }
+        if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+            this.options.color = '#' +color.toUpperCase();
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * Method: alphaChanged
+     * handle the user entering a new alpha value manually by updating the
+     * selected alpha if the entered value is valid alpha (0-100).
+     */
+    alphaChanged: function() {
+        var alpha = this.alphaInput.value;
+        if (alpha.match(/^[0-9]{1,3}$/)) {
+            this.options.alpha = parseFloat(alpha/100);
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the colour represented by this colour panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function( color ) {
+        this.colorInput.value = color;
+        this.changed();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this colour panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function( alpha ) {
+        this.alphaInput.value = alpha;
+        this.alphaChanged();
+    },
+
+    /**
+     * Method: updateSelected
+     * update the colour panel user interface based on the current
+     * colour and alpha values
+     */
+    updateSelected: function() {
+        var styles = {'backgroundColor':this.options.color};
+
+        this.colorInput.value = this.options.color.substring(1);
+
+        this.alphaInput.value = parseInt(this.options.alpha*100,10);
+        if (this.options.alpha < 1) {
+            styles.opacity = this.options.alpha;
+            styles.filter = 'Alpha(opacity='+(this.options.alpha*100)+')';
+            
+        } else {
+            styles.opacity = 1;
+            //not sure what the proper way to remove the filter would be since
+            // I don't have IE to test against.
+            styles.filter = '';  
+        }
+        this.selectedSwatch.setStyles(styles);
+        this.previewSwatch.setStyles(styles);
+        
+        this.fireEvent('change', this);
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the
+     * widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	
+    	if ($defined(this.alphaLabel)) {
+    		this.alphaLabel.set('html', this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}));
+    	}
+    }
+});
+
+/*
+---
+
+name: Jx.Button.Color
+
+description:
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button.Flyout
+ - Jx.ColorPalette
+
+provides: [Jx.Button.Color]
+
+...
+ */
+// $Id: color.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Color
+ *
+ * Extends: <Jx.Button.Flyout>
+ *
+ * A <Jx.ColorPalette> wrapped up in a Jx.Button.  The button includes a
+ * preview of the currently selected color.  Clicking the button opens
+ * the color panel.
+ *
+ * A color button is essentially a <Jx.Button.Flyout> where the content
+ * of the flyout is a <Jx.ColorPalette>.  For performance, all color buttons
+ * share an instance of <Jx.ColorPalette> which means only one button can be
+ * open at a time.  This isn't a huge restriction as flyouts already close
+ * each other when opened.
+ *
+ * Example:
+ * (code)
+ * var colorButton = new Jx.Button.Color({
+ *     onChange: function(button) {
+ *         console.log('color:' + button.options.color + ' alpha: ' +
+ *                     button.options.alpha);
+ *     }
+ * });
+ * (end)
+ *
+ * Events:
+ * change - fired when the color is changed.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Color = new Class({
+    Family: 'Jx.Button.Color',
+    Extends: Jx.Button.Flyout,
+
+    /**
+     * Property: swatch
+     * the color swatch element used to portray the currently selected
+     * color
+     */
+    swatch: null,
+
+    options: {
+        /**
+         * Option: color
+         * a color to initialize the panel with, defaults to #000000
+         * (black) if not specified.
+         */
+        color: '#000000',
+        /**
+         * Option: alpha
+         * an alpha value to initialize the panel with, defaults to 1
+         *  (opaque) if not specified.
+         *
+         */
+        alpha: 100,
+        /*
+         * Option: template
+         * the HTML template for the color button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        swatch: 'jxButtonSwatchColor',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * creates a new color button.
+     */
+    render: function() {
+        if (!Jx.Button.Color.ColorPalette) {
+            Jx.Button.Color.ColorPalette = new Jx.ColorPalette(this.options);
+        }
+
+        /* we need to have an image to replace, but if a label is
+           requested, there wouldn't normally be an image. */
+        this.options.image = Jx.aPixel.src;
+
+        /* now we can safely initialize */
+        this.parent();
+        this.updateSwatch();
+
+        this.bound.changed = this.changed.bind(this);
+        this.bound.hide = this.hide.bind(this);
+    },
+    cleanup: function() {
+      this.bound.changed = false;
+      this.bound.hide = false;
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * override <Jx.Button.Flyout> to use a singleton color palette.
+     */
+    clicked: function() {
+        var cp = Jx.Button.Color.ColorPalette;
+        if (cp.currentButton) {
+            cp.currentButton.hide();
+        }
+        cp.currentButton = this;
+        cp.addEvent('change', this.bound.changed);
+        cp.addEvent('click', this.bound.hide);
+        this.content.appendChild(cp.domObj);
+        cp.domObj.setStyle('display', 'block');
+        Jx.Button.Flyout.prototype.clicked.apply(this, arguments);
+        /* setting these before causes an update problem when clicking on
+         * a second color button when another one is open - the color
+         * wasn't updating properly
+         */
+
+        cp.options.color = this.options.color;
+        cp.options.alpha = this.options.alpha/100;
+        cp.updateSelected();
+},
+
+    /**
+     * APIMethod: hide
+     * hide the color panel
+     */
+    hide: function() {
+        var cp = Jx.Button.Color.ColorPalette;
+        this.setActive(false);
+        cp.removeEvent('change', this.bound.changed);
+        cp.removeEvent('click', this.bound.hide);
+        Jx.Button.Flyout.prototype.hide.apply(this, arguments);
+        cp.currentButton = null;
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the color represented by this color panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function(color) {
+        this.options.color = color;
+        this.updateSwatch();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this color panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function(alpha) {
+        this.options.alpha = alpha;
+        this.updateSwatch();
+    },
+
+    /**
+     * Method: changed
+     * handle the color changing in the palette by updating the preview swatch
+     * in the button and firing the change event.
+     *
+     * Parameters:
+     * panel - <Jx.ColorPalette> the palette that changed.
+     */
+    changed: function(panel) {
+        var changed = false;
+        if (this.options.color != panel.options.color) {
+            this.options.color = panel.options.color;
+            changed = true;
+        }
+        if (this.options.alpha != panel.options.alpha * 100) {
+            this.options.alpha = panel.options.alpha * 100;
+            changed = true;
+        }
+        if (changed) {
+            this.updateSwatch();
+            this.fireEvent('change',this);
+        }
+    },
+
+    /**
+     * Method: updateSwatch
+     * Update the swatch color for the current color
+     */
+    updateSwatch: function() {
+        var styles = {'backgroundColor':this.options.color};
+        if (this.options.alpha < 100) {
+            styles.filter = 'Alpha(opacity='+(this.options.alpha)+')';
+            styles.opacity = this.options.alpha / 100;
+
+        } else {
+            styles.opacity = 1;
+            styles.filter = '';
+        }
+        this.swatch.setStyles(styles);
+    }
+});
+/*
+---
+
+name: Jx.Menu
+
+description: A main menu as opposed to a sub menu that lives inside the menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.List
+
+provides: [Jx.Menu]
+
+css:
+ - menu
+
+images:
+ - flyout_chrome.png
+ - emblems.png
+...
+ */
+// $Id: menu.js 966 2010-07-20 13:31:16Z pagameba $
+/**
+ * Class: Jx.Menu
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A main menu as opposed to a sub menu that lives inside the menu.
+ *
+ * TODO: Jx.Menu
+ * revisit this to see if Jx.Menu and Jx.SubMenu can be merged into
+ * a single implementation.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu = new Class({
+    Family: 'Jx.Menu',
+    Extends: Jx.Widget,
+    // Binds: ['onMouseEnter','onMouseLeave','hide','keypressHandler'],
+    /**
+     * Property: button
+     * {<Jx.Button>} The button that represents this menu in a toolbar and
+     * opens the menu.
+     */
+    button : null,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML element that contains the menu items
+     * within the menu.
+     */
+    subDomObj : null,
+    /**
+     * Property: list
+     * {<Jx.List>} the list of items in the menu
+     */
+    list: null,
+
+    parameters: ['buttonOptions', 'options'],
+
+    options: {
+        /**
+         * Option: exposeOnHover
+         * {Boolean} default false, if set to true the menu will show
+         * when the mouse hovers over it rather than when it is clicked.
+         */
+        exposeOnHover: false,
+        /**
+         * Option: hideDelay
+         * {Integer} default 0, if greater than 0, this is the number of
+         * milliseconds to delay before hiding a menu when the mouse leaves
+         * the menu button or list.
+         */
+        hideDelay: 0,
+        template: "<div class='jxMenuContainer'><ul class='jxMenu'></ul></div>",
+        buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><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: new Hash({
+        contentContainer: 'jxMenuContainer',
+        subDomObj: 'jxMenu'
+    }),
+    
+    init: function() {
+        this.bound.stop = function(e){e.stop();};
+        this.bound.remove = function(item) {item.setOwner(null);};
+        this.bound.show = this.show.bind(this);
+        this.bound.mouseenter = this.onMouseEnter.bind(this);
+        this.bound.mouseleave = this.onMouseLeave.bind(this);
+        this.bound.keypress = this.keypressHandler.bind(this);
+        this.bound.hide = this.hide.bind(this);
+        this.parent();
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.
+     */
+    render : function() {
+        this.parent();
+        if (!Jx.Menu.Menus) {
+            Jx.Menu.Menus = [];
+        }
+
+        this.contentClone = this.contentContainer.clone();
+        this.list = new Jx.List(this.subDomObj, {
+            onRemove: this.bound.remove
+        });
+
+        /* if options are passed, make a button inside an LI so the
+           menu can be embedded inside a toolbar */
+        if (this.options.buttonOptions) {
+            this.button = new Jx.Button($merge(this.options.buttonOptions,{
+                template: this.options.buttonTemplate,
+                onClick:this.bound.show
+            }));
+
+            this.button.domA.addEvent('mouseenter', this.bound.mouseenter);
+            this.button.domA.addEvent('mouseleave', this.bound.mouseleave);
+
+            this.domObj = this.button.domObj;
+            this.domObj.store('jxMenu', this);
+        }
+        
+        this.subDomObj.addEvent('mouseenter', this.bound.mouseenter);
+        this.subDomObj.addEvent('mouseleave', this.bound.mouseleave);
+
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    cleanup: function() {
+      if (this.hideTimer) {
+        window.clearTimeout(this.hideTimer);
+      }
+      this.list.removeEvent('remove', this.bound.remove);
+      this.list.destroy();
+      this.list = null;
+      if (this.button) {
+        this.domObj.eliminate('jxMenu');
+        this.domObj = null;
+        this.button.removeEvent('click', this.bound.show);
+        this.button.domA.removeEvents({
+          mouseenter: this.bound.mouseenter,
+          mouseleave: this.bound.mouseleave
+        });
+        
+        this.button.destroy();
+        this.button = null;
+      }
+      this.subDomObj.removeEvents({
+        mouseenter: this.bound.mouseenter,
+        mouseleave: this.bound.mouseleave
+      });
+      this.subDomObj.removeEvents();
+      this.contentContainer.removeEvent('contextmenu', this.bound.stop);
+      this.subDomObj.destroy();
+      this.contentContainer.destroy();
+      this.contentClone.destroy();
+      this.bound.remove = null;
+      this.bound.show = null;
+      this.bound.stop = null;
+      this.bound.mouseenter = null;
+      this.bound.mouseleave = null;
+      this.bound.keypress = null;
+      this.bound.hide = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     *     can be added by passing an array of menu items.
+     * position - the index to add the item at, defaults to the end of the
+     *     menu
+     */
+    add: function(item, position, owner) {
+        if (Jx.type(item) == 'array') {
+            item.each(function(i){
+                i.setOwner(owner||this);
+            }, this);
+        } else {
+            item.setOwner(owner||this);
+        }
+        this.list.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: empty
+     * Empty the menu of items
+     */
+    empty: function() {
+      this.list.each(function(item){
+        if (item.empty) {
+          item.empty();
+        }
+        item.setOwner(null);
+      }, this);
+      this.list.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the menu by hiding it.
+     */
+    deactivate: function() {this.hide();},
+    /**
+     * Method: onMouseOver
+     * Handle the user moving the mouse over the button for this menu
+     * by showing this menu and hiding the other menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseEnter: function(e) {
+      if (this.hideTimer) {
+        window.clearTimeout(this.hideTimer);
+        this.hideTimer = null;
+      }
+      if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] != this) {
+          this.show.delay(1,this);
+      } else if (this.options.exposeOnHover) {
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+          Jx.Menu.Menus[0] = null;
+        }
+        this.show.delay(1,this);
+      }
+    },
+    /**
+     * Method: onMouseLeave
+     * Handle the user moving the mouse off this button or menu by
+     * starting the hide process if so configured.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseLeave: function(e) {
+      if (this.options.hideDelay > 0) {
+        this.hideTimer = (function(){
+          this.deactivate();
+        }).delay(this.options.hideDelay, this);
+      }
+    },
+    
+    /**
+     * Method: eventInMenu
+     * determine if an event happened inside this menu or a sub menu
+     * of this menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     *
+     * Returns:
+     * {Boolean} true if the event happened in the menu or
+     * a sub menu of this menu, false otherwise
+     */
+    eventInMenu: function(e) {
+        var target = document.id(e.target);
+        if (!target) {
+            return false;
+        }
+        if (target.descendantOf(this.domObj) ||
+            target.descendantOf(this.subDomObj)) {
+            return true;
+        } else {
+            var ul = target.findElement('ul');
+            if (ul) {
+                var sm = ul.retrieve('jxSubMenu');
+                if (sm) {
+                    var owner = sm.owner;
+                    while (owner) {
+                        if (owner == this) {
+                            return true;
+                        }
+                        owner = owner.owner;
+                    }
+                }
+            }
+            return false;
+        }
+    },
+
+    /**
+     * APIMethod: hide
+     * Hide the menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    hide: function(e) {
+        if (e) {
+            if (this.visibleItem && this.visibleItem.eventInMenu) {
+                if (this.visibleItem.eventInMenu(e)) {
+                    return;
+                }
+            } else if (this.eventInMenu(e)) {
+                return;
+            }
+        }
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+            Jx.Menu.Menus[0] = null;
+        }
+        if (this.button && this.button.domA) {
+            this.button.domA.removeClass(this.button.options.activeClass);
+        }
+        if (this.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+        this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
+        document.removeEvent('mousedown', this.bound.hide);
+        document.removeEvent('keydown', this.bound.keypress);
+        this.unstack(this.contentContainer);
+        this.contentContainer.dispose();
+        this.visibleItem = null;
+        this.fireEvent('hide', this);
+    },
+    /**
+     * APIMethod: show
+     * Show the menu
+     */
+    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.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+
+        this.subDomObj.dispose();
+        this.contentContainer.destroy();
+        this.contentContainer = this.contentClone.clone();
+        this.contentContainer.empty().adopt(this.subDomObj);
+        this.contentContainer.addEvent('contextmenu', this.bound.stop);
+        this.contentContainer.setStyle('display','none');
+        document.id(document.body).adopt(this.contentContainer);
+        this.contentContainer.setStyles({
+            visibility: 'hidden',
+            display: 'block'
+        });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+        this.showChrome(this.contentContainer);
+
+        this.position(this.contentContainer, this.domObj, $merge({
+            offsets: this.chromeOffsets
+        }, this.options.position));
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','visible');
+
+        if (this.button && this.button.domA) {
+            this.button.domA.addClass(this.button.options.activeClass);
+        }
+
+        /* fix bug in IE that closes the menu as it opens 
+         * because of bubbling (I think)
+         */
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keydown', this.bound.keypress);
+        this.fireEvent('show', this);
+    },
+    /**
+     * APIMethod: setVisibleItem
+     * Set the sub menu that is currently open
+     *
+     * Parameters:
+     * obj- {<Jx.SubMenu>} the sub menu that just became visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    },
+
+    /* hide flyout if the user presses the ESC key */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            this.hide();
+        }
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the menu is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the menu is enabled or not
+     */
+    isEnabled: function() {
+        return this.button ? this.button.isEnabled() : this.options.enabled ;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the menu.
+     *
+     * Parameters:
+     * enabled - {Boolean} the new enabled state of the menu
+     */
+    setEnabled: function(enabled) {
+        return this.button ? this.button.setEnabled(enabled) : this.options.enable;
+    },
+    /**
+     * APIMethod: isActive
+     * returns true if the menu is open.
+     *
+     * Returns:
+     * {Boolean} the active state of the menu
+     */
+    isActive: function() {
+        return this.button ? this.button.isActive() : this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the menu
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the menu
+     */
+    setActive: function(active) {
+        if (this.button) {
+          this.button.setActive(active);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this menu to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this menu
+     */
+    setImage: function(path) {
+        if (this.button) {
+          this.button.setImage(path);
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     *
+     * sets the text of the menu.
+     *
+     * Parameters:
+     *
+     * label - {String} the new label for the menu
+     */
+    setLabel: function(label) {
+        if (this.button) {
+          this.button.setLabel(label);
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     *
+     * returns the text of the menu.
+     */
+    getLabel: function() {
+        return this.button ? this.button.getLabel() : '';
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the menu
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.button) {
+          this.button.setTooltip(tooltip);
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this menu
+     */
+    focus: function() {
+        if (this.button) {
+          this.button.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this menu
+     */
+    blur: function() {
+        if (this.button) {
+          this.button.blur();
+        }
+    }
+});
+
+/*
+---
+
+name: Jx.Menu.Item
+
+description: A menu item is a single entry in a menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Item]
+
+images:
+ - menuitem.png
+...
+ */
+// $Id: menu.item.js 967 2010-07-20 13:31:44Z pagameba $
+/**
+ * Class: Jx.Menu.Item
+ *
+ * Extends: <Jx.Button>
+ *
+ * A menu item is a single entry in a menu.  It is typically composed of
+ * a label and an optional icon.  Selecting the menu item emits an event.
+ *
+ * Jx.Menu.Item is represented by a <Jx.Button> with type MenuItem and the
+ * associated CSS changes noted in <Jx.Button>.  The container of a MenuItem
+ * is an 'li' element.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - fired when the menu item is clicked.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Item = new Class({
+    Family: 'Jx.Menu.Item',
+    Extends: Jx.Button,
+    // Binds: ['onMouseOver'],
+    /**
+     * Property: owner
+     * {<Jx.SubMenu> or <Jx.Menu>} the menu that contains the menu item.
+     */
+    owner: null,
+    options: {
+        //image: null,
+        label: '&nbsp;',
+        toggleClass: 'jxMenuItemToggle',
+        pressedClass: 'jxMenuItemPressed',
+        activeClass: 'jxMenuItemActive',
+        /* 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: new Hash({
+        domObj:'jxMenuItemContainer',
+        domA: 'jxMenuItem',
+        domImg: 'jxMenuItemIcon',
+        domLabel: 'jxMenuItemLabel'
+    }),
+    init: function() {
+      this.bound.mouseover = this.onMouseOver.bind(this);
+      this.parent();
+    },
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.Item
+     */
+    render: function() {
+        if (!this.options.image) {
+            this.options.image = Jx.aPixel.src;
+        }
+        this.parent();
+        if (this.options.image && this.options.image != Jx.aPixel.src) {
+            this.domObj.removeClass(this.options.toggleClass);
+        }
+        if (this.options.target) {
+          this.domA.set('target', this.options.target);
+        }
+        this.domObj.addEvent('mouseover', this.bound.mouseover);
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.domObj.removeEvent('mouseover', this.bound.mouseover);
+      this.bound.mouseover = null;
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: function() {this.blur.delay(1,this);},
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty,
+    /**
+     * Method: clicked
+     * Handle the user clicking on the menu item, overriding the <Jx.Button::clicked>
+     * method to facilitate menu tracking
+     *
+     * Parameters:
+     * obj - {Object} an object containing an event property that was the user
+     * event.
+     */
+    clicked: function(obj) {
+        var href = this.options.href && this.options.href.indexOf('javascript:') != 0;
+        if (this.options.enabled) {
+          if (!href) {
+            if (this.options.toggle) {
+                this.setActive.delay(1,this,!this.options.active);
+            }
+            this.fireEvent.delay(1, this, ['click', {obj: this}]);
+            this.blur();
+          }
+          if (this.owner && this.owner.deactivate) {
+              this.owner.deactivate.delay(1, this.owner, obj.event);
+          }
+        }
+        return href ? true : false;
+    },
+    /**
+     * Method: onmouseover
+     * handle the mouse moving over the menu item
+     */
+    onMouseOver: function(e) {
+        e.stop();
+        if (this.owner && this.owner.setVisibleItem) {
+            this.owner.setVisibleItem(this);
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the menu item on langChange Event for
+     * Internationalization
+     */
+    changeText: function(lang) {
+        this.parent();
+        if (this.owner && this.owner.deactivate) {
+            this.owner.deactivate();
+        }
+    }
+});
+
+/*
+---
+
+name: Jx.ButtonSet
+
+description: A ButtonSet manages a set of Jx.Button instances by ensuring that only one of the buttons is active.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.ButtonSet]
+
+
+...
+ */
+// $Id: set.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.ButtonSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only
+ * one of the buttons is active.  All the buttons need to have been created
+ * with the toggle option set to true for this to work.
+ *
+ * Example:
+ * (code)
+ * var toolbar = new Jx.Toolbar('bar');
+ * var buttonSet = new Jx.ButtonSet();
+ *
+ * var b1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
+ * var b2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
+ * var b3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
+ * var b4 = new Jx.Button({label: 'b4', toggle:true, contentID: 'content4'});
+ *
+ * buttonSet.add(b1,b2,b3,b4);
+ * (end)
+ *
+ * Events:
+ * change - the current button has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ButtonSet = new Class({
+    Family: 'Jx.ButtonSet',
+    Extends: Jx.Object,
+    Binds: ['buttonChanged'],
+    /**
+     * Property: buttons
+     * {Array} array of buttons that are managed by this button set
+     */
+    buttons: [],
+    
+    cleanup: function() {
+      this.buttons.each(function(b){
+        b.removeEvent('down', this.buttonChanged);
+        b.setActive = null;
+      },this);
+      this.activeButton = null;
+      this.buttons = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: add
+     * Add one or more <Jx.Button>s to the ButtonSet.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button
+     * set.  More than one button can be added by passing extra parameters to
+     * this method.
+     */
+    add : function() {
+        $A(arguments).each(function(button) {
+            if (button.domObj.hasClass(button.options.toggleClass)) {
+                button.domObj.removeClass(button.options.toggleClass);
+                button.domObj.addClass(button.options.toggleClass+'Set');
+            }
+            button.addEvent('down',this.buttonChanged);
+            button.setActive = function(active) {
+                if (button.options.active && this.activeButton == button) {
+                    return;
+                } else {
+                    Jx.Button.prototype.setActive.apply(button, [active]);
+                }
+            }.bind(this);
+            if (!this.activeButton || button.options.active) {
+                button.options.active = false;
+                button.setActive(true);
+            }
+            this.buttons.push(button);
+        }, this);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * Remove a button from this Button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove.
+     */
+    remove : function(button) {
+        this.buttons.erase(button);
+        if (this.activeButton == button) {
+            if (this.buttons.length) {
+                this.buttons[0].setActive(true);
+            }
+            button.removeEvent('down',this.buttonChanged);
+            button.setActive = Jx.Button.prototype.setActive;
+        }
+    },
+    /**
+     * APIMethod: empty
+     * empty the button set and clear the active button
+     */
+    empty: function() {
+      this.buttons = [];
+      this.activeButton = null;
+    },
+    /**
+     * APIMethod: setActiveButton
+     * Set the active button to the one passed to this method
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    setActiveButton: function(button) {
+        var b = this.activeButton;
+        this.activeButton = button;
+        if (b && b != button) {
+            b.setActive(false);
+        }
+    },
+    /**
+     * Method: buttonChanged
+     * Handle selection changing on the buttons themselves and activate the
+     * appropriate button in response.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    buttonChanged: function(button) {
+        this.setActiveButton(button);
+        this.fireEvent('change', this);
+    }
+});/*
+---
+
+name: Jx.Button.Multi
+
+description: 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.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.Menu
+ - Jx.ButtonSet
+
+provides: [Jx.Button.Multi]
+
+images:
+ - button_multi.png
+ - button_multi_disclose.png
+
+...
+ */
+// $Id: multi.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Multi
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * that button to replace the active button in the toolbar and performs
+ * the button's regular action.
+ *
+ * Other buttons can be added to the Multi button using the add method.
+ *
+ * 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
+ * added buttons are placed.  The main button content is cloned from the
+ * last button clicked (or first button added).
+ *
+ * The Multi button does not trigger any events itself, only the contained
+ * buttons trigger events.
+ *
+ * Example:
+ * (code)
+ * var b1 = new Jx.Button({
+ *     label: 'b1',
+ *     onClick: function(button) {
+ *         console.log('b1 clicked');
+ *     }
+ * });
+ * var b2 = new Jx.Button({
+ *     label: 'b2',
+ *     onClick: function(button) {
+ *         console.log('b2 clicked');
+ *     }
+ * });
+ * var b3 = new Jx.Button({
+ *     label: 'b3',
+ *     onClick: function(button) {
+ *         console.log('b3 clicked');
+ *     }
+ * });
+ * var multiButton = new Jx.Button.Multi();
+ * multiButton.add(b1, b2, b3);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Multi = new Class({
+    Family: 'Jx.Button.Multi',
+    Extends: Jx.Button,
+
+    /**
+     * Property: {<Jx.Button>} activeButton
+     * the currently selected button
+     */
+    activeButton: null,
+
+    /**
+     * Property: buttons
+     * {Array} the buttons added to this multi button
+     */
+    buttons: null,
+
+    options: {
+        /* Option: template
+         * the button template for a multi button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>',
+        menuOptions: {}
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel',
+        domDisclose: 'jxButtonDisclose'
+    }),
+
+    /**
+     * Method: render
+     * construct a new instance of Jx.Button.Multi.
+     */
+    render: function() {
+        this.parent();
+        this.buttons = [];
+
+        this.menu = new Jx.Menu({}, this.options.menuOptions);
+        this.menu.button = this;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.bound.click = this.clicked.bind(this);
+
+        if (this.domDisclose) {
+            var button = this;
+            var hasFocus;
+
+            this.bound.disclose = {
+              click: function(e) {
+                  if (this.list.count() === 0) {
+                      return;
+                  }
+                  if (!button.options.enabled) {
+                      return;
+                  }
+                  this.contentContainer.setStyle('visibility','hidden');
+                  this.contentContainer.setStyle('display','block');
+                  document.id(document.body).adopt(this.contentContainer);
+                  /* we have to size the container for IE to render the chrome
+                   * correctly but just in the menu/sub menu case - there is
+                   * some horrible peekaboo bug in IE related to ULs that we
+                   * just couldn't figure out
+                   */
+                  this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+                  this.showChrome(this.contentContainer);
+
+                  this.position(this.contentContainer, this.button.domObj, {
+                      horizontal: ['right right'],
+                      vertical: ['bottom top', 'top bottom'],
+                      offsets: this.chromeOffsets
+                  });
+
+                  this.contentContainer.setStyle('visibility','');
+
+                  document.addEvent('mousedown', this.bound.hide);
+                  document.addEvent('keyup', this.bound.keypress);
+
+                  this.fireEvent('show', this);
+              }.bindWithEvent(this.menu),
+              mouseenter:function(){
+                  document.id(this.domObj.firstChild).addClass('jxButtonHover');
+                  if (hasFocus) {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bind(this),
+              mouseleave:function(){
+                  document.id(this.domObj.firstChild).removeClass('jxButtonHover');
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bind(this),
+              mousedown: function(e) {
+                  this.domDisclose.addClass(this.options.pressedClass);
+                  hasFocus = true;
+                  this.focus();
+              }.bindWithEvent(this),
+              mouseup: function(e) {
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bindWithEvent(this),
+              keydown: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              keyup: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.removeClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              blur: function() { hasFocus = false; }
+            };
+
+            this.domDisclose.addEvents({
+              click: this.bound.disclose.click,
+              mouseenter: this.bound.disclose.mouseenter,
+              mouseleave: this.bound.disclose.mouseleave,
+              mousedown: this.bound.disclose.mousedown,
+              mouseup: this.bound.disclose.mouseup,
+              keydown: this.bound.disclose.keydown,
+              keyup: this.bound.disclose.keyup,
+              blur: this.bound.disclose.blur
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domDisclose, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+        this.bound.show = function() {
+            this.domA.addClass(this.options.activeClass);
+        }.bind(this);
+        this.bound.hide = function() {
+            if (this.options.active) {
+                this.domA.addClass(this.options.activeClass);
+            }
+        }.bind(this);
+
+        this.menu.addEvents({
+            'show': this.bound.show,
+            'hide': this.bound.hide
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+    },
+    cleanup: function() {
+      var self = this,
+          bound = this.bound;
+      // clean up the discloser
+      if (self.domDisclose) {
+        self.domDisclose.removeEvents({
+          click: bound.disclose.click,
+          mouseenter: bound.disclose.mouseenter,
+          mouseleave: bound.disclose.mouseleave,
+          mousedown: bound.disclose.mousedown,
+          mouseup: bound.disclose.mouseup,
+          keydown: bound.disclose.keydown,
+          keyup: bound.disclose.keyup,
+          blur: bound.disclose.blur
+        });
+      }
+
+      // clean up the button set
+      self.buttonSet.destroy();
+      self.buttonSet = null;
+
+      // clean up the buttons array
+      self.buttons.each(function(b){
+        b.removeEvents();
+        self.menu.remove(b.multiButton);
+        b.multiButton.destroy();
+        b.multiButton = null;
+        b.destroy();
+      });
+      self.buttons.empty();
+      self.buttons = null;
+
+      // clean up the menu object
+      self.menu.removeEvents({
+        'show': bound.show,
+        'hide': bound.hide
+      });
+      // unset the menu button because it references this object
+      self.menu.button = null;
+      self.menu.destroy();
+      self.menu = null;
+
+      // clean up binds and call parent to finish
+      self.bound.show = null;
+      self.bound.hide = null;
+      self.bound.clicked = null;
+      self.bound.disclose = null;
+      self.activeButton = null;
+      self.parent();
+    },
+    /**
+     * APIMethod: add
+     * adds one or more buttons to the Multi button.  The first button
+     * added becomes the active button initialize.  This function
+     * takes a variable number of arguments, each of which is expected
+     * to be an instance of <Jx.Button>.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance, may be repeated in the parameter list
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(theButton){
+          var f,
+              opts,
+              button;
+            if (!theButton instanceof Jx.Button) {
+                return;
+            }
+            theButton.domA.addClass('jxDiscloser');
+            theButton.setLabel(theButton.options.label);
+            this.buttons.push(theButton);
+            f = this.setButton.bind(this, theButton);
+            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;
+            }
+            button = new Jx.Menu.Item(opts);
+            this.buttonSet.add(button);
+            this.menu.add(button);
+            theButton.multiButton = button;
+            theButton.domA.addClass('jxButtonMulti');
+            if (!this.activeButton) {
+                this.domA.dispose();
+                this.setActiveButton(theButton);
+            }
+        }, this);
+    },
+    /**
+     * APIMethod: remove
+     * remove a button from a multi button
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove
+     */
+    remove: function(button) {
+        if (!button || !button.multiButton) {
+            return;
+        }
+        // the toolbar will only remove the li.toolItem, which is
+        // the parent node of the multiButton's domObj.
+        if (this.menu.remove(button.multiButton)) {
+            button.multiButton = null;
+            if (this.activeButton == button) {
+                // if any buttons are left that are not this button
+                // then set the first one to be the active button
+                // otherwise set the active button to nothing
+                if (!this.buttons.some(function(b) {
+                    if (b != button) {
+                        this.setActiveButton(b);
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }, this)) {
+                    this.setActiveButton(null);
+                }
+            }
+            this.buttons.erase(button);
+        }
+    },
+    /**
+     * APIMethod: empty
+     * remove all buttons from the multi button
+     */
+    empty: function() {
+      this.buttons.each(function(b){this.remove(b);}, this);
+    },
+    /**
+     * APIMethod: setActiveButton
+     * update the menu item to be the requested button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance that was added to this multi button.
+     */
+    setActiveButton: function(button) {
+        if (this.activeButton) {
+            this.activeButton.domA.dispose();
+            this.activeButton.domA.removeEvent('click', this.bound.click);
+        }
+        if (button && button.domA) {
+            this.domObj.grab(button.domA, 'top');
+            this.domA = button.domA;
+            this.domA.addEvent('click', this.bound.click);
+            if (this.options.toggle) {
+                this.options.active = false;
+                this.setActive(true);
+            }
+        }
+        this.activeButton = button;
+    },
+    /**
+     * Method: setButton
+     * update the active button in the menu item, trigger the button's action
+     * and hide the flyout that contains the buttons.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} The button to set as the active button
+     */
+    setButton: function(button) {
+        this.setActiveButton(button);
+        button.clicked();
+    }
+});/*
+---
+
+name: Jx.Layout
+
+description: Jx.Layout is used to provide more flexible layout options for applications
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+
+provides: [Jx.Layout]
+
+css:
+ - layout
+
+...
+ */
+// $Id: layout.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Layout
+ *
+ * Extends: <Jx.Object>
+ *
+ * Jx.Layout is used to provide more flexible layout options for applications
+ *
+ * Jx.Layout wraps an existing DOM element (typically a div) and provides
+ * extra functionality for sizing that element within its parent and sizing
+ * elements contained within it that have a 'resize' function attached to them.
+ *
+ * To create a Jx.Layout, pass the element or id plus an options object to
+ * the constructor.
+ *
+ * Example:
+ * (code)
+ * var myContainer = new Jx.Layout('myDiv', options);
+ * (end)
+ *
+ * Events:
+ * sizeChange - fired when the size of the container changes
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Layout = new Class({
+    Family: 'Jx.Layout',
+    Extends: Jx.Object,
+
+    options: {
+        /* Option: resizeWithWindow
+         * boolean, automatically resize this layout when the window size
+         * changes, even if the element is not a direct descendant of the
+         * BODY.  False by default.
+         */
+        resizeWithWindow: false,
+        /* Option: propagate
+         * boolean, controls propogation of resize to child nodes.
+         * True by default. If set to false, changes in size will not be
+         * propogated to child nodes.
+         */
+        propagate: true,
+        /* Option: position
+         * how to position the element, either 'absolute' or 'relative'.
+         * The default (if not passed) is 'absolute'.  When using
+         * 'absolute' positioning, both the width and height are
+         * controlled by Jx.Layout.  If 'relative' positioning is used
+         * then only the width is controlled, allowing the height to
+         * be controlled by its content.
+         */
+        position: 'absolute',
+        /* Option: left
+         * the distance (in pixels) to maintain the left edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the left edge can be any distance from its parent
+         * based on other parameters.
+         */
+        left: 0,
+        /* Option: right
+         * the distance (in pixels) to maintain the right edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the right edge can be any distance from its parent
+         * based on other parameters.
+         */
+        right: 0,
+        /* Option: top
+         * the distance (in pixels) to maintain the top edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the top edge can be any distance from its parent
+         * based on other parameters.
+         */
+        top: 0,
+        /* Option: bottom
+         * the distance (in pixels) to maintain the bottom edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the bottom edge can be any distance from its parent
+         * based on other parameters.
+         */
+        bottom: 0,
+        /* Option: width
+         * the width (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the width can be any value based on
+         * other parameters.
+         */
+        width: null,
+        /* Option: height
+         * the height (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the height can be any value based on
+         * other parameters.
+         */
+        height: null,
+        /* Option: minWidth
+         * the minimum width that the element can be sized to.  The default
+         * value is 0.
+         */
+        minWidth: 0,
+        /* Option: minHeight
+         * the minimum height that the element can be sized to.  The
+         * default value is 0.
+         */
+        minHeight: 0,
+        /* Option: maxWidth
+         * the maximum width that the element can be sized to.  The default
+         * value is -1, which means no maximum.
+         */
+        maxWidth: -1,
+        /* Option: maxHeight
+         * the maximum height that the element can be sized to.  The
+         * default value is -1, which means no maximum.
+         */
+        maxHeight: -1
+    },
+
+    /**
+     * Parameters:
+     * domObj - {HTMLElement} element or id to apply the layout to
+     * options - <Jx.Layout.Options>
+     */
+    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);
+
+        if (this.options.resizeWithWindow || document.body == this.domObj.parentNode) {
+            window.addEvent('resize', this.windowResize.bindWithEvent(this));
+            window.addEvent('load', this.windowResize.bind(this));
+        }
+        //this.resize();
+    },
+
+    /**
+     * Method: windowResize
+     * when the window is resized, any Jx.Layout controlled elements that are
+     * direct children of the BODY element are resized
+     */
+     windowResize: function() {
+         this.resize();
+         if (this.resizeTimer) {
+             $clear(this.resizeTimer);
+             this.resizeTimer = null;
+         }
+         this.resizeTimer = this.resize.delay(50, this);
+    },
+
+    /**
+     * Method: resize
+     * resize the element controlled by this Jx.Layout object.
+     *
+     * Parameters:
+     * options - new options to apply, see <Jx.Layout.Options>
+     */
+    resize: function(options) {
+         /* this looks like a really big function but actually not
+          * much code gets executed in the two big if statements
+          */
+        this.resizeTimer = null;
+        var needsResize = false;
+        if (options) {
+            for (var i in options) {
+                //prevent forceResize: false from causing a resize
+                if (i == 'forceResize') {
+                    continue;
+                }
+                if (this.options[i] != options[i]) {
+                    needsResize = true;
+                    this.options[i] = options[i];
+                }
+            }
+            if (options.forceResize) {
+                needsResize = true;
+            }
+        }
+        if (!document.id(this.domObj.parentNode)) {
+            return;
+        }
+
+        var parentSize;
+        if (this.domObj.parentNode.tagName == 'BODY') {
+            parentSize = Jx.getPageDimensions();
+        } else {
+            parentSize = document.id(this.domObj.parentNode).getContentBoxSize();
+        }
+
+        if (this.lastParentSize && !needsResize) {
+            needsResize = (this.lastParentSize.width != parentSize.width ||
+                          this.lastParentSize.height != parentSize.height);
+        } else {
+            needsResize = true;
+        }
+        this.lastParentSize = parentSize;
+
+        if (!needsResize) {
+            return;
+        }
+
+        var l, t, w, h;
+
+        /* calculate left and width */
+        if (this.options.left != null) {
+            /* fixed left */
+            l = this.options.left;
+            if (this.options.right == null) {
+                /* variable right */
+                if (this.options.width == null) {
+                    /* variable right and width
+                     * set right to min, stretch width */
+                    w = parentSize.width - l;
+                    if (w < this.options.minWidth ) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* variable right, fixed width
+                     * use width
+                     */
+                    w = this.options.width;
+                }
+            } else {
+                /* fixed right */
+                if (this.options.width == null) {
+                    /* fixed right, variable width
+                     * stretch width
+                     */
+                    w = parentSize.width - l - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* fixed right, fixed width
+                     * respect left and width, allow right to stretch
+                     */
+                    w = this.options.width;
+                }
+            }
+
+        } else {
+            if (this.options.right == null) {
+                if (this.options.width == null) {
+                    /* variable left, width and right
+                     * set left, right to min, stretch width
+                     */
+                     l = 0;
+                     w = parentSize.width;
+                     if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                         l = l + parseInt(w - this.options.maxWidth,10)/2;
+                         w = this.options.maxWidth;
+                     }
+                } else {
+                    /* variable left, fixed width, variable right
+                     * distribute space between left and right
+                     */
+                    w = this.options.width;
+                    l = parseInt((parentSize.width - w)/2,10);
+                    if (l < 0) {
+                        l = 0;
+                    }
+                }
+            } else {
+                if (this.options.width != null) {
+                    /* variable left, fixed width, fixed right
+                     * left is calculated directly
+                     */
+                    w = this.options.width;
+                    l = parentSize.width - w - this.options.right;
+                    if (l < 0) {
+                        l = 0;
+                    }
+                } else {
+                    /* variable left and width, fixed right
+                     * set left to min value and stretch width
+                     */
+                    l = 0;
+                    w = parentSize.width - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        l = w - this.options.maxWidth - this.options.right;
+                        w = this.options.maxWidth;
+                    }
+                }
+            }
+        }
+
+        /* calculate the top and height */
+        if (this.options.top != null) {
+            /* fixed top */
+            t = this.options.top;
+            if (this.options.bottom == null) {
+                /* variable bottom */
+                if (this.options.height == null) {
+                    /* variable bottom and height
+                     * set bottom to min, stretch height */
+                    h = parentSize.height - t;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* variable bottom, fixed height
+                     * stretch height
+                     */
+                    h = this.options.height;
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = h - this.options.maxHeight;
+                        h = this.options.maxHeight;
+                    }
+                }
+            } else {
+                /* fixed bottom */
+                if (this.options.height == null) {
+                    /* fixed bottom, variable height
+                     * stretch height
+                     */
+                    h = parentSize.height - t - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* fixed bottom, fixed height
+                     * respect top and height, allow bottom to stretch
+                     */
+                    h = this.options.height;
+                }
+            }
+        } else {
+            if (this.options.bottom == null) {
+                if (this.options.height == null) {
+                    /* variable top, height and bottom
+                     * set top, bottom to min, stretch height
+                     */
+                     t = 0;
+                     h = parentSize.height;
+                     if (h < this.options.minHeight) {
+                         h = this.options.minHeight;
+                     }
+                     if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                         t = parseInt((parentSize.height - this.options.maxHeight)/2,10);
+                         h = this.options.maxHeight;
+                     }
+                } else {
+                    /* variable top, fixed height, variable bottom
+                     * distribute space between top and bottom
+                     */
+                    h = this.options.height;
+                    t = parseInt((parentSize.height - h)/2,10);
+                    if (t < 0) {
+                        t = 0;
+                    }
+                }
+            } else {
+                if (this.options.height != null) {
+                    /* variable top, fixed height, fixed bottom
+                     * top is calculated directly
+                     */
+                    h = this.options.height;
+                    t = parentSize.height - h - this.options.bottom;
+                    if (t < 0) {
+                        t = 0;
+                    }
+                } else {
+                    /* variable top and height, fixed bottom
+                     * set top to min value and stretch height
+                     */
+                    t = 0;
+                    h = parentSize.height - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = parentSize.height - this.options.maxHeight - this.options.bottom;
+                        h = this.options.maxHeight;
+                    }
+                }
+            }
+        }
+
+        //TODO: check left, top, width, height against current styles
+        // and only apply changes if they are not the same.
+
+        /* apply the new sizes */
+        var sizeOpts = {width: w};
+        if (this.options.position == 'absolute') {
+            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+m.left,
+                top: t+m.top
+            });
+            sizeOpts.height = h;
+        } else {
+            if (this.options.height) {
+                sizeOpts.height = this.options.height;
+            }
+        }
+        this.domObj.setBorderBoxSize(sizeOpts);
+
+        if (this.options.propagate) {
+            // propogate changes to children
+            var o = {forceResize: options ? options.forceResize : false};
+            $A(this.domObj.childNodes).each(function(child){
+                if (child.resize && child.getStyle('display') != 'none') {
+                    child.resize.delay(0,child,o);
+                }
+            });
+        }
+
+        this.fireEvent('sizeChange',this);
+    }
+});/*
+---
+
+name: Jx.Toolbar
+
+description: A toolbar is a container object that contains other objects such as buttons.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.List
+
+provides: [Jx.Toolbar]
+
+css:
+ - toolbar
+
+images:
+ - toolbar.png
+...
+ */
+// $Id: toolbar.js 960 2010-06-06 22:23: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: list
+     * {<Jx.List>} the list that holds the items in this toolbar
+     */
+    list : 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: {
+        /* 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: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. If scroll is set to true
+         * this option does nothing. Default: 'left', valid values: 'left',
+         * 'center', or 'right'
+         */
+        align: 'left',
+        /* Option: scroll
+         * if true, the toolbar may scroll if the contents are wider than
+         * the size of the toolbar
+         */
+        scroll: true,
+        template: '<ul class="jxToolbar"></ul>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolbar'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxToolbar', this);
+        if ($defined(this.options.id)) {
+            this.domObj.id = this.options.id;
+        }
+
+        this.list = new Jx.List(this.domObj, {
+            onAdd: function(item) {
+                this.fireEvent('add', this);
+            }.bind(this),
+            onRemove: function(item) {
+                this.fireEvent('remove', this);
+            }.bind(this)
+        });
+
+        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 = document.id(parent).retrieve('jxBarContainer');
+        if (!tbc) {
+            tbc = new Jx.Toolbar.Container({
+                parent: parent,
+                position: this.options.position,
+                autoSize: this.options.autoSize,
+                align: this.options.align,
+                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) {
+            var item = thing;
+            if (item.domObj) {
+                item = item.domObj;
+            }
+
+            if (item.tagName == 'LI') {
+                if (!item.hasClass('jxToolItem')) {
+                    item.addClass('jxToolItem');
+                }
+            } else {
+                item = new Jx.Toolbar.Item(thing);
+            }
+            this.list.add(item);
+        }, this);
+        
+        //Update the size of the toolbar container.
+        this.update();
+        
+        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');
+        this.list.remove(li);
+        this.update();
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the toolbar
+     */
+    empty: function() {
+      this.list.each(function(item){this.remove(item);},this);
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the Toolbar (when it is acting as a menu bar).
+     */
+    deactivate: function() {
+        this.list.each(function(item){
+            if (item.retrieve('jxMenu')) {
+                item.retrieve('jxMenu').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);
+    },
+    /**
+     * Method: update
+     * Updates the size of the UL so that the size is always consistently the 
+     * exact size of the size of the sum of the buttons. This will keep all of 
+     * the buttons on one line.
+     */
+    update: function () {
+        // if (['top','bottom'].contains(this.options.position)) {
+        //     (function(){
+        //         var s = 0;
+        //         var children = this.domObj.getChildren();
+        //         children.each(function(button){
+        //             var size = button.getMarginBoxSize();
+        //             s += size.width +0.5;
+        //         },this);
+        //         if (s !== 0) {
+        //             this.domObj.setStyle('width', Math.round(s));
+        //         } else {
+        //             this.domObj.setStyle('width','auto');
+        //         }
+        //     }).delay(1,this);
+        // }
+        this.fireEvent('update');
+    },
+    changeText : function(lang) {
+      this.update();
+    }
+});
+/*
+---
+
+name: Jx.Toolbar.Container
+
+description: A toolbar container contains toolbars.  This has an optional dependency on Fx.Tween that, if included, will allow toolbars that contain more elements than can be displayed to be smoothly scrolled left and right.  Without this optional dependency, the toolbar will jump in fixed increments rather than smoothly scrolling.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+ - Jx.Button
+
+optional:
+ - Core/Fx.Tween
+
+provides: [Jx.Toolbar.Container]
+
+images:
+ - emblems.png
+
+...
+ */
+// $Id: container.js 1006 2011-01-01 22:43:42Z 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,
+    Binds: ['update'],
+    pluginNamespace: 'ToolbarContainer',
+    /**
+     * 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,
+        /**
+         * Option: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. This option overrides
+         * scroll if set to something other than the default. Default: 'left',
+         * valid values are 'left','center', or 'right'
+         */
+        align: 'left',
+        template: "<div class='jxBarContainer'><div class='jxBarControls'></div></div>",
+        scrollerTemplate: "<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"
+    },
+    classes: new Hash({
+        domObj: 'jxBarContainer',
+        scroller: 'jxBarScroller',
+        //used to hide the overflow of the wrapper
+        wrapper: 'jxBarWrapper',
+        controls: 'jxBarControls'
+        //used to allow multiple toolbars to float next to each other
+    }),
+
+    updating: false,
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Container
+     */
+    render: function() {
+        this.parent();
+        /* if a container was passed in, use it instead of the one from the
+         * template
+         */
+        if (document.id(this.options.parent)) {
+            this.domObj = document.id(this.options.parent);
+            this.elements = new Hash({
+                'jxBarContainer': this.domObj
+            });
+            this.domObj.addClass('jxBarContainer');
+            this.domObj.grab(this.controls);
+            this.domObj.addEvent('sizeChange', this.update);
+        }
+
+        if (!['center', 'right'].contains(this.options.align) && this.options.scroll) {
+            this.processElements(this.options.scrollerTemplate, this.classes);
+            this.domObj.grab(this.scroller, 'top');
+        }
+        
+        //add the alignment option... not sure why this keeps getting removed??
+        this.domObj.addClass('jxToolbarAlign' + 
+                this.options.align.capitalize());
+
+        /* 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', function(){
+              this.domObj.getParent().addEvent('sizeChange', this.update);
+              this.update();
+            });
+
+            this.scrollLeft = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollLeft).addClass('jxBarScrollLeft');
+            this.scrollLeft.addEvents({
+                click: this.scroll.bind(this, 'left')
+            });
+
+            this.scrollRight = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollRight).addClass('jxBarScrollRight');
+            this.scrollRight.addEvents({
+                click: this.scroll.bind(this, 'right')
+            });
+
+        } else if (this.options.scroll && ['left', 'right'].contains(this.options.position)) {
+            //do we do scrolling up and down?
+            //for now disable scroll in this case
+            this.options.scroll = false;
+        } else {
+            this.options.scroll = false;
+        }
+
+        this.addEvent('add', this.update);
+        if (this.options.toolbars) {
+            this.add(this.options.toolbars);
+        }
+    },
+
+    /**
+     * APIMethod: update
+     * Updates the scroller enablement dependent on the total size of the
+     * toolbar(s).
+     */
+    update: function() {
+        if (this.options.scroll) {
+            if (['top', 'bottom'].contains(this.options.position)) {
+                var tbcSize = this.domObj.getContentBoxSize().width;
+
+                var s = 0;
+                //next check to see if we need the scrollers or not.
+                var children = this.wrapper.getChildren();
+                if (children.length > 0) {
+                    children.each(function(tb) {
+                        s += tb.getMarginBoxSize().width;
+                    },
+                    this);
+
+                    var scrollerSize = tbcSize;
+
+                    if (s === 0) {
+                        this.scrollLeft.setEnabled(false);
+                        this.scrollRight.setEnabled(false);
+                    } else {
+
+
+                        var leftMargin = this.wrapper.getStyle('margin-left').toInt();
+                        scrollerSize -= this.controls.getMarginBoxSize().width;
+
+
+                        if (leftMargin < 0) {
+                            //has been scrolled left so activate the right scroller
+                            this.scrollLeft.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollLeft.setEnabled(false);
+                        }
+
+                        if (s + leftMargin > scrollerSize) {
+                            //we need the right one
+                            this.scrollRight.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollRight.setEnabled(false);
+                        }
+                    }
+
+                } else {
+                    this.scrollRight.setEnabled(false);
+                    this.scrollLeft.setEnabled(false);
+                }
+                this.scroller.setStyle('width', scrollerSize);
+
+                this.findFirstVisible();
+                this.updating = false;
+            }
+        }
+    },
+    /**
+     * Method: findFirstVisible
+     * Finds the first visible button on the toolbar and saves a reference in 
+     * the scroller object
+     */
+    findFirstVisible: function() {
+        if ($defined(this.scroller.retrieve('buttonPointer'))) {
+            return;
+        };
+
+        var children = this.wrapper.getChildren();
+
+        if (children.length > 0) {
+            children.each(function(toolbar) {
+                var buttons = toolbar.getChildren();
+                if (buttons.length > 1) {
+                    buttons.each(function(button) {
+                        var pos = button.getCoordinates(this.scroller);
+                        if (pos.left >= 0 && !$defined(this.scroller.retrieve('buttonPointer'))) {
+                            //this is the first visible button
+                            this.scroller.store('buttonPointer', button);
+                        }
+                    },
+                    this);
+                }
+            },
+            this);
+        }
+    },
+
+    /**
+     * APIMethod: 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('update', this.update.bind(this));
+                thing.addEvent('show', this.scrollIntoView.bind(this));
+            }
+            if (this.wrapper) {
+                this.wrapper.adopt(thing.domObj);
+            } else {
+                this.domObj.adopt(thing.domObj);
+            }
+            this.domObj.addClass('jxBar' + this.options.position.capitalize());
+        },
+        this);
+        if (arguments.length > 0) {
+            this.fireEvent('add', this);
+        }
+        return this;
+    },
+
+    /**
+     * Method: scroll
+     * Does the work of scrolling the toolbar to a specific position.
+     *
+     * Parameters:
+     * direction - whether to scroll left or right
+     */
+    scroll: function(direction) {
+        if (this.updating) {
+            return
+        };
+        this.updating = true;
+
+        var currentButton = this.scroller.retrieve('buttonPointer');
+        if (direction === 'left') {
+            //need to tween the amount of the previous button
+            var previousButton = this.scroller.retrieve('previousPointer');
+            if (!previousButton) {
+                previousButton = this.getPreviousButton(currentButton);
+            }
+            if (previousButton) {
+                var w = previousButton.getMarginBoxSize().width;
+                var ml = this.wrapper.getStyle('margin-left').toInt();
+                ml += w;
+                if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                    //scroll it
+                    this.wrapper.get('tween', {
+                        property: 'margin-left',
+                        onComplete: this.afterTweenLeft.bind(this, previousButton)
+                    }).start(ml);
+                } else {
+                    //set it
+                    this.wrapper.setStyle('margin-left', ml);
+                    this.afterTweenLeft(previousButton);
+                }
+            } else {
+                this.update();
+            }
+        } else {
+            //must be right
+            var w = currentButton.getMarginBoxSize().width;
+
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= w;
+
+            //now, if Fx is defined tween the margin to the left to
+            //hide the current button
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+
+        }
+    },
+
+    /**
+     * Method: afterTweenRight
+     * Updates pointers to buttons after the toolbar scrolls right
+     *
+     * Parameters:
+     * currentButton - the button that was currently first before the scroll
+     */
+    afterTweenRight: function(currentButton) {
+        var np = this.getNextButton(currentButton);
+        if (!np) {
+            np = currentButton;
+        }
+        this.scroller.store('buttonPointer', np);
+        if (np !== currentButton) {
+            this.scroller.store('previousPointer', currentButton);
+        }
+        this.update();
+    },
+    /**
+     * Method: afterTweenLeft
+     * Updates pointers to buttons after the toolbar scrolls left
+     *
+     * Parameters:
+     * previousButton - the button that was to the left of the first visible
+     *      button.
+     */
+    afterTweenLeft: function(previousButton) {
+        this.scroller.store('buttonPointer', previousButton);
+        var pp = this.getPreviousButton(previousButton);
+        if ($defined(pp)) {
+            this.scroller.store('previousPointer', pp);
+        } else {
+            this.scroller.eliminate('previousPointer');
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 instanceof Jx.Widget) {
+            item.dispose();
+        } else {
+            document.id(item).dispose();
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 currentButton = this.scroller.retrieve('buttonPointer');
+
+        if (!$defined(currentButton)) return;
+
+        if ($defined(item.domObj)) {
+            item = item.domObj;
+            while (!item.hasClass('jxToolItem')) {
+                item = item.getParent();
+            }
+        }
+        var pos = item.getCoordinates(this.scroller);
+        var scrollerSize = this.scroller.getStyle('width').toInt();
+
+        if (pos.right > 0 && pos.right <= scrollerSize && pos.left > 0 && pos.left <= scrollerSize) {
+           //we are completely on screen 
+            return;
+        };
+
+        if (pos.right > scrollerSize) {
+            //it's right of the scroller
+            var diff = pos.right - scrollerSize;
+
+            //loop through toolbar items until we have enough width to
+            //make the item visible
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            var w = currentButton.getMarginBoxSize().width;
+            var np;
+            while (w < diff && $defined(currentButton)) {
+                np = this.getNextButton(currentButton);
+                if (np) {
+                    w += np.getMarginBoxSize().width;
+                } else {
+                    break;
+                }
+                currentButton = np;
+            }
+
+            ml -= w;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+        } else {
+            //it's left of the scroller
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= pos.left;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenLeft.bind(this, item)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenLeft(item);
+            }
+        }
+
+    },
+    /**
+     * Method: getPreviousButton
+     * Finds the button to the left of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getPreviousButton: function(currentButton) {
+        pp = currentButton.getPrevious();
+        if (!$defined(pp)) {
+            //check for a new toolbar
+            pp = currentButton.getParent().getPrevious();
+            if (pp) {
+                pp = pp.getLast();
+            }
+        }
+        return pp;
+    },
+    /**
+     * Method: getNextButton
+     * Finds the button to the right of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getNextButton: function(currentButton) {
+        np = currentButton.getNext();
+        if (!np) {
+            np = currentButton.getParent().getNext();
+            if (np) {
+                np = np.getFirst();
+            }
+        }
+        return np;
+    }
+
+});
+/*
+---
+
+name: Jx.Toolbar.Item
+
+description: A helper class to provide a container for something to go into a Jx.Toolbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+
+provides: [Jx.Toolbar.Item]
+
+...
+ */
+// $Id: toolbar.item.js 960 2010-06-06 22:23: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.Widget,
+    options: {
+        /* Option: active
+         * is this item active or not?  Default is true.
+         */
+        active: true,
+        template: '<li class="jxToolItem"></li>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolItem'
+    }),
+
+    parameters: ['jxThing', 'options'],
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Item.
+     */
+    render: function() {
+        this.parent();
+        var el = document.id(this.options.jxThing);
+        if (el) {
+            this.domObj.adopt(el);
+        }
+    }
+});/*
+---
+
+name: Jx.Panel
+
+description: A panel is a fundamental container object that has a content area and optional toolbars around the content area.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.Menu.Item
+ - Jx.Layout
+ - Jx.Toolbar.Container
+ - Jx.Toolbar.Item
+
+provides: [Jx.Panel]
+
+css:
+ - panel
+
+images:
+ - panel_controls.png
+ - panelbar.png
+
+...
+ */
+// $Id: panel.js 980 2010-09-09 14:02:45Z pagameba $
+/**
+ * Class: Jx.Panel
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * some user controls as determined by the options passed to the
+ * constructor.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * close - fired when the panel is closed
+ * collapse - fired when the panel is collapsed
+ * expand - fired when the panel is opened
+ * 
+ * MooTools.lang Keys:
+ * - panel.collapseTooltip
+ * - panel.collapseLabel
+ * - panel.expandlabel
+ * - panel.maximizeTooltip
+ * - panel.maximizeLabel
+ * - panel.restoreTooltip
+ * - panel.restoreLabel
+ * - panel.closeTooltip
+ * - panel.closeLabel
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel = new Class({
+    Family: 'Jx.Panel',
+    Extends: Jx.Widget,
+
+    toolbarContainers: {
+        top: null,
+        right: null,
+        bottom: null,
+        left: null
+    },
+
+     options: {
+        position: null,
+        collapsedClass: 'jxPanelMin',
+        collapseClass: 'jxPanelCollapse',
+        menuClass: 'jxPanelMenu',
+        maximizeClass: 'jxPanelMaximize',
+        closeClass: 'jxPanelClose',
+
+        /* Option: label
+         * String, the title of the Jx Panel
+         */
+        label: '&nbsp;',
+        /* Option: height
+         * integer, fixed height to give the panel - no fixed height by
+         * default.
+         */
+        height: null,
+        /* Option: collapse
+         * boolean, determine if the panel can be collapsed and expanded
+         * by the user.  This puts a control into the title bar for the user
+         * to control the state of the panel.
+         */
+        collapse: true,
+        /* Option: close
+         * boolean, determine if the panel can be closed (hidden) by the user.
+         * The application needs to provide a way to re-open the panel after
+         * it is closed.  The closeable property extends to dialogs created by
+         * floating panels.  This option puts a control in the title bar of
+         * the panel.
+         */
+        close: false,
+        /* Option: closed
+         * boolean, initial state of the panel (true to start the panel
+         *  closed), default is false
+         */
+        closed: false,
+        /* Option: hideTitle
+         * Boolean, hide the title bar if true.  False by default.
+         */
+        hideTitle: false,
+        /* Option: toolbars
+         * array of Jx.Toolbar objects to put in the panel.  The position
+         * of each toolbar is used to position the toolbar within the panel.
+         */
+        toolbars: [],
+        type: 'panel',
+        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',
+        controlButtonTemplate: '<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'
+    },
+    classes: new Hash({
+        domObj: 'jxPanel',
+        title: 'jxPanelTitle',
+        domImg: 'jxPanelIcon',
+        domLabel: 'jxPanelLabel',
+        domControls: 'jxPanelControls',
+        contentContainer: 'jxPanelContentContainer',
+        content: 'jxPanelContent'
+    }),
+
+    /**
+     * APIMethod: render
+     * Initialize a new Jx.Panel instance
+     */
+    render : function(){
+        this.parent();
+
+        this.toolbars = this.options ? this.options.toolbars || [] : [];
+
+        this.options.position = ($defined(this.options.height) && !$defined(this.options.position)) ? 'relative' : 'absolute';
+
+        if (this.options.image && this.domImg) {
+            this.domImg.setStyle('backgroundImage', 'url('+this.options.image+')');
+        }
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        var tbDiv = new Element('div');
+        this.domControls.adopt(tbDiv);
+        this.toolbar = new Jx.Toolbar({parent:tbDiv, scroll: false});
+
+        var that = this;
+        if (this.options.menu) {
+            this.menu = new Jx.Menu({
+                image: Jx.aPixel.src
+            }, {
+              buttonTemplate: this.options.controlButtonTemplate
+            });
+            this.menu.domObj.addClass(this.options.menuClass);
+            this.menu.domObj.addClass('jxButtonContentLeft');
+            this.toolbar.add(this.menu);
+        }
+
+        //var b, item;
+        if (this.options.collapse) {
+            if (this.title) {
+              this.title.addEvent('dblclick', function() {
+                that.toggleCollapse();
+              });
+            }
+            this.colB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'collapseTooltip'},
+                onClick: function() {
+                    that.toggleCollapse();
+                }
+            });
+            this.colB.domObj.addClass(this.options.collapseClass);
+            this.addEvents({
+                collapse: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'expandTooltip'});
+                }.bind(this),
+                expand: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.colB);
+            if (this.menu) {
+                this.colM = new Jx.Menu.Item({
+                    label: this.options.collapseLabel,
+                    onClick: function() { that.toggleCollapse(); }
+                });
+                var item = this.colM
+                this.addEvents({
+                    collapse: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+                    }.bind(this),
+                    expand: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(item);
+            }
+        }
+
+        if (this.options.maximize) {
+            this.maxB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'maximizeTooltip'},
+                onClick: function() {
+                    that.maximize();
+                }
+            });
+            this.maxB.domObj.addClass(this.options.maximizeClass);
+            this.addEvents({
+                maximize: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'restoreTooltip'});
+                }.bind(this),
+                restore: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.maxB);
+            if (this.menu) {
+                this.maxM = new Jx.Menu.Item({
+                    label: this.options.maximizeLabel,
+                    onClick: function() { that.maximize(); }
+                });
+                
+                this.addEvents({
+                    maximize: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'maximizeLabel'});
+                    }.bind(this),
+                    restore: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'restoreLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(this.maxM);
+            }
+        }
+
+        if (this.options.close) {
+            this.closeB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'closeTooltip'},
+                onClick: function() {
+                    that.close();
+                }
+            });
+            this.closeB.domObj.addClass(this.options.closeClass);
+            this.toolbar.add(this.closeB);
+            if (this.menu) {
+                this.closeM = new Jx.Menu.Item({
+                    label: {set:'Jx',key:'panel',value:'closeLabel'},
+                    onClick: function() {
+                        that.close();
+                    }
+                });
+                this.menu.add(item);
+            }
+
+        }
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        var jxl = new Jx.Layout(this.domObj, $merge(this.options, {propagate:false}));
+        var layoutHandler = this.layoutContent.bind(this);
+        jxl.addEvent('sizeChange', layoutHandler);
+
+        if (this.options.hideTitle) {
+            this.title.dispose();
+        }
+
+        if (Jx.type(this.options.toolbars) == 'array') {
+            this.options.toolbars.each(function(tb){
+                var position = tb.options.position;
+                var tbc = this.toolbarContainers[position];
+                if (!tbc) {
+                    tbc = new Element('div');
+                    new Jx.Layout(tbc);
+                    this.contentContainer.adopt(tbc);
+                    this.toolbarContainers[position] = tbc;
+                }
+                tb.addTo(tbc);
+            }, this);
+        }
+
+        new Jx.Layout(this.contentContainer);
+        new Jx.Layout(this.content);
+
+        if(this.shouldLoadContent()) {
+          this.loadContent(this.content);
+        }
+
+        this.toggleCollapse(this.options.closed);
+
+        this.addEvent('addTo', function() {
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: layoutContent
+     * the sizeChange event of the <Jx.Layout> that manages the outer container
+     * is intercepted and passed through this method to handle resizing of the
+     * panel contents because we need to do some calculations if the panel
+     * is collapsed and if there are toolbars to put around the content area.
+     */
+    layoutContent: function() {
+        var titleHeight = 0;
+        var top = 0;
+        var bottom = 0;
+        var left = 0;
+        var right = 0;
+        var tbc;
+        var tb;
+        var position;
+        if (!this.options.hideTitle && this.title.parentNode == this.domObj) {
+            titleHeight = this.title.getMarginBoxSize().height;
+        }
+        var domSize = this.domObj.getContentBoxSize();
+        if (domSize.height > titleHeight) {
+            this.contentContainer.setStyle('display','block');
+            this.options.closed = false;
+            this.contentContainer.resize({
+                top: titleHeight,
+                height: null,
+                bottom: 0
+            });
+            ['left','right'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.width = 'auto';
+                }
+            }, this);
+            ['top','bottom'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.height = '';
+                }
+            }, this);
+            if (Jx.type(this.options.toolbars) == 'array') {
+                this.options.toolbars.each(function(tb){
+                    tb.update();
+                    position = tb.options.position;
+                    tbc = this.toolbarContainers[position];
+                    // IE 6 doesn't seem to want to measure the width of
+                    // things correctly
+                    if (Browser.Engine.trident4) {
+                        var oldParent = document.id(tbc.parentNode);
+                        tbc.style.visibility = 'hidden';
+                        document.id(document.body).adopt(tbc);
+                    }
+                    var size = tbc.getBorderBoxSize();
+                    // put it back into its real parent now we are done
+                    // measuring
+                    if (Browser.Engine.trident4) {
+                        oldParent.adopt(tbc);
+                        tbc.style.visibility = '';
+                    }
+                    switch(position) {
+                        case 'bottom':
+                            bottom = size.height;
+                            break;
+                        case 'left':
+                            left = size.width;
+                            break;
+                        case 'right':
+                            right = size.width;
+                            break;
+                        case 'top':
+                        default:
+                            top = size.height;
+                            break;
+                    }
+                },this);
+            }
+            tbc = this.toolbarContainers['top'];
+            if (tbc) {
+                tbc.resize({top: 0, left: left, right: right, bottom: null, height: top, width: null});
+            }
+            tbc = this.toolbarContainers['bottom'];
+            if (tbc) {
+                tbc.resize({top: null, left: left, right: right, bottom: 0, height: bottom, width: null});
+            }
+            tbc = this.toolbarContainers['left'];
+            if (tbc) {
+                tbc.resize({top: top, left: 0, right: null, bottom: bottom, height: null, width: left});
+            }
+            tbc = this.toolbarContainers['right'];
+            if (tbc) {
+                tbc.resize({top: top, left: null, right: 0, bottom: bottom, height: null, width: right});
+            }
+            this.content.resize({top: top, bottom: bottom, left: left, right: right});
+        } else {
+            this.contentContainer.setStyle('display','none');
+            this.options.closed = true;
+        }
+        this.fireEvent('sizeChange', this);
+    },
+
+    /**
+     * Method: setLabel
+     * Set the label in the title bar of this panel
+     *
+     * Parameters:
+     * s - {String} the new label
+     */
+    setLabel: function(s) {
+        this.domLabel.set('html',this.getText(s));
+    },
+    /**
+     * Method: getLabel
+     * Get the label of the title bar of this panel
+     *
+     * Returns:
+     * {String} the label
+     */
+    getLabel: function() {
+        return this.domLabel.get('html');
+    },
+    /**
+     * Method: finalize
+     * Clean up the panel
+     */
+    finalize: function() {
+        this.domObj = null;
+        this.deregisterIds();
+    },
+    /**
+     * Method: maximize
+     * Maximize this panel
+     */
+    maximize: function() {
+        if (this.manager) {
+            this.manager.maximizePanel(this);
+        }
+    },
+    /**
+     * Method: setContent
+     * set the content of this panel to some HTML
+     *
+     * Parameters:
+     * html - {String} the new HTML to go in the panel
+     */
+    setContent : function (html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+    },
+    /**
+     * Method: setContentURL
+     * Set the content of this panel to come from some URL.
+     *
+     * Parameters:
+     * url - {String} URL to some HTML content for this panel
+     */
+    setContentURL : function (url) {
+        this.bContentReady = false;
+        this.setBusy(true);
+        if (arguments[1]) {
+            this.onContentReady = arguments[1];
+        }
+        if (url.indexOf('?') == -1) {
+            url = url + '?';
+        }
+        var a = new Request({
+            url: url,
+            method: 'get',
+            evalScripts:true,
+            onSuccess:this.panelContentLoaded.bind(this),
+            requestHeaders: ['If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT']
+        }).send();
+    },
+    /**
+     * Method: panelContentLoaded
+     * When the content of the panel is loaded from a remote URL, this
+     * method is called when the ajax request returns.
+     *
+     * Parameters:
+     * html - {String} the html return from xhr.onSuccess
+     */
+    panelContentLoaded: function(html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+        this.setBusy(false);
+        if (this.onContentReady) {
+            window.setTimeout(this.onContentReady.bind(this),1);
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','none');
+                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;
+                }
+                this.domObj.resize({height: height});
+                this.fireEvent('collapse', this);
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','block');
+                this.domObj.resize({height: this.options.height});
+                this.fireEvent('expand', this);
+            }
+        }
+    },
+
+    /**
+     * Method: close
+     * Closes the panel (completely hiding it).
+     */
+    close: function() {
+        this.domObj.dispose();
+        this.fireEvent('close', this);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();	//TODO: change this class so that we can access these properties without too much voodoo...
+    	if($defined(this.closeB)) {
+    		this.closeB.setTooltip({set:'Jx',key:'panel',value:'closeTooltip'});
+    	}
+    	if ($defined(this.closeM)) {
+    		this.closeM.setLabel({set:'Jx',key:'panel',value:'closeLabel'});
+    	}
+    	if ($defined(this.maxB)) {
+    		this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+    	}
+    	if ($defined(this.colB)) {
+    		this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+    	}
+    	if ($defined(this.colM)) {
+	    	if (this.options.closed == true) {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+	    	} else {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+	    	}
+    	}
+      if (this.options.label && this.domLabel) {
+          this.setLabel(this.options.label);
+      }
+      // TODO: is this the right method to call?
+      // if toolbars left/right are used and localized, they may change their size..
+      this.layoutContent();
+    },
+
+    /**
+     * Method to be able to allow loadingOnDemand in subclasses but not here
+     */
+    shouldLoadContent: function() {
+      return true;
+    }
+});/*
+---
+
+name: Jx.Dialog
+
+description: A Jx.Panel that implements a floating dialog.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - more/Keyboard
+
+optional:
+ - More/Drag
+
+provides: [Jx.Dialog]
+
+css:
+ - dialog
+
+images:
+ - dialog_chrome.png
+ - dialog_resize.png
+
+...
+ */
+// $Id: dialog.js 1006 2011-01-01 22:43:42Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog
+ *
+ * Extends: <Jx.Panel>
+ *
+ * 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
+ * operating system dialog boxes, including:
+ *
+ * - dialogs may be modal (user must dismiss the dialog to continue) or
+ * non-modal
+ *
+ * - dialogs are movable (user can drag the title bar to move the dialog
+ * around)
+ *
+ * - dialogs may be a fixed size or allow user resizing.
+ *
+ * Jx.Dialog uses <Jx.ContentLoader> to load content into the content area
+ * of the dialog.  Refer to the <Jx.ContentLoader> documentation for details
+ * on content options.
+ *
+ * Example:
+ * (code)
+ * var dialog = new Jx.Dialog();
+ * (end)
+ *
+ * Events:
+ * open - triggered when the dialog is opened
+ * close - triggered when the dialog is closed
+ * change - triggered when the value of an input in the dialog is changed
+ * resize - triggered when the dialog is resized
+ *
+ * Extends:
+ * Jx.Dialog extends <Jx.Panel>, please go there for more details.
+ * 
+ * MooTools.lang Keys:
+ * - dialog.resizeToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog = new Class({
+    Family: 'Jx.Dialog',
+    Extends: Jx.Panel,
+
+    options: {
+        /* Option: modal
+         * (optional) {Boolean} controls whether the dialog will be modal
+         * or not.  The default is to create modal dialogs.
+         */
+        modal: true,
+        /** 
+         * Option: maskOptions
+         */
+        maskOptions: {
+          'class':'jxModalMask',
+          maskMargins: true,
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          }
+        },
+        eventMaskOptions: {
+          'class':'jxEventMask',
+          maskMargins: false,
+          useIframeShim: false,
+          destroyOnHide: true
+        },
+        /* just overrides default position of panel, don't document this */
+        position: 'absolute',
+        /* Option: width
+         * (optional) {Integer} the initial width in pixels of the dialog.
+         * The default value is 250 if not specified.
+         */
+        width: 250,
+        /* Option: height
+         * (optional) {Integer} the initial height in pixels of the
+         * dialog. The default value is 250 if not specified.
+         */
+        height: 250,
+        /* Option: horizontal
+         * (optional) {String} the horizontal rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        horizontal: 'center center',
+        /* Option: vertical
+         * (optional) {String} the vertical rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        vertical: 'center center',
+        /* Option: label
+         * (optional) {String} the title of the dialog box.
+         */
+        label: '',
+        /* Option: parent
+         * (optional) {HTMLElement} a reference to an HTML element that
+         * the dialog is to be contained by.  The default value is for the dialog
+         * to be contained by the body element.
+         */
+        //parent: null,
+        /* Option: resize
+         * (optional) {Boolean} determines whether the dialog is
+         * resizeable by the user or not.  Default is false.
+         */
+        resize: false,
+
+        /* Option: move
+         * (optional) {Boolean} determines whether the dialog is
+         * moveable by the user or not.  Default is true.
+         */
+        move: true,
+        /*
+         * Option: limit
+         * (optional) {Object} || false
+         * passed to the Drag instance of this dialog to limit the movement
+         * {Object} must have x&y coordinates with a range, like {x:[0,500],y:[0,500]}.
+         * Set an id or a reference of a DOM Element (ie 'document', 'myContainerWithId', 
+         * $('myContainer'), $('domID').getParent()) to use these dimensions
+         * as boundaries. Default is false.
+         */
+        limit : false,
+        /* Option: close
+         * (optional) {Boolean} determines whether the dialog is
+         * closeable by the user or not.  Default is true.
+         */
+        close: true,
+        /**
+         * Option: useKeyboard
+         * (optional) {Boolean} determines whether the Dialog listens to keyboard events globally
+         * Default is false
+         */
+        useKeyboard : false,
+        /**
+         * Option: keys
+         * (optional) {Object} refers with the syntax for MooTools Keyboard Class
+         * to functions. Set key to false to disable it manually 
+         */
+        keys: {
+          'esc' : 'close'
+        },
+        /**
+         * Option: keyboardMethods
+         *
+         * can be used to overwrite existing keyboard methods that are used inside
+         * this.options.keys - also possible to add new ones.
+         * Functions are bound to the dialog when using 'this'
+         *
+         * example:
+         *  keys : {
+         *    'alt+enter' : 'maximizeDialog'
+         *  },
+         *  keyboardMethods: {
+         *    'maximizeDialog' : function(ev){
+         *      ev.preventDefault();
+         *      this.maximize();
+         *    }
+         *  }
+         */
+        keyboardMethods : {},
+        collapsedClass: 'jxDialogMin',
+        collapseClass: 'jxDialogCollapse',
+        menuClass: 'jxDialogMenu',
+        maximizeClass: 'jxDialogMaximize',
+        closeClass: 'jxDialogClose',
+        type: 'dialog',
+        template: '<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'
+    },
+    classes: new Hash({
+        domObj: 'jxDialog',
+        title: 'jxDialogTitle',
+        domImg: 'jxDialogIcon',
+        domLabel: 'jxDialogLabel',
+        domControls: 'jxDialogControls',
+        contentContainer: 'jxDialogContentContainer',
+        content: 'jxDialogContent'
+    }),
+    /**
+     * MooTools Keyboard class for Events (mostly used in Dialog.Confirm, Prompt or Message)
+     * But also optional here with esc to close
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * renders Jx.Dialog
+     */
+    render: function() {
+        this.isOpening = false;
+        this.firstShow = true;
+
+        this.options = $merge(
+            {parent:document.body}, // these are defaults that can be overridden
+            this.options,
+            {position: 'absolute'} // these override anything passed to the options
+        );
+
+        /* initialize the panel overriding the type and position */
+        this.parent();
+        this.openOnLoaded = this.open.bind(this);
+        this.options.parent = document.id(this.options.parent);
+
+        this.domObj.setStyle('display','none');
+        this.options.parent.adopt(this.domObj);
+
+        /* the dialog is moveable by its title bar */
+        if (this.options.move && typeof Drag != 'undefined') {
+            this.title.addClass('jxDialogMoveable');
+
+            this.options.limit = this.setDragLimit(this.options.limit);
+            // local reference to use Drag instance variables inside onDrag()
+            var self = this;
+            // COMMENT: any reason why the drag instance isn't referenced to the dialog?
+            new Drag(this.domObj, {
+                handle: this.title,
+                limit: this.options.limit,
+                onBeforeStart: (function(){
+                    this.stack();
+                }).bind(this),
+                onStart: function() {
+                    if (!self.options.modal && self.options.parent.mask) {
+                      self.options.parent.mask(self.options.eventMaskOptions);
+                    }
+                    self.contentContainer.setStyle('visibility','hidden');
+                    self.chrome.addClass('jxChromeDrag');
+                    if(self.options.limit) {
+                      var coords = self.options.limitOrig.getCoordinates();
+                      for(var i in coords) {
+                        window.console ? console.log(i, coords[i]) : false;
+                      }
+                      this.options.limit = self.setDragLimit(self.options.limitOrig);
+                    }
+                }, // COMMENT: removed bind(this) for setting the limit to the drag instance
+                onDrag: function() {
+                  if(this.options.limit) {
+                    // find out if the right border of the dragged element is out of range
+                    if(this.value.now.x+self.options.width >= this.options.limit.x[1]) {
+                      this.value.now.x = this.options.limit.x[1] - self.options.width;
+                      this.element.setStyle('left',this.value.now.x);
+                    }
+                    // find out if the bottom border of the dragged element is out of range
+                    if(this.value.now.y+self.options.height >= this.options.limit.y[1]) {
+                      this.value.now.y = this.options.limit.y[1] - self.options.height;
+                      this.element.setStyle('top',this.value.now.y);
+                    }
+                  }
+                },
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    this.contentContainer.setStyle('visibility','');
+                    var left = Math.max(this.chromeOffsets.left, parseInt(this.domObj.style.left,10));
+                    var top = Math.max(this.chromeOffsets.top, parseInt(this.domObj.style.top,10));
+                    this.options.horizontal = left + ' left';
+                    this.options.vertical = top + ' top';
+                    this.position(this.domObj, this.options.parent, this.options);
+                    this.options.left = parseInt(this.domObj.style.left,10);
+                    this.options.top = parseInt(this.domObj.style.top,10);
+                    if (!this.options.closed) {
+                        this.domObj.resize(this.options);
+                    }
+                }).bind(this)
+            });
+        }
+
+        /* the dialog is resizeable */
+        if (this.options.resize && typeof Drag != 'undefined') {
+            this.resizeHandle = new Element('div', {
+                'class':'jxDialogResize',
+                title: this.getText({set:'Jx',key:'panel',value:'resizeTooltip'}),
+                styles: {
+                    'display':this.options.closed?'none':'block'
+                }
+            });
+            this.domObj.appendChild(this.resizeHandle);
+
+            this.resizeHandleSize = this.resizeHandle.getSize();
+            this.resizeHandle.setStyles({
+                bottom: this.resizeHandleSize.height,
+                right: this.resizeHandleSize.width
+            });
+            this.domObj.makeResizable({
+                handle:this.resizeHandle,
+                onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
+                    this.contentContainer.setStyle('visibility','hidden');
+                    this.chrome.addClass('jxChromeDrag');
+                }).bind(this),
+                onDrag: (function() {
+                    this.resizeChrome(this.domObj);
+                }).bind(this),
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    var size = this.domObj.getMarginBoxSize();
+                    this.options.width = size.width;
+                    this.options.height = size.height;
+                    this.layoutContent();
+                    this.domObj.resize(this.options);
+                    this.contentContainer.setStyle('visibility','');
+                    this.fireEvent('resize');
+                    this.resizeChrome(this.domObj);
+
+                }).bind(this)
+            });
+        }
+        /* this adjusts the zIndex of the dialogs when activated */
+        this.domObj.addEvent('mousedown', (function(){
+            this.stack();
+        }).bind(this));
+
+        // initialize keyboard class
+        this.initializeKeyboard();
+    },
+
+    /**
+     * Method: resize
+     * resize the dialog.  This can be called when the dialog is closed
+     * or open.
+     *
+     * Parameters:
+     * width - the new width
+     * height - the new height
+     * autoPosition - boolean, false by default, if resizing an open dialog
+     * setting this to true will reposition it according to its position
+     * rules.
+     */
+    resize: function(width, height, autoPosition) {
+        this.options.width = width;
+        this.options.height = height;
+        if (this.domObj.getStyle('display') != 'none') {
+            this.layoutContent();
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            if (autoPosition) {
+                this.position(this.domObj, this.options.parent, this.options);
+            }
+        } else {
+            this.firstShow = false;
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * overload panel's sizeChanged method
+     */
+    sizeChanged: function() {
+        if (!this.options.closed) {
+            this.layoutContent();
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','none');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','none');
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','block');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','block');
+            }
+        }
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+            this.fireEvent('collapse');
+        } else {
+            this.domObj.resize(this.options);
+            this.fireEvent('expand');
+        }
+        this.showChrome(this.domObj);
+    },
+    
+    /**
+     * Method: maximize
+     * Called when the maximize button of a dialog is clicked. It will maximize
+     * the dialog to match the size of its parent.
+     */
+    maximize: function () {
+        
+        if (!this.maximized) {
+            //get size of parent
+            var p = this.options.parent;
+            var size;
+            
+            if (p === document.body) {
+                size = Jx.getPageDimensions();
+            } else {
+                size = p.getBorderBoxSize();
+            }
+            this.previousSettings = {
+                width: this.options.width,
+                height: this.options.height,
+                horizontal: this.options.horizontal,
+                vertical: this.options.vertical,
+                left: this.options.left,
+                right: this.options.right,
+                top: this.options.top,
+                bottom: this.options.bottom
+            };
+            this.options.width = size.width;
+            this.options.height = size.height;
+            this.options.vertical = '0 top';
+            this.options.horizontal = '0 left';
+            this.options.right = 0;
+            this.options.left = 0;
+            this.options.top = 0;
+            this.options.bottom = 0;
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = true;
+            this.domObj.addClass('jxDialogMaximized');
+            this.fireEvent('maximize');
+        } else {
+            this.options = $merge(this.options, this.previousSettings);
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = false;
+            if (this.domObj.hasClass('jxDialogMaximized')) {
+                this.domObj.removeClass('jxDialogMaximized');
+            }
+            this.fireEvent('restore');
+        }
+    },
+
+    /**
+     * Method: show
+     * show the dialog, external code should use the <Jx.Dialog::open> method
+     * to make the dialog visible.
+     */
+    show : function( ) {
+        /* prepare the dialog for display */
+        this.domObj.setStyles({
+            'display': 'block',
+            'visibility': 'hidden'
+        });
+        this.toolbar.update();
+        
+        /* do the modal thing */
+        if (this.options.modal && this.options.parent.mask) {
+          var opts = $merge(this.options.maskOptions || {}, {
+            style: {
+              'z-index': Jx.getNumber(this.domObj.getStyle('z-index')) - 1
+            }
+          });
+          this.options.parent.mask(opts);
+          Jx.Stack.stack(this.options.parent.get('mask').element);
+        }
+        /* stack the dialog */
+        this.stack();
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+        } else {
+            this.domObj.resize(this.options);
+        }
+        
+        if (this.firstShow) {
+            this.contentContainer.resize({forceResize: true});
+            this.layoutContent();
+            this.firstShow = false;
+            /* if the chrome got built before the first dialog show, it might
+             * not have been properly created and we should clear it so it
+             * does get built properly
+             */
+            if (this.chrome) {
+                this.chrome.dispose();
+                this.chrome = null;
+            }
+        }
+        /* update or create the chrome */
+        this.showChrome(this.domObj);
+        /* put it in the right place using auto-positioning */
+        this.position(this.domObj, this.options.parent, this.options);
+        this.domObj.setStyle('visibility', 'visible');
+    },
+    /**
+     * Method: hide
+     * hide the dialog, external code should use the <Jx.Dialog::close>
+     * method to hide the dialog.
+     */
+    hide : function() {
+        this.domObj.setStyle('display','none');
+        this.unstack();
+        if (this.options.modal && this.options.parent.unmask) {
+          Jx.Stack.unstack(this.options.parent.get('mask').element);
+          this.options.parent.unmask();
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.deactivate();
+        }
+    },
+    /**
+     * Method: openURL
+     * open the dialog and load content from the provided url.  If you don't
+     * provide a URL then the dialog opens normally.
+     *
+     * Parameters:
+     * url - <String> the url to load when opening.
+     */
+    openURL: function(url) {
+        if (url) {
+            this.options.contentURL = url;
+            this.options.content = null;  //force Url loading
+            this.setBusy();
+            this.loadContent(this.content);
+            this.addEvent('contentLoaded', this.openOnLoaded);
+        } else {
+            this.open();
+        }
+    },
+
+    /**
+     * Method: open
+     * open the dialog.  This may be delayed depending on the
+     * asynchronous loading of dialog content.  The onOpen
+     * callback function is called when the dialog actually
+     * opens
+     */
+    open: function() {
+        if (!this.isOpening) {
+            this.isOpening = true;
+        }
+        // COMMENT: this works only for onDemand -> NOT for cacheContent = false..
+        // for this loading an URL everytime, use this.openURL(url) 
+        if(!this.contentIsLoaded && this.options.loadOnDemand) {
+          this.loadContent(this.content);
+        }
+        if (this.contentIsLoaded) {
+            this.removeEvent('contentLoaded', this.openOnLoaded);
+            this.show();
+            this.fireEvent('open', this);
+            this.isOpening = false;
+        } else {
+            this.addEvent('contentLoaded', this.openOnLoaded);
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.activate();
+        }
+    },
+    /**
+     * Method: close
+     * close the dialog and trigger the onClose callback function
+     * if necessary
+     */
+    close: function() {
+        this.isOpening = false;
+        this.hide();
+        this.fireEvent('close');
+    },
+
+    cleanup: function() { },
+    
+    /**
+     * APIMethod: isOpen
+     * returns true if the dialog is currently open, false otherwise
+     */
+    isOpen: function () {
+        //check to see if we're visible
+        return !((this.domObj.getStyle('display') === 'none') || (this.domObj.getStyle('visibility') === 'hidden'));
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.maxM)) {
+			if (this.maximize) {
+				this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'restoreLabel'}));
+	    	} else {
+	    		this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'maximizeLabel'}));
+	    	}
+    	}
+    	if ($defined(this.resizeHandle)) {
+    		this.resizeHandle.set('title', this.getText({set:'Jx',key:'dialog',value:'resizeTooltip'}));
+    	}
+      this.toggleCollapse(false);
+    },
+
+    initializeKeyboard: function() {
+      if(this.options.useKeyboard) {
+        var self = this;
+        this.keyboardEvents = {};
+        this.keyboardMethods = {
+          close : function(ev) {ev.preventDefault();self.close()}
+        }
+        this.keyboard = new Keyboard({
+          events: this.getKeyboardEvents()
+        });
+      }
+    },
+
+    /**
+     * Method: getKeyboardMethods
+     * used by this and all child classes to have methods listen to keyboard events,
+     * returned object will be parsed to the events object of a MooTools Keyboard instance
+     *
+     * @return Object
+     */
+    getKeyboardEvents : function() {
+      var self = this;
+      for(var i in this.options.keys) {
+        // only add a reference once, otherwise keyboard events will be fired twice in subclasses
+        if(!$defined(this.keyboardEvents[i])) {
+          if($defined(this.keyboardMethods[this.options.keys[i]])) {
+            this.keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+          }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+            this.keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+          }else if(Jx.type(this.options.keys[i]) == 'function') {
+            this.keyboardEvents[i] = this.options.keys[i].bind(self);
+          }else{
+            // allow disabling of special keys by setting them to false or null with having a warning
+            if(this.options.keyboardMethods[this.options.keys[i]] != false) {
+              $defined(console) ? console.warn("keyboard method %o not defined for %o", this.options.keys[i], this) : false;
+            }
+          }
+        }
+      }
+      return this.keyboardEvents;
+    },
+
+    /**
+     * Method: setDragLimit
+     * calculates the drag-dimensions of an given element to drag
+     *
+     * Parameters:
+     * - reference {Object} (optional) the element|elementId|object to set the limits
+     */
+    setDragLimit : function(reference) {
+      if($defined(reference)) this.options.limit = reference;
+      
+      // check drag limit if it is an container or string for an element and use dimensions
+      var limitType = this.options.limit != null ? Jx.type(this.options.limit) : false;
+      if(this.options.limit && limitType != 'object') {
+        var coords = false;
+        switch(limitType) {
+          case 'string':
+            if(document.id(this.options.limit)) {
+              coords = document.id(this.options.limit).getCoordinates();
+            }
+            break;
+          case 'element':
+          case 'document':
+          case 'window':
+            coords = this.options.limit.getCoordinates();
+            break;
+        }
+        if(coords) {
+          this.options.limitOrig = this.options.limit;
+          this.options.limit = {
+            x : [coords.left, coords.right],
+            y : [coords.top, coords.bottom]
+          }
+        }else{
+          this.options.limit = false;
+        }
+      }
+      return this.options.limit;
+    },
+
+    /**
+     * gets called by parent class Jx.Panel and decides whether to load content or not
+     */
+    shouldLoadContent: function() {
+      return !this.options.loadOnDemand;
+    }
+});
+
+/*
+---
+
+name: Jx.Splitter
+
+description: A Jx.Splitter creates two or more containers within a parent container and provides user control over the size of the containers.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Layout
+
+optional:
+ - More/Drag
+
+provides: [Jx.Splitter]
+
+css:
+ - splitter
+
+...
+ */
+// $Id: splitter.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Splitter
+ *
+ * Extends: <Jx.Object>
+ *
+ * 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.
+ *
+ * A horizontal split creates containers that divide the space horizontally
+ * with vertical bars between the containers.  A vertical split divides
+ * the space vertically and creates horizontal bars between the containers.
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - splitter.barToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Splitter = new Class({
+    Family: 'Jx.Splitter',
+    Extends: Jx.Object,
+    /**
+     * Property: domObj
+     * {HTMLElement} the element being split
+     */
+    domObj: null,
+    /**
+     * Property: elements
+     * {Array} an array of elements that are displayed in each of the split
+     * areas
+     */
+    elements: null,
+    /**
+     * Property: bars
+     * {Array} an array of the bars between each of the elements used to
+     * resize the split areas.
+     */
+    bars: null,
+    /**
+     * Property: firstUpdate
+     * {Boolean} track the first resize event so that unexposed Jx things
+     * can be forced to calculate their size the first time they are exposed.
+     */
+    firstUpdate: true,
+    options: {
+        /* Option: useChildren
+         * {Boolean} if set to true, then the children of the
+         * element to be split are used as the elements.  The default value is
+         * false.  If this is set, then the elements and splitInto options
+         * are ignored.
+         */
+        useChildren: false,
+        /* Option: splitInto
+         * {Integer} the number of elements to split the domObj into.
+         * If not set, then the length of the elements option is used, or 2 if
+         * elements is not specified.  If splitInto is specified and elements
+         * is specified, then splitInto is used.  If there are more elements than
+         * splitInto specifies, then the extras are ignored.  If there are less
+         * elements than splitInto specifies, then extras are created.
+         */
+        splitInto: 2,
+        /* Option: elements
+         * {Array} an array of elements to put into the split areas.
+         * If splitInto is not set, then it is calculated from the length of
+         * this array.
+         */
+        elements: null,
+        /* Option: containerOptions
+         * {Array} an array of objects that provide options
+         *  for the <Jx.Layout> constraints on each element.
+         */
+        containerOptions: [],
+        /* Option: barOptions
+         * {Array} an array of object that provide options for the bars,
+         * this array should be one less than the number of elements in the
+         * splitter.  The barOptions objects can contain a snap property indicating
+         * that a default snap object should be created in the bar and the value
+         * of 'before' or 'after' indicates which element it snaps open/shut.
+         */
+        barOptions: [],
+        /* Option: layout
+         * {String} either 'horizontal' or 'vertical', indicating the
+         * direction in which the domObj is to be split.
+         */
+        layout: 'horizontal',
+        /* Option: snaps
+         * {Array} an array of objects which can be used to snap
+         * elements open or closed.
+         */
+        snaps: [],
+        /* Option: onStart
+         * an optional function to call when a bar starts dragging
+         */
+        onStart: null,
+        /* Option: onFinish
+         * an optional function to call when a bar finishes dragging
+         */
+        onFinish: null
+    },
+
+    parameters: ['domObj','options'],
+
+    /**
+     * APIMethod: init
+     * Create a new instance of Jx.Splitter
+     */
+    init: function() {
+        this.domObj = document.id(this.options.domObj);
+        this.domObj.addClass('jxSplitContainer');
+        var jxLayout = this.domObj.retrieve('jxLayout');
+        if (jxLayout) {
+            jxLayout.addEvent('sizeChange', this.sizeChanged.bind(this));
+        }
+
+        this.elements = [];
+        this.bars = [];
+        var i;
+        var nSplits = 2;
+        if (this.options.useChildren) {
+            this.elements = this.domObj.getChildren();
+            nSplits = this.elements.length;
+        } else {
+            nSplits = this.options.elements ?
+                            this.options.elements.length :
+                            this.options.splitInto;
+            for (i=0; i<nSplits; i++) {
+                var el;
+                if (this.options.elements && this.options.elements[i]) {
+                    if (this.options.elements[i].domObj) {
+                        el = this.options.elements[i].domObj;
+                    } else {
+                        el = document.id(this.options.elements[i]);
+                    }
+                    if (!el) {
+                        el = this.prepareElement();
+                        el.id = this.options.elements[i];
+                    }
+                } else {
+                    el = this.prepareElement();
+                }
+                this.elements[i] = el;
+                this.domObj.adopt(this.elements[i]);
+            }
+        }
+        this.elements.each(function(el) { el.addClass('jxSplitArea'); });
+        for (i=0; i<nSplits; i++) {
+            var jxl = this.elements[i].retrieve('jxLayout');
+            if (!jxl) {
+                new Jx.Layout(this.elements[i], this.options.containerOptions[i]);
+            } else {
+                if (this.options.containerOptions[i]) {
+                    jxl.resize($merge(this.options.containerOptions[i],
+                        {position:'absolute'}));
+                } else {
+                    jxl.resize({position: 'absolute'});
+                }
+            }
+        }
+
+        for (i=1; i<nSplits; i++) {
+            var bar;
+            if (this.options.prepareBar) {
+                bar = this.options.prepareBar(i-1);
+            } else {
+                bar = this.prepareBar();
+            }
+            bar.store('splitterObj', this);
+            bar.store('leftSide',this.elements[i-1]);
+            bar.store('rightSide', this.elements[i]);
+            this.elements[i-1].store('rightBar', bar);
+            this.elements[i].store('leftBar', bar);
+            this.domObj.adopt(bar);
+            this.bars[i-1] = bar;
+        }
+
+        //making dragging dependent on mootools Drag class
+        if ($defined(Drag)) {
+            this.establishConstraints();
+        }
+
+        for (i=0; i<this.options.barOptions.length; i++) {
+            if (!this.bars[i]) {
+                continue;
+            }
+            var opt = this.options.barOptions[i];
+            if (opt && opt.snap && (opt.snap == 'before' || opt.snap == 'after')) {
+                var element;
+                if (opt.snap == 'before') {
+                    element = this.bars[i].retrieve('leftSide');
+                } else if (opt.snap == 'after') {
+                    element = this.bars[i].retrieve('rightSide');
+                }
+                var snap;
+                var snapEvents;
+                if (opt.snapElement) {
+                    snap = opt.snapElement;
+                    snapEvents = opt.snapEvents || ['click', 'dblclick'];
+                } else {
+                    snap = this.bars[i];
+                    snapEvents = opt.snapEvents || ['dblclick'];
+                }
+                if (!snap.parentNode) {
+                    this.bars[i].adopt(snap);
+                }
+                new Jx.Splitter.Snap(snap, element, this, snapEvents);
+            }
+        }
+
+        for (i=0; i<this.options.snaps.length; i++) {
+            if (this.options.snaps[i]) {
+                new Jx.Splitter.Snap(this.options.snaps[i], this.elements[i], this);
+            }
+        }
+
+        this.sizeChanged();
+    },
+    /**
+     * Method: prepareElement
+     * Prepare a new, empty element to go into a split area.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that goes into a split area.
+     */
+    prepareElement: function(){
+        var o = new Element('div', {styles:{position:'absolute'}});
+        return o;
+    },
+
+    /**
+     * Method: prepareBar
+     * Prepare a new, empty bar to go into between split areas.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that becomes a bar.
+     */
+    prepareBar: function() {
+        var o = new Element('div', {
+            'class': 'jxSplitBar'+this.options.layout.capitalize(),
+            'title': this.getText({set:'Jx',key:'splitter',value:'barToolTip'})
+        });
+        return o;
+    },
+
+    /**
+     * Method: establishConstraints
+     * Setup the initial set of constraints that set the behaviour of the
+     * bars between the elements in the split area.
+     */
+    establishConstraints: function() {
+        var modifiers = {x:null,y:null};
+        var fn;
+        if (this.options.layout == 'horizontal') {
+            modifiers.x = "left";
+            fn = this.dragHorizontal;
+        } else {
+            modifiers.y = "top";
+            fn = this.dragVertical;
+        }
+        if (typeof Drag != 'undefined') {
+            this.bars.each(function(bar){
+                var mask;
+                new Drag(bar, {
+                    //limit: limit,
+                    modifiers: modifiers,
+                    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');
+                        if (obj.retrieve('splitterObj') != this) {
+                            return;
+                        }
+                        fn.apply(this,[obj]);
+                        this.fireEvent('complete',[obj]);
+                        this.fireEvent('finish',[obj]);
+                    }).bind(this),
+                    onBeforeStart: (function(obj) {
+                        this.fireEvent('beforeStart',[obj]);
+                        mask = new Element('div',{'class':'jxSplitterMask'}).inject(obj, 'after');
+                    }).bind(this),
+                    onStart: (function(obj, event) {
+                        this.fireEvent('start',[obj, event]);
+                    }).bind(this)
+                });
+            }, this);
+        }
+    },
+
+    /**
+     * Method: dragHorizontal
+     * In a horizontally split container, handle a bar being dragged left or
+     * right by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragHorizontal: function(obj) {
+        var leftEdge = parseInt(obj.style.left,10);
+        var leftSide = obj.retrieve('leftSide');
+        var rightSide = obj.retrieve('rightSide');
+        var leftJxl = leftSide.retrieve('jxLayout');
+        var rightJxl = rightSide.retrieve('jxLayout');
+
+        var paddingLeft = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        /* process right side first */
+        var rsLeft, rsWidth, rsRight;
+
+        var size = obj.retrieve('size');
+        if (!size) {
+            size = obj.getBorderBoxSize();
+            obj.store('size',size);
+        }
+        rsLeft = leftEdge + size.width - paddingLeft;
+
+        var parentSize = this.domObj.getContentBoxSize();
+
+        if (rightJxl.options.width != null) {
+            rsWidth = rightJxl.options.width + rightJxl.options.left - rsLeft;
+            rsRight = parentSize.width - rsLeft - rsWidth;
+        } else {
+            rsWidth = parentSize.width - rightJxl.options.right - rsLeft;
+            rsRight = rightJxl.options.right;
+        }
+
+        /* enforce constraints on right side */
+        if (rsWidth < 0) {
+            rsWidth = 0;
+        }
+
+        if (rsWidth < rightJxl.options.minWidth) {
+            rsWidth = rightJxl.options.minWidth;
+        }
+        if (rightJxl.options.maxWidth >= 0 && rsWidth > rightJxl.options.maxWidth) {
+            rsWidth = rightJxl.options.maxWidth;
+        }
+
+        rsLeft = parentSize.width - rsRight - rsWidth;
+        leftEdge = rsLeft - size.width;
+
+        /* process left side */
+        var lsLeft, lsWidth;
+        lsLeft = leftJxl.options.left;
+        lsWidth = leftEdge - lsLeft;
+
+        /* enforce constraints on left */
+        if (lsWidth < 0) {
+            lsWidth = 0;
+        }
+        if (lsWidth < leftJxl.options.minWidth) {
+            lsWidth = leftJxl.options.minWidth;
+        }
+        if (leftJxl.options.maxWidth >= 0 &&
+            lsWidth > leftJxl.options.maxWidth) {
+            lsWidth = leftJxl.options.maxWidth;
+        }
+
+        /* update the leftEdge to accomodate constraints */
+        if (lsLeft + lsWidth != leftEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            leftEdge = lsLeft + lsWidth;
+            var delta = leftEdge + size.width - rsLeft;
+            rsLeft += delta;
+            rsWidth -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.left = paddingLeft + leftEdge + 'px';
+
+        /* update leftSide positions */
+        if (leftJxl.options.width == null) {
+            parentSize = this.domObj.getContentBoxSize();
+            leftSide.resize({right: parentSize.width - lsLeft-lsWidth});
+        } else {
+            leftSide.resize({width: lsWidth});
+        }
+
+        /* update rightSide position */
+        if (rightJxl.options.width == null) {
+            rightSide.resize({left:rsLeft});
+        } else {
+            rightSide.resize({left: rsLeft, width: rsWidth});
+        }
+    },
+
+    /**
+     * Method: dragVertical
+     * In a vertically split container, handle a bar being dragged up or
+     * down by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragVertical: function(obj) {
+        /* top edge of the bar */
+        var topEdge = parseInt(obj.style.top,10);
+
+        /* the containers on either side of the bar */
+        var topSide = obj.retrieve('leftSide');
+        var bottomSide = obj.retrieve('rightSide');
+        var topJxl = topSide.retrieve('jxLayout');
+        var bottomJxl = bottomSide.retrieve('jxLayout');
+
+        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) {
+            size = obj.getBorderBoxSize();
+            obj.store('size', size);
+        }
+        var parentSize = this.domObj.getContentBoxSize();
+
+        /* process top side first */
+        var bsTop, bsHeight, bsBottom;
+
+        /* top edge of bottom side is the top edge of bar plus the height of the bar */
+        bsTop = topEdge + size.height - paddingTop;
+
+        if (bottomJxl.options.height != null) {
+            /* bottom side height is fixed */
+            bsHeight = bottomJxl.options.height + bottomJxl.options.top - bsTop;
+            bsBottom = parentSize.height - bsTop - bsHeight;
+        } else {
+            /* bottom side height is not fixed. */
+            bsHeight = parentSize.height - bottomJxl.options.bottom - bsTop;
+            bsBottom = bottomJxl.options.bottom;
+        }
+
+        /* enforce constraints on bottom side */
+        if (bsHeight < 0) {
+            bsHeight = 0;
+        }
+
+        if (bsHeight < bottomJxl.options.minHeight) {
+            bsHeight = bottomJxl.options.minHeight;
+        }
+
+        if (bottomJxl.options.maxHeight >= 0 && bsHeight > bottomJxl.options.maxHeight) {
+            bsHeight = bottomJxl.options.maxHeight;
+        }
+
+        /* recalculate the top of the bottom side in case it changed
+           due to a constraint.  The bar may have moved also.
+         */
+        bsTop = parentSize.height - bsBottom - bsHeight;
+        topEdge = bsTop - size.height;
+
+        /* process left side */
+        var tsTop, tsHeight;
+        tsTop = topJxl.options.top;
+        tsHeight = topEdge - tsTop;
+
+        /* enforce constraints on left */
+        if (tsHeight < 0) {
+            tsHeight = 0;
+        }
+        if (tsHeight < topJxl.options.minHeight) {
+            tsHeight = topJxl.options.minHeight;
+        }
+        if (topJxl.options.maxHeight >= 0 &&
+            tsHeight > topJxl.options.maxHeight) {
+            tsHeight = topJxl.options.maxHeight;
+        }
+
+        /* update the topEdge to accomodate constraints */
+        if (tsTop + tsHeight != topEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            topEdge = tsTop + tsHeight;
+            var delta = topEdge + size.height - bsTop;
+            bsTop += delta;
+            bsHeight -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.top = paddingTop + topEdge + 'px';
+
+        /* update topSide positions */
+        if (topJxl.options.height == null) {
+            topSide.resize({bottom: parentSize.height - tsTop-tsHeight});
+        } else {
+            topSide.resize({height: tsHeight});
+        }
+
+        /* update bottomSide position */
+        if (bottomJxl.options.height == null) {
+            bottomSide.resize({top:bsTop});
+        } else {
+            bottomSide.resize({top: bsTop, height: bsHeight});
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * handle the size of the container being changed.
+     */
+    sizeChanged: function() {
+        if (this.options.layout == 'horizontal') {
+            this.horizontalResize();
+        } else {
+            this.verticalResize();
+        }
+    },
+
+    /**
+     * Method: horizontalResize
+     * Resize a horizontally layed-out container
+     */
+    horizontalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().width;
+        var overallWidth = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.width == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size',size);
+            }
+            availableSpace -= size.width;
+        }
+
+        var nVariable = 0, w = 0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.width != null) {
+                availableSpace -= parseInt(jxo.width,10);
+            } else {
+                w = 0;
+                if (jxo.right != 0 ||
+                    jxo.left != 0) {
+                    w = e.getBorderBoxSize().width;
+                }
+
+                availableSpace -= w;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.width;
+            jxo.width = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var leftPadding = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.width != null) {
+                 jxl.resize({left: currentPosition});
+                 currentPosition += jxo.width;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 if (jxo.right != 0 || jxo.left != 0) {
+                     w = e.getBorderBoxSize().width + a;
+                 } else {
+                     w = a;
+                 }
+
+                 if (w < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + w/nVariable;
+                     }
+                     w = 0;
+                 }
+                 if (w < jxo.minWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.minWidth)/nVariable;
+                     }
+                     w = jxo.minWidth;
+                 }
+                 if (jxo.maxWidth >= 0 && w > jxo.maxWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.maxWidth)/nVariable;
+                     }
+                     w = e.options.maxWidth;
+                 }
+
+                 var r = overallWidth - currentPosition - w;
+                 jxl.resize({left: currentPosition, right: r});
+                 currentPosition += w;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.setStyle('left', leftPadding + currentPosition);
+                 currentPosition += rightBar.retrieve('size').width;
+             }
+         }
+    },
+
+    /**
+     * Method: verticalResize
+     * Resize a vertically layed out container.
+     */
+    verticalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().height;
+        var overallHeight = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.height == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size', size);
+            }
+            availableSpace -= size.height;
+        }
+
+        var nVariable = 0, h=0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.height != null) {
+                availableSpace -= parseInt(jxo.height,10);
+            } else {
+                if (jxo.bottom != 0 || jxo.top != 0) {
+                    h = e.getBorderBoxSize().height;
+                }
+
+                availableSpace -= h;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.height;
+            jxo.height = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var paddingTop = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['top']);
+            return m.padding.top;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.height != null) {
+                 jxl.resize({top: currentPosition});
+                 currentPosition += jxo.height;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 h = 0;
+                 if (jxo.bottom != 0 || jxo.top != 0) {
+                     h = e.getBorderBoxSize().height + a;
+                 } else {
+                     h = a;
+                 }
+
+                 if (h < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + h/nVariable;
+                     }
+                     h = 0;
+                 }
+                 if (h < jxo.minHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.minHeight)/nVariable;
+                     }
+                     h = jxo.minHeight;
+                 }
+                 if (jxo.maxHeight >= 0 && h > jxo.maxHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.maxHeight)/nVariable;
+                     }
+                     h = jxo.maxHeight;
+                 }
+
+                 var r = overallHeight - currentPosition - h;
+                 jxl.resize({top: currentPosition, bottom: r});
+                 currentPosition += h;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.style.top = paddingTop + currentPosition + 'px';
+                 currentPosition += rightBar.retrieve('size').height;
+             }
+         }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	this.bars.each(function(bar){
+    		document.id(bar).set('title', this.getText({set:'Jx',key:'splitter',value:'barToolTip'}));
+    	},this);	
+    }
+});/*
+---
+
+name: Jx.PanelSet
+
+description: A panel set manages a set of panels within a DOM element.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Splitter
+ - Jx.Panel
+
+provides: [Jx.PanelSet]
+
+...
+ */
+// $Id: panelset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.PanelSet
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A panel set manages a set of panels within a DOM element.  The PanelSet
+ * fills its container by resizing the panels in the set to fill the width and
+ * then distributing the height of the container across all the panels. 
+ * Panels can be resized by dragging their respective title bars to make them
+ * taller or shorter.  The maximize button on the panel title will cause all
+ * other panels to be closed and the target panel to be expanded to fill the
+ * remaining space.  In this respect, PanelSet works like a traditional
+ * Accordion control.
+ *
+ * When creating panels for use within a panel set, it is important to use the
+ * proper options.  You must override the collapse option and set it to false
+ * and add a maximize option set to true.  You must also not include options
+ * for menu and close.
+ *
+ * Example:
+ * (code)
+ * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'c1'});
+ * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'c2'});
+ * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'c3'});
+ * var panelSet = new Jx.PanelSet('panels', [p1,p2,p3]);
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - panelset.barTooltip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.PanelSet = new Class({
+    Family: 'Jx.PanelSet',
+    Extends: Jx.Widget,
+
+    options: {
+        /* Option: parent
+         * the object to add the panel set to
+         */
+        parent: null,
+        /* Option: panels
+         * an array of <Jx.Panel> objects that will be managed by the set.
+         */
+        panels: []
+    },
+
+    /**
+     * Property: panels
+     * {Array} the panels being managed by the set
+     */
+    panels: null,
+    /**
+     * Property: height
+     * {Integer} the height of the container, cached for speed
+     */
+    height: null,
+    /**
+     * Property: firstLayout
+     * {Boolean} true until the panel set has first been resized
+     */
+    firstLayout: true,
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.PanelSet.
+     */
+    render: function() {
+        if (this.options.panels) {
+            this.panels = this.options.panels;
+            this.options.panels = null;
+        }
+        this.domObj = new Element('div');
+        new Jx.Layout(this.domObj);
+
+        //make a fake panel so we get the right number of splitters
+        var d = new Element('div', {styles:{position:'absolute'}});
+        new Jx.Layout(d, {minHeight:0,maxHeight:0,height:0});
+        var elements = [d];
+        this.panels.each(function(panel){
+            elements.push(panel.domObj);
+            panel.options.hideTitle = true;
+            panel.contentContainer.resize({top:0});
+            panel.toggleCollapse = this.maximizePanel.bind(this,panel);
+            panel.domObj.store('Jx.Panel', panel);
+            panel.manager = this;
+        }, this);
+
+        this.splitter = new Jx.Splitter(this.domObj, {
+            splitInto: this.panels.length+1,
+            layout: 'vertical',
+            elements: elements,
+            prepareBar: (function(i) {
+                var bar = new Element('div', {
+                    'class': 'jxPanelBar',
+                    'title': this.getText({set:'Jx',key:'panelset',value:'barToolTip'})
+                });
+
+                var panel = this.panels[i];
+                panel.title.setStyle('visibility', 'hidden');
+                document.id(document.body).adopt(panel.title);
+                var size = panel.title.getBorderBoxSize();
+                bar.adopt(panel.title);
+                panel.title.setStyle('visibility','');
+
+                bar.setStyle('height', size.height);
+                bar.store('size', size);
+
+                return bar;
+            }).bind(this)
+        });
+        this.addEvent('addTo', function() {
+            document.id(this.domObj.parentNode).setStyle('overflow', 'hidden');
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: maximizePanel
+     * Maximize a panel, taking up all available space (taking into
+     * consideration any minimum or maximum values)
+     */
+    maximizePanel: function(panel) {
+        var domHeight = this.domObj.getContentBoxSize().height;
+        var space = domHeight;
+        var panelSize = panel.domObj.retrieve('jxLayout').options.maxHeight;
+        var panelIndex,i,p,thePanel,o,panelHeight;
+        /* calculate how much space might be left after setting all the panels to
+         * their minimum height (except the one we are resizing of course)
+         */
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            space -= p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                space -= o.minHeight;
+            } else {
+                panelIndex = i;
+            }
+        }
+
+        // calculate how much space the panel will take and what will be left over
+        if (panelSize == -1 || panelSize >= space) {
+            panelSize = space;
+            space = 0;
+        } else {
+            space = space - panelSize;
+        }
+        var top = 0;
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            top += p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // this panel can stay open at its current height
+                        space -= panelHeight;
+                        p.resize({top: top, height: panelHeight});
+                        top += panelHeight;
+                    } else {
+                        // this panel needs to shrink some
+                        if (space > o.minHeight) {
+                            // it can use all the space
+                            p.resize({top: top, height: space});
+                            top += space;
+                            space = 0;
+                        } else {
+                            p.resize({top: top, height: o.minHeight});
+                            top += o.minHeight;
+                        }
+                    }
+                } else {
+                    // no more space, just shrink away
+                    p.resize({top:top, height: o.minHeight});
+                    top += o.minHeight;
+                }
+                p.retrieve('rightBar').style.top = top + 'px';
+            } else {
+                break;
+            }
+        }
+
+        /* now work from the bottom up */
+        var bottom = domHeight;
+        for (i=this.splitter.elements.length - 1; i > 0; i--) {
+            p = this.splitter.elements[i];
+            if (p !== panel.domObj) {
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // panel can stay open
+                        bottom -= panelHeight;
+                        space -= panelHeight;
+                        p.resize({top: bottom, height: panelHeight});
+                    } else {
+                        if (space > o.minHeight) {
+                            bottom -= space;
+                            p.resize({top: bottom, height: space});
+                            space = 0;
+                        } else {
+                            bottom -= o.minHeight;
+                            p.resize({top: bottom, height: o.minHeight});
+                        }
+                    }
+                } else {
+                    bottom -= o.minHeight;
+                    p.resize({top: bottom, height: o.minHeight, bottom: null});
+                }
+                bottom -= p.retrieve('leftBar').getBorderBoxSize().height;
+                p.retrieve('leftBar').style.top = bottom + 'px';
+
+            } else {
+                break;
+            }
+        }
+        panel.domObj.resize({top: top, height:panelSize, bottom: null});
+        this.fireEvent('panelMaximize',panel);
+    },
+    
+    createText: function (lang) {
+      this.parent();
+      //barTooltip is handled by the splitter's createText() function
+    }
+});/*
+---
+
+name: Jx.Dialog.Message
+
+description: A subclass of jx.Dialog for displaying messages w/a single OK button.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+
+provides: [Jx.Dialog.Message]
+
+css:
+ - message
+
+...
+ */
+// $Id: message.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog.Message
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Message is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user. It only presents an OK button.
+ * 
+ * MooTools.lang Keys:
+ * - message.okButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Message = new Class({
+    Family: 'Jx.Dialog.Message',
+    Extends: Jx.Dialog,
+    Binds: ['onOk'],
+    options: {
+        /**
+         * Option: message
+         * The message to display to the user
+         */
+        message: '',
+        /**
+         * Option: width
+         * default width of message dialogs is 300px
+         */
+        width: 300,
+        /**
+         * Option: height
+         * default height of message dialogs is 150px
+         */
+        height: 150,
+        /**
+         * Option: close
+         * by default, message dialogs are closable
+         */
+        close: true,
+        /**
+         * Option: resize
+         * by default, message dialogs are resizable
+         */
+        resize: true,
+        /**
+         * Option: collapse
+         * by default, message dialogs are not collapsible
+         */
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * Method: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'message',value:'okButton'}),
+            onClick: this.onOk
+        });
+        this.buttons.add(this.ok);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.message);
+        if (type === 'string' || type == 'object' || type == 'element') {
+            this.question = new Element('div', {
+                'class': 'jxMessage'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.message));
+              break;
+              case 'element':
+                this.options.message.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxMessage');
+        }
+        this.options.content = this.question;
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok = function(ev) { ev.preventDefault(); self.close(); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onOk
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onOk: function () {
+        this.close();
+    },
+    
+    /**
+     * APIMethod: setMessage
+     * set the message of the dialog, useful for responding to language
+     * changes on the fly.
+     *
+     * Parameters
+     * message - {String} the new message
+     */
+    setMessage: function(message) {
+      this.options.message = message;
+      if ($defined(this.question)) {
+        this.question.set('html',this.getText(message));
+      }
+    },
+    
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.ok)) {
+        this.ok.setLabel({set:'Jx',key:'message',value:'okButton'});
+      }
+      if(Jx.type(this.options.message) === 'object') {
+        this.question.set('html', this.getText(this.options.message))
+      }
+    }
+});
+/*
+---
+
+name: Jx.Dialog.Confirm
+
+description: A subclass of Jx.dialog for asking a yes/no type question of the user.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+
+provides: [Jx.Dialog.Confirm]
+
+css:
+ - confirm
+
+...
+ */
+// $Id: confirm.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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.
+ * 
+ * MooTools.lang Keys:
+ * - confirm.affirmitiveLabel
+ * - confirm.negativeLabel
+ * 
+ * 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: '',
+        /**
+         * Jx.Dialog option defaults
+         */
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        },
+        width: 300,
+        height: 150,
+        close: false,
+        resize: true,
+        collapse: false
+    },
+    /**
+     * Reference to MooTools keyboards Class for handling keypress events like Enter or ESC
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * creates the dialog
+     */
+    render: function () {
+        //create content to be added
+        //turn scrolling off as confirm only has 2 buttons.
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll: false});
+
+        // COMMENT: returning boolean would be more what people expect instead of a localized label of a button?
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'affirmativeLabel'}),
+            onClick: this.onClick.bind(this, true)
+        }),
+        this.cancel = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'negativeLabel'}),
+            onClick: this.onClick.bind(this, false)
+        })
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.question);
+        if (type === 'string' || type === 'object' || type == 'element'){
+            this.question = new Element('div', {
+                'class': 'jxConfirmQuestion'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.question));
+              break;
+              case 'element':
+                this.options.question.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxConfirmQuestion');
+        }
+        this.options.content = this.question;
+
+        // add default key functions
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        // add new ones
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * 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]);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'confirm',value:'affirmativeLabel'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'confirm',value:'negativeLabel'});
+    	}
+      if(Jx.type(this.options.question) === 'object') {
+        this.question.set('html', this.getText(this.options.question))
+      }
+    }
+
+});/*
+---
+
+name: Jx.Tooltip
+
+description: 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.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Tooltip]
+
+css:
+ - tooltip
+
+...
+ */
+// $Id: tooltip.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends : Jx.Widget,
+    Binds: ['enter', 'leave', 'move'],
+    options : {
+        /**
+         * Option: offsets
+         * An object with x and y components for where to put the tip related
+         * to the mouse cursor.
+         */
+        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'],
+
+    /**
+     * Method: 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' || Jx.type(this.options.tip) == 'object') {
+            this.domObj.set('html', this.getText(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);
+        this.options.target.addEvent('mouseleave', this.leave);
+        this.options.target.addEvent('mousemove', this.move);
+    },
+
+    /**
+     * Method: enter
+     * Method run when the cursor passes over an element with a tip
+     *
+     * Parameters:
+     * event - the event object
+     */
+    enter : function (event) {
+        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
+     */
+    leave : function (event) {
+        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);
+    },
+    /**
+     * APIMethod: detach
+     * Called to manually remove a tooltip.
+     */
+    detach : function () {
+        this.target.eliminate('Tip');
+        this.destroy();
+    }
+});
+/*
+---
+
+name: Jx.Fieldset
+
+description: Used to create fieldsets in Forms
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Fieldset]
+
+...
+ */
+// $Id: fieldset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Fieldset',
+    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
+    },
+
+    classes: new Hash({
+        domObj: 'jxFieldset',
+        legend: 'jxFieldsetLegend'
+    }),
+
+    /**
+     * 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;
+        }
+
+        //FIELDSET
+        if (this.domObj) {
+            if ($defined(this.options.id)) {
+                this.domObj.set('id', this.options.id);
+            }
+            if ($defined(this.options.fieldsetClass)) {
+                this.domObj.addClass(this.options.fieldsetClass);
+            }
+        }
+
+        if (this.legend) {
+            if ($defined(this.options.legend)) {
+                this.legend.set('html', this.getText(this.options.legend));
+                if ($defined(this.options.legendClass)) {
+                    this.legend.addClass(this.options.legendClass);
+                }
+            } else {
+                this.legend.destroy();
+            }
+        }
+    },
+    /**
+     * APIMethod: add
+     * Adds fields to this fieldset
+     *
+     * Parameters:
+     * pass as many fields to this method as you like. They should 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 ($defined(field.jxFamily) && !$defined(field.form) && $defined(this.form)) {
+                field.form = this.form;
+                this.form.addField(field);
+            }
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: addTo
+     *
+     */
+    addTo: function(what) {
+        if (what instanceof Jx.Form) {
+            this.form = what;
+        } else if (what instanceof Jx.Fieldset) {
+            this.form = what.form;
+        }
+        return this.parent(what);
+    }
+    
+});
+/*
+---
+
+name: Jx.Form
+
+description: Represents a HTML Form
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - More/String.QueryString
+ - More/Form.Validator
+
+provides: [Jx.Form]
+
+css:
+ - form
+
+...
+ */
+// $Id: form.js 1010 2011-01-04 00:09:39Z jonlb at comcast.net $
+/**
+ * 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
+ * through the use of a plugin (Jx.Plugin.Form.Validator).
+ *
+ * Jx.Form has the ability to submit itself via normal HTTP submit as well as
+ * via AJAX. To submit normally you simply call the submit() function. To submit by
+ * AJAX, call ajaxSubmit().  If the form contains Jx.Field.File instances it will
+ * either submit all of the files individually and then the data, or it will submit
+ * data with the last File instance it finds. This behavior is dependant on the
+ * uploadFilesFirst option (which defaults to false).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Form = new Class({
+    Family: 'Jx.Form',
+    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: formClass
+         */
+        formClass: null,
+        /**
+         * Option: name
+         * the name property for the form
+         */
+        name: '',
+        /**
+         * Option: acceptCharset
+         * the character encoding to be used. Defaults to utf-8.
+         */
+        acceptCharset: 'utf-8',
+        /**
+         * Option: uploadFilesFirst
+         * Whether to upload all of the files in the form before
+         * uploading the rest of the form. If set to false the form will
+         * upload the data with the last file that it finds,
+         */
+        uploadFilesFirst: false,
+
+        template: '<form class="jxForm"></form>'
+    },
+    
+    /**
+     * Property: defaultAction
+     * the default field to activate if the user hits the enter key in this
+     * form.  Set by specifying default: true as an option to a field.  Will
+     * only work if the default is a Jx button field or an input of a type
+     * that is a button
+     */
+    defaultAction: null,
+
+    /**
+     * Property: fields
+     * An array of all of the single fields (not contained in a fieldset) for
+     * this form
+     */
+    fields : null,
+    /**
+     * Property: pluginNamespace
+     * required variable for plugins
+     */
+    pluginNamespace: 'Form',
+
+    classes: $H({
+        domObj: 'jxForm'
+    }),
+    
+    init: function() {
+      this.parent();
+      this.fields = new Hash();
+      this.data = {};
+    },
+    /**
+     * 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 () {
+        this.parent();
+        //create the form first
+        this.domObj.set({
+            'method' : this.options.method,
+            'action' : this.options.action,
+            'name' : this.options.name,
+            'accept-charset': this.options.acceptCharset,
+            events: {
+                keypress: function(e) {
+                    if (e.key == 'enter' && 
+                        e.target.tagName != "TEXTAREA" && 
+                        this.defaultAction &&
+                        this.defaultAction.click) {
+                        document.id(this.defaultAction).focus();
+                        this.defaultAction.click();
+                        e.stop();
+                    }
+                }.bind(this)
+            }
+        });
+
+        if (this.options.fileUpload) {
+            this.domObj.set('enctype', 'multipart/form-data');
+        }
+        
+        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);
+        if (field.options.defaultAction) {
+            this.defaultAction = 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) && 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) {
+        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);
+            } else if (field instanceof Jx.Fieldset && !$defined(field.form)) {
+                field.form = this;
+            }
+            
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: reset
+     * Resets all fields back to their original value
+     */
+    reset : function () {
+        this.fields.each(function (field, name) {
+            field.reset();
+        }, this);
+        this.fireEvent('reset',this);
+    },
+    /**
+     * APIMethod: getFieldsByName
+     * Allows retrieving a field from a form by the name of the field (NOT the
+     * ID).
+     *
+     * Parameters:
+     * name - {string} the name of the field to find
+     */
+    getFieldsByName: function (name) {
+        var fields = [];
+        this.fields.each(function(val, id){
+            if (val.name === name) {
+                fields.push(val);
+            }
+        },this);
+        return fields;
+    },
+    /**
+     * APIMethod: getField
+     * Returns a Jx.Field object by its ID.
+     *
+     * Parameters:
+     * id - {string} the id of the field to find.
+     */
+    getField: function (id) {
+        if (this.fields.has(id)) {
+            return this.fields.get(id);
+        } 
+        return null;
+    },
+    /**
+     * APIMethod: setBusy
+     * Sets the busy state of the Form and all of it's fields.
+     *
+     * Parameters:
+     * state - {boolean} indicated whether the form is busy or not.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.parent(state);
+      this.fields.each(function(field) {
+        field.setBusy(state, true);
+      });
+    },
+
+    submit: function() {
+        //are there any files in this form?
+        var opts = this.options;
+        if (opts.fileUpload) {
+            //grab all of the files and pull them into the main domObj
+            var files = this.findFiles();
+            files.each(function(file){
+                var inputs = file.getFileInputs();
+                if (inputs.length > 1) {
+                    //we need to make these an array...
+                    inputs.each(function(input){
+                        input.set('name',input.get('name') + '[]');
+                    },this);
+                }
+                file.destroy();
+                this.domObj.adopt(inputs);
+            },this);
+        }
+        this.domObj.submit();
+    },
+
+    ajaxSubmit: function() {
+        var opts = this.options;
+        if (opts.fileUpload) {
+            var files = this.findFiles();
+            this.files = files.length;
+            this.completed = 0;
+            files.each(function(file, index){
+                file.addEvent('onFileUploadComplete',this.fileUploadComplete.bind(this));
+                if (index==(this.files - 1) && !opts.uploadFilesFirst) {
+                    file.upload(this);
+                } else {
+                    file.upload();
+                }
+            },this);
+        } else {
+            this.submitForm();
+        }
+    },
+
+    submitForm: function() {
+        //otherwise if no file field(s) present, just get the values and
+        //submit to the action via the method
+        var data = this.getValues();
+        var req = new Request.JSON({
+            url: this.action,
+            method: this.method,
+            data: data,
+            urlEncoded: true,
+            onSuccess: function(responseJSON, responseText) {
+                this.fileUploadComplete(responseJSON, true);
+            }.bind(this)
+        });
+        req.send();
+    },
+
+    findFiles: function() {
+        var files = [];
+        this.fields.each(function(field){
+            if (field instanceof Jx.Field.File) {
+                files.push(field);
+            }
+        },this);
+        return files;
+    },
+
+    fileUploadComplete: function(data){
+        this.completed++;
+        $each(data,function(value,key){
+            this.data[key] = value;
+        },this);
+        if (this.completed == this.files && this.options.uploadFilesFirst) {
+            this.submitForm();
+        } else {
+            this.fireEvent('formSubmitComplete',[this.data]);
+        }
+    }
+
+});
+/*
+---
+
+name: Jx.Field
+
+description: Base class for all inputs
+
+license: MIT-style license.
+
+requires:
+ - Jx.Fieldset
+ - Jx.Form
+
+provides: [Jx.Field]
+
+
+...
+ */
+// $Id: field.js 969 2010-08-20 12:14:54Z pagameba $
+/**
+ * Class: Jx.Field
+ *
+ * Extends: <Jx.Widget>
+ *
+ * This class is the base class for all form fields.
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - field.requiredText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field = new Class({
+    Family: 'Jx.Field',
+    Extends : Jx.Widget,
+    pluginNamespace: 'Field',
+    Binds: ['changeText'],
+    
+    options : {
+        /**
+         * Option: id
+         * The ID assigned to the container of the Jx.Field element, this is
+         * not the id of the input element (which is internally computed to be
+         * unique)
+         */
+        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 provided
+         * that the plugin Jx.Plugin.Field.Validator has been added to this
+         * field.
+         */
+        required : false,
+        /**
+         * 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,
+        /**
+         * Option: defaultAction
+         * {Boolean} defaults to false, if true and this field is a button
+         * of some kind (Jx.Button, a button or an input of type submit) then
+         * if the user hits the enter key on any field in the form except a
+         * textarea, this field will be activated as if clicked
+         */
+        defaultAction: 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
+     * A computed, unique id attached to the input element 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 : new Hash({
+        domObj: 'jxInputContainer',
+        label: 'jxInputLabel',
+        tag: 'jxInputTag'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render : function () {
+        this.classes.set('field', 'jxInput'+this.type);
+        var name = $defined(this.options.name) ? this.options.name : '';
+        this.options.template = this.options.template.substitute({name:name});
+        this.parent();
+
+        this.id = this.generateId();
+        this.name = this.options.name;
+
+        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';
+            }
+        }
+
+
+        // FIELD
+        if (this.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 events
+            this.field.addEvents({
+              'focus': this.onFocus.bind(this),
+              'blur': this.onBlur.bind(this),
+              'change': this.onChange.bind(this)
+            });
+
+            this.field.store('field', this);
+
+            // add click event to label to set the focus to the field
+            // COMMENT: tried it without a function using addEvent('click', this.field.focus.bind(this)) but crashed in IE
+            if(this.label) {
+              this.label.addEvent('click', function() {
+                this.field.focus();
+              }.bind(this));
+            }
+        }
+        // LABEL
+        if (this.label) {
+            if ($defined(this.options.labelClass)) {
+                this.label.addClass(this.options.labelClass);
+            }
+            if ($defined(this.options.label)) {
+                this.label.set('html', this.getText(this.options.label)
+                        + this.options.labelSeparator);
+            }
+
+            this.label.set('for', this.id);
+
+            if (this.options.required) {
+                this.requiredText = new Element('em', {
+                    'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+                    'class' : 'required'
+                });
+                this.requiredText.inject(this.label);
+            }
+
+        }
+
+        // TAG
+        if (this.tag) {
+            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) {
+        if (!this.options.readonly) {
+            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.setValue(this.options.value);
+        this.fireEvent('reset', this);
+    },
+    /**
+     * APIMethod: disable
+     * Disabled the field
+     */
+    disable : function () {
+        this.options.disabled = true;
+        this.field.set("disabled", "disabled");
+        this.field.addClass('jxFieldDisabled');
+    },
+    /**
+     * APIMethod: enable
+     * Enables the field
+     */
+    enable : function () {
+        this.options.disabled = false;
+        this.field.erase("disabled");
+        this.field.removeClass('jxFieldDisabled');
+    },
+    
+    /**
+     * APIMethod: addTo
+     * Overrides default Jx.Widget AddTo() so that we can call .add() if
+     * adding to a Jx.Form or Jx.Fieldset object.
+     *
+     * Parameters:
+     * what - the element or object to add this field to.
+     * where - where in the object to place it. Not valid if adding to Jx.Form
+     *      or Jx.Fieldset.
+     */
+    addTo: function(what, where) {
+        if (what instanceof Jx.Fieldset || what instanceof Jx.Form) {
+            what.add(this);
+        } else {
+            this.parent(what, where);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        this.parent();
+        if ($defined(this.options.label) && this.label) {
+          this.label.set('html', this.getText(this.options.label) + this.options.labelSeparator);
+        }
+        if(this.options.required) {
+          this.requiredText = new Element('em', {
+              'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+              'class' : 'required'
+          });
+          this.requiredText.inject(this.label);
+        }
+        if ($defined(this.requiredText)) {
+          this.requiredText.set('html',this.getText({set:'Jx',key:'field',value:'requiredText'}));
+        }
+    }, 
+    
+    onFocus: function() {
+      this.fireEvent('focus', this);
+    },
+    
+    onBlur: function () {
+      this.fireEvent('blur',this);
+    },
+    
+    onChange: function () {
+      this.fireEvent('change', this);
+    },
+    
+    setBusy: function(state, withoutMask) {
+      if (!withoutMask) {
+        this.parent(state);
+      }
+      this.field.set('readonly', state || this.options.readonly);
+    }
+
+});
+/*
+---
+
+name: Jx.Field.Text
+
+description: Represents a text input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+optional:
+ - More/OverText
+
+provides: [Jx.Field.Text]
+
+...
+ */
+// $Id: text.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span></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();
+        }
+
+    }
+
+});/*
+---
+
+name: Jx.Dialog.Prompt
+
+description: A subclass of Jx.dialog for prompting the user for text input.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+ - Jx.Field.Text
+
+provides: [Jx.Dialog.Prompt]
+
+...
+ */
+// $Id: prompt.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog.Prompt
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Prompt is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user and ask for a text response. 
+ * 
+ * MooTools.lang Keys:
+ * - prompt.okButton
+ * - prompt.cancelButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Prompt = new Class({
+
+    Extends: Jx.Dialog,
+
+    options: {
+        /**
+         * Option: prompt
+         * The message to display to the user
+         */
+        prompt: '',
+        /**
+         * Option: startingValue
+         * The startingvalue to place in the input field
+         */
+        startingValue: '',
+        /**
+         * Option: fieldOptions,
+         * Object with various
+         */
+        fieldOptions: {
+          type : 'Text',
+          options: {},
+          validate : true,
+          validatorOptions: {
+            validators: ['required'],
+            validateOnBlur: true,
+            validateOnChange : false
+          },
+          showErrorMsg : true
+        },
+        /**
+         * Jx.Dialog option defaults
+         */
+        width: 400,
+        height: 200,
+        close: true,
+        resize: true,
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * APIMethod: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'okButton'}),
+                onClick: this.onClick.bind(this, true)
+            });
+        this.cancel = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'cancelButton'}),
+                onClick: this.onClick.bind(this, false)
+            });
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+
+        var fOpts = this.options.fieldOptions;
+            fOpts.options.label = this.getText(this.options.prompt);
+            fOpts.options.value = this.options.startingValue;
+            fOpts.options.containerClass = 'jxPrompt';
+
+        if(Jx.type(fOpts.type) === 'string' && $defined(Jx.Field[fOpts.type.capitalize()])) {
+          this.field = new Jx.Field[fOpts.type.capitalize()](fOpts.options);
+        }else if(Jx.type(fOpts.type) === 'Jx.Object'){
+          this.field = fOpts.type;
+        }else{
+          // warning and fallback?
+          window.console ? console.warn("Field type does not exist %o, using Jx.Field.Text", fOpts.type) : false;
+          this.field = new Jx.Field.Text(fOpts.options);
+        }
+
+        if(this.options.fieldOptions.validate) {
+          this.validator = new Jx.Plugin.Field.Validator(this.options.fieldOptions.validatorOptions);
+          this.validator.attach(this.field);
+        }
+
+        this.options.content = document.id(this.field);
+        
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onClick
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onClick: function (value) {
+        if(value && $defined(this.validator)) {
+          if(this.validator.isValid()) {
+            this.isOpening = false;
+            this.hide();
+            this.fireEvent('close', [this, value, this.field.getValue()]);
+          }else{
+            //this.options.content.adopt(this.validator.getError());
+            this.field.field.focus.delay(50, this.field.field);
+            //todo: show error messages ?
+          }
+        }else{
+          this.isOpening = false;
+          this.hide();
+          this.fireEvent('close', [this, value, this.field.getValue()]);
+        }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'prompt',value:'okButton'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'prompt',value:'cancelButton'});
+    	}
+      this.field.label.set('html', this.getText(this.options.prompt));
+    }
+
+
+});
+/*
+---
+
+name: Jx.Panel.DataView
+
+description: A panel used for displaying records from a store in a list-style interface rather than a grid.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - Jx.Store
+ - Jx.List
+
+provides: [Jx.Panel.DataView]
+
+...
+ */
+// $Id: dataview.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Panel.DataView
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This panel extension takes a standard Jx.Store (or subclass) and displays
+ * each record as an item using a provided template. It sorts the store as requested
+ * before doing so. The class only creates the HTML and has no default CSS display. All
+ * styling must be done by the developer using the control.
+ *
+ *
+ * Events:
+ * renderDone - fires when the panel completes creating all of the items.
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.DataView = new Class({
+
+    Extends: Jx.Panel,
+
+    options: {
+        /**
+         * Option: data
+         * The store containing the data
+         */
+        data: null,
+        /**
+         * Option: sortColumns
+         * An array of columns to sort the store by.
+         */
+        sortColumns: null,
+        /**
+         * Option: itemTemplate
+         * The template to use in rendering records
+         */
+        itemTemplate: null,
+        /**
+         * Option: emptyTemplate
+         * the template that is displayed when there are no records in the
+         * store.
+         */
+        emptyTemplate: null,
+        /**
+         * Option: containerClass
+         * The class added to the container. It can be used to target the items
+         * in the panel.
+         */
+        containerClass: null,
+        /**
+         * Option: itemClass
+         * The class to add to each item. Used for styling purposes
+         */
+        itemClass: null,
+        /**
+         * Option: itemOptions
+         * Options to pass to the list object
+         */
+        listOptions: {
+            select: true,
+            hover: true
+        }
+    },
+
+    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('storeDataLoaded', this.bound.update);
+        this.options.data.addEvent('storeSortFinished', this.bound.update);
+        this.options.data.addEvent('storeDataLoadFailed', 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));
+    }
+});
+/*
+---
+
+name: Jx.Panel.DataView.Group
+
+description: A subclass of Dataview that can display records in groups.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel.DataView
+ - Jx.Selection
+
+provides: [Jx.Panel.DataView.Group]
+
+...
+ */
+// $Id: group.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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);
+    }
+
+});
+/*
+---
+
+name: Jx.ListItem
+
+description: Represents a single item in a listview.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.ListItem]
+
+...
+ */
+// $Id: listitem.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.ListItem',
+    Extends: Jx.Widget,
+
+    options: {
+        enabled: true,
+        template: '<li class="jxListItemContainer jxListItem"></li>'
+    },
+
+    classes: new Hash({
+        domObj: 'jxListItemContainer',
+        domContent: 'jxListItem'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render: function () {
+        this.parent();
+        this.domContent.store('jxListItem', this);
+        this.domObj.store('jxListTarget', this.domContent);
+        this.loadContent(this.domContent);
+    },
+
+    enable: function(state) {
+
+    }
+});/*
+---
+
+name: Jx.ListView
+
+description: A widget that displays items in a list format.
+
+license: MIT-style license.
+
+requires:
+ - Jx.List
+ - Jx.ListItem
+
+provides: [Jx.ListView]
+
+css:
+ - list
+
+images:
+ - listitem.png
+...
+ */
+// $Id: listview.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends: Jx.Widget,
+
+    pluginNamespace: 'ListView',
+
+    options: {
+        template: '<ul class="jxListView jxList"></ul>',
+        /**
+         * Option: listOptions
+         * control the behaviour of the list, see <Jx.List>
+         */
+        listOptions: {
+            hover: true,
+            press: true,
+            select: true
+        }
+    },
+
+    classes: new Hash({
+        domObj: 'jxListView',
+        listObj: 'jxList'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    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;
+        }
+
+        this.list = new Jx.List(this.listObj, this.options.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;
+    },
+
+    empty: function () {
+        this.list.empty();
+        return this;
+    }
+});/*
+---
+
+name: Jx.Field.Hidden
+
+description: Represents a hidden input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Hidden]
+
+...
+ */
+// $Id: hidden.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputHidden" type="hidden" name="{name}"/></span>'
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Hidden'
+
+});
+
+
+
+
+/*
+---
+
+name: Jx.Field.File
+
+description: Represents a file input w/upload and progress tracking capabilities (requires APC for progress)
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field.Text
+ - Jx.Button
+ - Core/Request.JSON
+ - Jx.Field.Hidden
+ - Jx.Form
+
+provides: [Jx.Field.File]
+
+css:
+ - file
+
+
+...
+ */
+/**
+ * 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.
+ *
+ * MooTools.lang Keys:
+ * - file.browseLabel
+ * 
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="jxInputTag"></span></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,
+        /**
+         * Option: mode
+         * determines whether this file field acts in single upload mode or
+         * multiple file upload mode. The multiple upload mode was done to work with
+         * Jx.Panel.FileUpload. When in multiple mode, this field will remove the actual
+         * file control after a file is selected, fires an event to signify the selection but will
+         * hold on to the files until told to upload them. Obviously 'multiple' mode isn't designed
+         * to be used when the control is outside of the Upload Panel (unless the user designs
+         * their own upload panel around it).
+         */
+        mode: 'single'
+
+    },
+    /**
+     * Property: type
+     * The Field type used in rendering
+     */
+    type: 'File',
+    /**
+     * Property: forms
+     * holds all form references when we're in multiple mode
+     */
+    forms: null,
+
+    init: function () {
+        this.parent();
+
+        this.forms = new Hash();
+        //create the iframe
+        //we use the same iFrame for each so we don't have to recreate it each time
+        this.isIFrameSetup = true;
+        this.iframe = new Element('iframe', {
+          name: this.generateId(),
+          styles: {
+            'display':'none',
+            'visibility':'hidden'
+          }
+        });
+        // this.iframe = new IFrame(null, {
+        //     styles: {
+        //         'display':'none',
+        //         'visibility':'hidden'
+        //     },
+        //     name : this.generateId()
+        // });
+        this.iframe.inject(document.body);
+        this.iframe.addEvent('load', this.processIFrameUpload.bind(this));
+
+    },
+
+    /**
+     * 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 : '<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'
+        });
+        this.browseButton = new Jx.Button({
+            label: this.getText({set:'Jx',key:'file',value:'browseLabel'})
+        });
+
+        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.options.mode=='single' && this.field.value !== '' && (this.text.field.value !== this.field.value)) {
+            this.text.field.value = this.field.value;
+            this.fireEvent('fileSelected', this);
+            this.forms.set(this.field.value, this.prepForm());
+            if (this.options.autoUpload) {
+                this.uploadSingle();
+            }
+        } else if (this.options.mode=='multiple') {
+            var filename = this.field.value;
+            var form = this.prepForm();
+            this.forms.set(filename, form);
+            this.text.setValue('');
+            //fire the selected event.
+            this.fireEvent('fileSelected', filename);
+        }
+    },
+    /**
+     * 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');
+    },
+
+    prepForm: function () {
+        //load in the form
+        var form = new Jx.Form({
+            action : this.options.handlerUrl,
+            name : 'jxUploadForm',
+            fileUpload: true
+        });
+
+        //move the form input into it (cloneNode)
+        var parent = document.id(this.field.getParent());
+        var sibling = document.id(this.field).getPrevious();
+        var clone = this.field.clone().cloneEvents(this.field);
+        document.id(form).grab(this.field);
+        // tried clone.replaces(this.field) but it didn't seem to work
+        if (sibling) {
+          clone.inject(sibling, 'after');
+        } else if (parent) {
+            clone.inject(parent, 'top');
+        }
+        this.field = clone;
+
+        this.mouseLeave();
+
+        return form;
+    },
+
+    upload: function (externalForm) {
+        //do we have files to upload?
+        if (this.forms.getLength() > 0) {
+            var keys = this.forms.getKeys();
+            this.currentKey = keys[0];
+            var form = this.forms.get(this.currentKey);
+            this.forms.erase(this.currentKey);
+            if ($defined(externalForm) && this.forms.getLength() == 0) {
+                var fields = externalForm.fields;
+                fields.each(function(field){
+                    if (!(field instanceof Jx.Field.File)) {
+                        document.id(field).clone().inject(form);
+                    }
+                },this);
+            }
+            this.uploadSingle(form);
+        } else {
+            //fire finished event...
+            this.fireEvent('allUploadsComplete', this);
+        }
+    },
+    /**
+     * APIMethod: uploadSingle
+     * Call this to upload the file to the server
+     */
+    uploadSingle: function (form) {
+        this.form = $defined(form) ? form : this.prepForm();
+
+        this.fireEvent('fileUploadBegin', [this.currentKey, this]);
+
+        this.text.setValue('');
+
+        document.id(this.form).set('target', this.iframe.get('name')).setStyles({
+            visibility: 'hidden',
+            display: 'none'
+        }).inject(document.body);
+
+        //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');
+        }
+
+        //submit the form
+        document.id(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('fileUploadProgress', [data, this.currentKey, this]);
+            if (data.current < data.total) {
+                this.polling = true;
+                this.pollUpload();
+            } else {
+                this.polling = false;
+            }
+        }
+    },
+    /**
+     * Method: uploadFailure
+     * called if there is a problem getting progress on the upload
+     */
+    uploadFailure: function (xhr) {
+        this.fireEvent('fileUploadProgressError', [this, xhr]);
+    },
+    /**
+     * 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
+        if (this.isIFrameSetup) {
+            if (this.iframe.contentDocument  && this.iframe.contentDocument.defaultView) {
+              var iframeBody = this.iframe.contentDocument.defaultView.document.body.innerHTML;
+            } else {
+              // seems to be needed for ie7
+              var iframeBody = this.iframe.contentWindow.document.body.innerHTML;
+            }
+
+            var data = JSON.decode(iframeBody);
+            if ($defined(data) && $defined(data.success) && data.success) {
+                this.done = true;
+                this.doneData = data;
+                this.uploadCleanUp();
+                this.fireEvent('fileUploadComplete', [data, this.currentKey, this]);
+            } else {
+                this.fireEvent('fileUploadError', [data , this.currentKey, this]);
+            }
+
+            if (this.options.mode == 'multiple') {
+                this.upload();
+            } else {
+                this.fireEvent('allUploadsComplete', 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();
+        }
+    },
+    /**
+     * APIMethod: remove
+     * Removes a file from the hash of forms to upload.
+     *
+     * Parameters:
+     * filename - the filename indicating which file to remove.
+     */
+    remove: function (filename) {
+        if (this.forms.has(filename)) {
+            this.forms.erase(filename);
+        }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.browseButton)) {
+    		this.browseButton.setLabel( this.getText({set:'Jx',key:'file',value:'browseLabel'}) );
+    	}
+    },
+    
+    /**
+     * APIMethod: getFileInputs
+     * Used to get an array of all of the basic file inputs. This is mainly 
+     * here for use by Jx.Form to be able to suck in the file inputs
+     * before a standard submit.
+     * 
+     */
+    getFileInputs: function () {
+        var inputs = [];
+        this.forms.each(function(form){
+            var input = document.id(form).getElement('input[type=file]');
+            inputs.push(input);
+        },this);
+        return inputs;
+    }
+});/*
+---
+
+name: Jx.Progressbar
+
+description: A css-based progress bar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Core/Fx.Tween
+
+provides: [Jx.Progressbar]
+
+css:
+ - progressbar
+
+images:
+ - progressbar.png
+
+...
+ */
+/**
+ * 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
+ * 
+ * MooTools.lang keys:
+ * - progressbar.messageText
+ * - progressbar.progressText
+ *
+ * Copyright (c) 2010 by Jonathan Bomgardner
+ * Licensed under an mit-style license
+ */
+Jx.Progressbar = new Class({
+    Family: 'Jx.Progressbar',
+    Extends: Jx.Widget,
+    
+    options: {
+        onUpdate: $empty,
+        onComplete: $empty,
+        /**
+         * Option: parent
+         * The element to put this progressbar into
+         */
+        parent: null,
+        /**
+         * Option: progressText
+         * Text to show while processing, uses 
+         * {progress} von {total}
+         */
+        progressText : null,
+        /**
+         * Option: template
+         * The template used to create the progressbar
+         */
+        template: '<div class="jxProgressBar-container"><div class="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div></div>'
+    },
+    /**
+     * Property: classes
+     * The classes used in the template
+     */
+    classes: new Hash({
+        domObj: 'jxProgressBar-container',
+        message: 'jxProgressBar-message', 
+        container: 'jxProgressBar',
+        outline: 'jxProgressBar-outline',
+        fill: 'jxProgressBar-fill',
+        text: '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.parent();
+        
+        if ($defined(this.options.parent)) {
+            this.domObj.inject(document.id(this.options.parent));
+        }
+        
+        this.domObj.addClass('jxProgressStarting');
+
+        //we need to know the width of the bar
+        this.width = document.id(this.domObj).getContentBoxSize().width;
+        
+        //Message
+        if (this.message) {
+            if ($defined(MooTools.lang.get('Jx','progressbar').messageText)) {
+                this.message.set('html', this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+            } else {
+                this.message.destroy();
+            }
+        }
+
+        //Fill
+        if (this.fill) {
+            this.fill.setStyles({
+                'width': 0
+            });
+        }
+        
+        //TODO: check for {progress} and {total} in progressText
+        var obj = {};
+        var progressText = this.options.progressText == null ? 
+                              this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                              this.getText(this.options.progressText);
+        if (progressText.contains('{progress}')) {
+            obj.progress = 0;
+        }
+        if (progressText.contains('{total}')) {
+            obj.total = 0;
+        }
+        
+        //Progress text
+        if (this.text) {
+            this.text.set('html', 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) {
+    	
+    	//check for starting class
+    	if (this.domObj.hasClass('jxProgressStarting')) {
+    		this.domObj.removeClass('jxProgressStarting').addClass('jxProgressWorking');
+    	}
+    	
+        var newWidth = (progress * this.width) / total;
+        
+        //update bar width
+        this.text.get('tween', {property:'width', onComplete: function() {
+            var obj = {};
+            var progressText = this.options.progressText == null ?
+                                  this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                                  this.getText(this.options.progressText);
+            if (progressText.contains('{progress}')) {
+                obj.progress = progress;
+            }
+            if (progressText.contains('{total}')) {
+                obj.total = total;
+            }
+            var t = progressText.substitute(obj);
+            this.text.set('text', t);
+        }.bind(this)}).start(newWidth);
+        
+        this.fill.get('tween', {property: 'width', onComplete: (function () {
+            
+            if (total === progress) {
+                this.complete = true;
+                this.domObj.removeClass('jxProgressWorking').addClass('jxProgressFinished');
+                this.fireEvent('complete');
+            } else {
+                this.fireEvent('update');
+            }
+        }).bind(this)}).start(newWidth);
+        
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.message) {
+    		this.message.set('html',this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+    	}
+        //progress text will update on next update.
+    }
+    
+});/*
+---
+
+name: Jx.Panel.FileUpload
+
+description: A panel subclass that is designed to be a multiple file upload panel with a queue listing.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - Jx.ListView
+ - Jx.Field.File
+ - Jx.Progressbar
+ - Jx.Button
+ - Jx.Toolbar.Item
+ - Jx.Tooltip
+
+provides: [Jx.Panel.FileUpload]
+
+css:
+ - upload
+
+images:
+ - icons.png
+...
+ */
+// $Id: upload.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * Class: Jx.Panel.FileUpload
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This class extends Jx.Panel to provide a consistent interface for uploading
+ * files in an application.
+ * 
+ * MooTools.lang Keys:
+ * - upload.buttonText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.FileUpload = new Class({
+
+    Family: 'Jx.Panel.FileUpload',
+    Extends: Jx.Panel,
+    Binds: ['moveToQueue','fileUploadBegin', 'fileUploadComplete','allUploadsComplete', 'fileUploadProgressError,', 'fileUploadError', 'fileUploadProgress'],
+
+    options: {
+        /**
+         * Option: file
+         * An object containing the options for Jx.Field.File
+         */
+        file: {
+            autoUpload: false,
+            progress: false,
+            progressIDUrl: '',
+            handlerUrl: '',
+            progressUrl: ''
+        },
+
+        progressOptions: {
+            template: "<li class='jxListItemContainer jxProgressBar-container' id='{id}'><div class='jxProgressBar'><div class='jxProgressBar-outline'></div><div class='jxProgressBar-fill'></div><div class='jxProgressBar-text'></div></div></li>",
+            containerClass: 'progress-container',
+            messageText: null,
+            messageClass: 'progress-message',
+            progressText: 'Uploading {filename}',
+            progressClass: 'progress-bar'
+        },
+        /**
+         * Option: onFileComplete
+         * 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: 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: [],
+
+    listTemplate: "<li class='jxListItemContainer' id='{id}'><a class='jxListItem' href='javascript:void(0);'><span class='itemLabel jxUploadFileName'>{name}</span><span class='jxUploadFileDelete' title='Remove this file from the queue.'></span></a></li>",
+    /**
+     * Method: 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="jxInputContainer jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';
+
+        this.file = new Jx.Field.File(this.fileOpt);
+        this.file.addEvent('fileSelected', this.moveToQueue);
+        this.file.addTo(this.domObjA);
+
+        this.listView = new Jx.ListView({
+            template: '<ul class="jxListView jxList jxUploadQueue"></ul>'
+            
+        }).addTo(this.domObjA);
+
+        if (!this.options.file.autoUpload) {
+            //this is the upload button at the bottom of the panel.
+            this.uploadBtn = new Jx.Button({
+                label : this.getText({set:'Jx',key:'upload',value:'buttonText'}),
+                onClick: this.upload.bind(this)
+            });
+            var tlb = new Jx.Toolbar({position: 'bottom', scroll: false}).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 (filename) {
+        var theTemplate = new String(this.listTemplate).substitute({
+            name: filename,
+            id: filename
+        });
+        var item = new Jx.ListItem({template:theTemplate, enabled: true});
+
+        $(item).getElement('.jxUploadFileDelete').addEvent('click', function(){
+            this.listView.remove(item);
+            this.file.remove(filename);
+            if (this.listView.list.count() == 0) {
+                this.uploadBtn.setEnabled(false);
+            }
+        }.bind(this));
+        this.listView.add(item);
+
+        if (!this.uploadBtn.isEnabled()) {
+            this.uploadBtn.setEnabled(true);
+        }
+
+    },
+    /**
+     * Method: upload
+     * Called when the user clicks the upload button. Runs the upload process.
+     */
+    upload: function () {
+
+        this.file.addEvents({
+            'fileUploadBegin': this.fileUploadBegin ,
+            'fileUploadComplete': this.fileUploadComplete,
+            'allUploadsComplete': this.allUploadsComplete,
+            'fileUploadError': this.fileUploadError,
+            'fileUploadProgress': this.fileUploadProgress,
+            'fileUploadProgressError': this.fileUploadError
+        });
+
+
+        this.file.upload();
+    },
+
+    fileUploadBegin: function (filename) {
+        if (this.options.file.progress) {
+            //progressbar
+            //setup options
+            // TODO: should (at least some of) these options be available to
+            // the developer?
+            var options = $merge({},this.options.progressOptions);
+            options.progressText = options.progressText.substitute({filename: filename});
+            options.template = options.template.substitute({id: filename});
+            this.pb = new Jx.Progressbar(options);
+            var item = document.id(filename);
+            this.oldContents = item;
+            this.listView.replace(item,$(this.pb));
+        } else {
+            var icon = document.id(filename).getElement('.jxUploadFileDelete')
+            icon.addClass('jxUploadFileProgress').set('title','File Uploading...');
+        }
+    },
+
+    /**
+     * Method: fileUploadComplete
+     * Called when a single file is uploaded completely .
+     *
+     * Parameters:
+     * data - the data returned from the event
+     * filename - the filename of 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, filename) {
+
+        if (this.options.file.progress) {
+            //show this old contents...
+            this.listView.replace(document.id(filename),this.oldContents);
+        }
+        var icon = document.id(filename).getElement('.jxUploadFileDelete');
+        icon.erase('title');
+        if (icon.hasClass('jxUploadFileProgress')) {
+            icon.removeClass('jxUploadFileProgress').addClass('jxUploadFileError');
+        } else {
+            icon.addClass('jxUploadFileError');
+        }
+        if ($defined(data.error) && $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 (filename) {
+
+        if (this.options.removeOnComplete) {
+           this.listView.remove(document.id(filename));
+        } else {
+            if (this.options.file.progress) {
+                this.listView.replace(document.id(filename),this.oldContents);
+            }
+            var l = document.id(filename).getElement('.jxUploadFileDelete');
+            if (l.hasClass('jxUploadFileDelete')) {
+                l.addClass('jxUploadFileComplete');
+            } else if (l.hasClass('jxUploadFileProgress')) {
+                l.removeClass('jxUploadFileProgress').addClass('jxUploadFileComplete');
+            }
+        }
+
+        this.fireEvent('fileUploadComplete', filename);
+    },
+    /**
+     * 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) {
+        if (this.options.progress) {
+            this.pb.update(data.total, data.current);
+        }
+    },
+    /**
+     * Method: allUploadCompleted
+     * Called when the Jx.Field.File completes uploading
+     * all files. Sets upload button to disabled and fires the allUploadCompleted
+     * event.
+     */
+    allUploadsComplete: function () {
+        this.uploadBtn.setEnabled(false);
+        this.fireEvent('allUploadsCompleted',this);
+    },
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.uploadBtn)) {
+        this.uploadBtn.setLabel({set:'Jx',key:'upload',value:'buttonText'});
+      }
+    }
+});
+/*
+---
+
+name: Jx.Column
+
+description: A representation of a single grid column
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Column]
+
+...
+ */
+// $Id: column.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * 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({
+
+    Family: 'Jx.Column',
+    Extends: Jx.Widget,
+
+    options: {
+        /**
+         * Option: renderMode
+         * The mode to use in rendering this column to determine its width.
+         * Valid options include
+         *
+         * fit - auto calculates the width for the best fit to the text. This
+         *      is the default.
+         * fixed - uses the value passed in the width option, doesn't
+         *      auto calculate.
+         * expand - uses the value in the width option as a minimum width and
+         *      allows this column to expand and take up any leftover space.
+         *      NOTE: there can be only 1 expand column in a grid. The
+         *      Jx.Columns object will only take the first column with this
+         *      option as the expanding column. All remaining columns marked
+         *      "expand" will be treated as "fixed".
+         */
+        renderMode: 'fixed',
+        /**
+         * Option: width
+         * Determines the width of the column when using 'fixed' rendering mode
+         * or acts as a minimum width when using 'expand' mode.
+         */
+        width: 100,
+
+        /**
+         * 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: name
+         * The name given to this column
+         */
+        name: '',
+
+        /**
+         * Option: template
+         */
+        template: null,
+        /**
+         * Option: renderer
+         * an instance of a Jx.Grid.Renderer to use in rendering the content
+         * of this column or a config object for creating one like so:
+         *
+         * (code)
+         * {
+         *     name: 'Text',
+         *     options: { ... renderer options ... }
+         * }
+         */
+        renderer: null
+    },
+
+    classes: $H({
+      domObj: 'jxGridCellContent'
+    }),
+
+    /**
+     * Property: grid
+     * holds a reference to the grid (an instance of <Jx.Grid>)
+     */
+    grid: null,
+
+    parameters: ['options','grid'],
+
+    /**
+     * Constructor: Jx.Column
+     * initializes the column object
+     */
+    init : function () {
+
+        this.name = this.options.name;
+
+        //adjust header for column
+        if (!$defined(this.options.template)) {
+            this.options.template = '<span class="jxGridCellContent">' + this.name.capitalize() + '</span>';
+        }
+
+        this.parent();
+        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
+            this.grid = this.options.grid;
+        }
+
+        //check renderer
+        if (!$defined(this.options.renderer)) {
+            //set a default renderer
+            this.options.renderer = new Jx.Grid.Renderer.Text({
+                textTemplate: '{' + this.name + '}'
+            });
+        } else {
+            if (!(this.options.renderer instanceof Jx.Grid.Renderer)) {
+                var t = Jx.type(this.options.renderer);
+                if (t === 'object') {
+                    if(!$defined(this.options.renderer.options.textTemplate)) {
+                      this.options.renderer.options.textTemplate = '{' + this.name + '}';
+                    }
+                    if(!$defined(this.options.renderer.name)) {
+                      this.options.renderer.name = 'Text';
+                    }
+                    this.options.renderer = new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](
+                            this.options.renderer.options);
+                }
+            }
+        }
+
+        this.options.renderer.setColumn(this);
+    },
+
+    getTemplate: function(idx) {
+      return "<span class='jxGridCellContent' title='{col"+idx+"}'>{col"+idx+"}</span>";
+    },
+
+    /**
+     * APIMethod: getHeaderHTML
+     */
+    getHeaderHTML : function () {
+      if (this.isSortable() && !this.sortImage) {
+        this.sortImage = new Element('img', {
+            src: Jx.aPixel.src
+        });
+        this.sortImage.inject(this.domObj);
+      } else {
+        if (!this.isSortable() && this.sortImage) {
+          this.sortImage.dispose();
+          this.sortImage = null;
+        }
+      }
+      return this.domObj;
+    },
+
+    setWidth: function(newWidth, asCellWidth) {
+        asCellWidth = $defined(asCellWidth) ? asCellWidth : false;
+
+        var delta = this.cellWidth - this.width;
+        if (!asCellWidth) {
+          this.width = parseInt(newWidth,10);
+          this.cellWidth = this.width + delta;
+          this.options.width = newWidth;
+        } else {
+            this.width = parseInt(newWidth,10) - delta;
+            this.cellWidth = newWidth;
+            this.options.width = this.width;
+        }
+      if (this.rule && parseInt(this.width,10) >= 0) {
+          this.rule.style.width = parseInt(this.width,10) + "px";
+      }
+      if (this.cellRule && parseInt(this.cellWidth,10) >= 0) {
+          this.cellRule.style.width = parseInt(this.cellWidth,10) + "px";
+      }
+    },
+
+    /**
+     * APIMethod: getWidth
+     * return the width of the column
+     */
+    getWidth: function () {
+      return this.width;
+    },
+
+    /**
+     * APIMethod: getCellWidth
+     * return the cellWidth of the column
+     */
+    getCellWidth: function() {
+      return this.cellWidth;
+    },
+
+    /**
+     * APIMethod: calculateWidth
+     * returns the width of the column.
+     *
+     * Parameters:
+     * rowHeader - flag to tell us if this calculation is for the row header
+     */
+    calculateWidth : function (rowHeader) {
+        //if this gets called then we assume that we want to calculate the width
+      rowHeader = $defined(rowHeader) ? rowHeader : false;
+      var maxWidth,
+          maxCellWidth,
+          store = this.grid.getStore(),
+          t,
+          s,
+          oldPos,
+          text,
+          klass;
+      store.first();
+      if ((this.options.renderMode == 'fixed' ||
+           this.options.renderMode == 'expand') &&
+          store.valid()) {
+        t = new Element('span', {
+          'class': 'jxGridCellContent',
+          html: 'a',
+          styles: {
+            width: this.options.width
+          }
+        });
+        s = this.measure(t,'jxGridCell');
+        maxWidth = s.content.width;
+        maxCellWidth = s.cell.width;
+      } else {
+          //calculate the width
+          oldPos = store.getPosition();
+          maxWidth = maxCellWidth = 0;
+          while (store.valid()) {
+              //check size by placing text into a TD and measuring it.
+              this.options.renderer.render();
+              text = document.id(this.options.renderer);
+              klass = 'jxGridCell';
+              if (this.grid.row.useHeaders()
+                      && this.options.name === this.grid.row
+                      .getRowHeaderColumn()) {
+                  klass = 'jxGridRowHead';
+              }
+              s = this.measure(text, klass, rowHeader, store.getPosition());
+              if (s.content.width > maxWidth) {
+                  maxWidth = s.content.width;
+              }
+              if (s.cell.width > maxCellWidth) {
+                maxCellWidth = s.cell.width;
+              }
+              if (store.hasNext()) {
+                  store.next();
+              } else {
+                  break;
+              }
+          }
+
+          //check the column header as well (unless this is the row header)
+          if (!(this.grid.row.useHeaders() &&
+              this.options.name === this.grid.row.getRowHeaderColumn())) {
+              klass = 'jxGridColHead';
+              if (this.isEditable()) {
+                  klass += ' jxColEditable';
+              }
+              if (this.isResizable()) {
+                  klass += ' jxColResizable';
+              }
+              if (this.isSortable()) {
+                  klass += ' jxColSortable';
+              }
+              s = this.measure(this.domObj.clone(), klass);
+              if (s.content.width > maxWidth) {
+                  maxWidth = s.content.width;
+              }
+              if (s.cell.width > maxCellWidth) {
+                maxCellWidth = s.cell.width;
+              }
+          }
+      }
+
+      this.width = maxWidth;
+      this.cellWidth = maxCellWidth;
+      store.moveTo(oldPos);
+      return this.width;
+    },
+    /**
+     * 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.
+     * rowHeader -
+     * row -
+     */
+    measure : function (text, klass, rowHeader, row) {
+        var d = new Element('span', {
+            'class' : klass
+        }),
+        s;
+        text.inject(d);
+        //d.setStyle('height', this.grid.row.getHeight(row));
+        d.setStyles({
+            'visibility' : 'hidden',
+            'width' : 'auto'
+        });
+
+        d.inject(document.body, 'bottom');
+        s = d.measure(function () {
+            var el = this;
+            //if not rowHeader, get size of innner span
+            if (!rowHeader) {
+                el = el.getFirst();
+            }
+            return {
+              content: el.getMarginBoxSize(),
+              cell: el.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: isAttached
+     * returns whether this column is attached to a store.
+     */
+    isAttached: function () {
+        return this.options.renderer.attached;
+    },
+
+    /**
+     * APIMethod: getHTML
+     * calls render method of the renderer object passed in.
+     */
+    getHTML : function () {
+        this.options.renderer.render();
+        return document.id(this.options.renderer);
+    }
+
+});/*
+---
+
+name: Jx.Columns
+
+description: A container for defining and holding individual columns
+
+license: MIT-style license.
+
+requires:
+ - Jx.Column
+ - Jx.Object
+
+provides: [Jx.Columns]
+
+...
+ */
+// $Id: columns.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * 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({
+
+  Family: 'Jx.Columns',
+    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 : [],
+    
+    /**
+     * Property: rowTemplate
+     * a string holding a template for a single row of cells to be populated
+     * when rendering the store into a grid.  The template is constructed from
+     * the individual column templates once the store has been loaded.
+     */
+    rowTemplate: null,
+
+    parameters: ['options','grid'],
+    /**
+     * Property: hasExpandable
+     * boolean indicates whether any of the columns are expandable or not,
+     * which affects some calculations for column widths
+     */
+    hasExpandable: null,
+
+    /**
+     * 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.hasExpandable = false;
+
+        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") {
+                this.columns.push(new Jx.Column(col,this.grid));
+            }
+            var c = this.columns[this.columns.length - 1 ];
+        }, this);
+        
+        this.buildTemplates();
+    },
+    
+    /**
+     * APIMethod: addColumns
+     * add new columns to the columns object after construction.  Causes
+     * the template to change.
+     * 
+     * Parameters:
+     * columns - {Array} an array of columns to add
+     */
+    addColumns: function(columns) {
+      this.columns.extend(columns);
+      this.buildTemplates();
+    },
+    
+    /**
+     * Method: buildTemplates
+     * create the row template based on the current columns
+     */
+    buildTemplates: function() {
+      if (!this.grid) {
+        return;
+      }
+      var rowTemplate = '',
+          hasExpandable = false,
+          grid = this.grid,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null,
+          colTemplate;
+      
+      this.columns.each(function(col, idx) {
+        var colTemplate = '';
+        if (!col.isHidden() && col != rhc) {
+          hasExpandable |= col.options.renderMode == 'expand';
+          if (!col.options.renderer || !col.options.renderer.domInsert) {
+            colTemplate = col.getTemplate(idx);
+          }
+          rowTemplate += "<td class='jxGridCell jxGridCol"+idx+" jxGridCol"+col.options.name+"'>" + colTemplate + "</td>";
+        }
+      });
+      if (!hasExpandable) {
+        rowTemplate += "<td><span class='jxGridCellUnattached'></span></td>";
+      }
+      this.rowTemplate = rowTemplate;
+      this.hasExpandable = hasExpandable;
+    },
+    /**
+     * 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
+        var cell = headers[index];
+          var hClasses = cell.get('class').split(' ').filter(function (cls) {
+            if(this.options.useHeaders)
+              return cls.test('jxColHead-');
+            else
+              return cls.test('jxCol-');
+          }.bind(this));
+        var parts = hClasses[0].split('-');
+        return this.getByName(parts[1]);
+    },
+
+    /**
+     * APIMethod: getHeaders
+     * Returns a row with the headers in it.
+     *
+     * Parameters:
+     * row - the row to add the headers to.
+     */
+    getHeaders : function (tr) {
+      var grid = this.grid,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null;
+      if (this.useHeaders()) {
+        this.columns.each(function(col, idx) {
+          if (!col.isHidden() && col != rhc) {
+            var classes = ['jxGridColHead', 'jxGridCol'+idx, 'jxCol-'+col.options.name, 'jxColHead-'+col.options.name],
+                th;
+            if (col.isEditable()) { classes.push('jxColEditable'); }
+            if (col.isResizable()) { classes.push('jxColResizable'); }
+            if (col.isSortable()) { classes.push('jxColSortable'); }
+            th = new Element('th', {
+              'class': classes.join(' ')
+            });
+            th.store('jxCellData', {
+              column: col,
+              colHeader: true,
+              index: idx
+            });
+            th.adopt(col.getHeaderHTML());
+            th.inject(tr);
+          }
+        });
+        if (!this.hasExpandable) {
+          new Element('th', {
+            'class': 'jxGridColHead jxGridCellUnattached'
+          }).inject(tr);
+        }
+      }
+    },
+    
+    /**
+     * Method: getRow
+     * create a single row in the grid for a single record and populate
+     * the DOM elements for it.
+     *
+     * Parameters:
+     * tr - {DOMElement} the TR element to insert the row into
+     * record - {<Jx.Record>} the record to create the row for
+     */
+    getRow: function(tr, record) {
+      var data = {},
+          grid = this.grid,
+          store = grid.store,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? 
+                     this.getByName(row.options.headerColumn) : null,
+          domInserts = [],
+          i = 0;
+      this.columns.each(function(column, index) {
+        if (!column.isHidden() && column != rhc) {
+          if (column.options.renderer && column.options.renderer.domInsert) {
+            domInserts.push({column: column, index: i});
+          } else {
+            var renderer = column.options.renderer,
+                formatter = renderer.options.formatter,
+                text = '';
+            if (renderer.options.textTemplate) {
+              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
+            } else {
+              text = record.data.get(column.name);
+            }
+            if (formatter) {
+              text = formatter.format(text);
+            }
+            data['col'+index] = text;
+          }
+          i++;
+        }
+      });
+      tr.set('html', this.rowTemplate.substitute(data));
+      domInserts.each(function(obj) {
+        tr.childNodes[obj.index].adopt(obj.column.getHTML());
+      });
+    },
+
+    /**
+     * APIMethod: calculateWidths
+     * force calculation of column widths.  For columns with 'fit' this will
+     * cause the column to test every value in the store to compute the
+     * optimal width of the column.  Columns marked as 'expand' will get
+     * any extra space left over between the column widths and the width
+     * of the grid container (if any).
+     */
+    calculateWidths: function () {
+      //to calculate widths we loop through each column
+      var expand = null,
+          totalWidth = 0,
+          rowHeaderWidth = 0,
+          gridSize = this.grid.contentContainer.getContentBoxSize(),
+          leftOverSpace = 0;
+      this.columns.each(function(col,idx){
+        //are we checking the rowheader?
+        var rowHeader = false;
+        // if (col.name == this.grid.row.options.headerColumn) {
+        //   rowHeader = true;
+        // }
+        //if it's fixed, set the width to the passed in width
+        if (col.options.renderMode == 'fixed') {
+          col.calculateWidth(); //col.setWidth(col.options.width);
+          
+        } else if (col.options.renderMode == 'fit') {
+          col.calculateWidth(rowHeader);
+        } else if (col.options.renderMode == 'expand' && !$defined(expand)) {
+          expand = col;
+        } else {
+          //treat it as fixed if has width, otherwise as fit
+          if ($defined(col.options.width)) {
+            col.setWidth(col.options.width);
+          } else {
+            col.calculateWidth(rowHeader);
+          }
+        }
+        if (!col.isHidden() /* && !(col.name == this.grid.row.options.headerColumn) */) {
+            totalWidth += Jx.getNumber(col.getCellWidth());
+            if (rowHeader) {
+                rowHeaderWidth = col.getWidth();
+            }
+        }
+      },this);
+      
+      // width of the container
+      if (gridSize.width > totalWidth) {
+        //now figure the expand column
+        if ($defined(expand)) {
+          // var leftOverSpace = gridSize.width - totalWidth + rowHeaderWidth;
+          leftOverSpace = gridSize.width - totalWidth;
+          //account for right borders in firefox...
+          if (Browser.Engine.gecko) {
+            leftOverSpace -= this.getColumnCount(true);
+          } else {
+            // -2 is for the right hand border on the cell and the table for all other browsers
+            leftOverSpace -= 2;
+          }
+          if (leftOverSpace >= expand.options.width) {
+            //in order for this to be set properly the cellWidth must be the
+            //leftover space. we need to figure out the delta value and
+            //subtract it from the leftover width
+            expand.options.width = leftOverSpace;
+            expand.calculateWidth();
+            expand.setWidth(leftOverSpace, true);
+            totalWidth += leftOverSpace;
+          } else {
+            expand.setWidth(expand.options.width);
+          }
+        }
+      }
+      this.grid.gridObj.setContentBoxSize({'width': totalWidth});
+      this.grid.colObj.setContentBoxSize({'width': totalWidth});
+    },
+
+    /**
+     * Method: createRules
+     * create CSS rules for the current grid object
+     */
+    createRules: function(styleSheet, scope) {
+      var autoRowHeight = this.grid.row.options.rowHeight == 'auto';
+      this.columns.each(function(col, idx) {
+        var selector = scope+' .jxGridCol'+idx,
+            dec = '';
+        if (autoRowHeight) {
+          //set the white-space to 'normal !important'
+          dec = 'white-space: normal !important';
+        }
+        col.cellRule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+        col.cellRule.style.width = col.getCellWidth() + "px";
+
+        selector = scope+" .jxGridCol" + idx + " .jxGridCellContent";
+        col.rule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+        col.rule.style.width = col.getWidth() + "px";
+      }, this);
+    },
+
+    updateRule: function(column) {
+        var col = this.getByName(column);
+        if (col.options.renderMode === 'fit') {
+          col.calculateWidth();
+        }
+        col.rule.style.width = col.getWidth() + "px";
+        col.cellRule.style.width = col.getCellWidth() + "px";
+    },
+    
+    /**
+     * APIMethod: getColumnCount
+     * returns the number of columns in this model (including hidden).
+     */
+    getColumnCount : function (noHidden) {
+        noHidden = $defined(noHidden) ? false : true;
+        var total = this.columns.length;
+        if (noHidden) {
+            this.columns.each(function(col){
+                if (col.isHidden()) {
+                    total -= 1;
+                }
+            },this);
+        }
+        return total;
+    },
+    /**
+     * 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren(),
+            c,
+            i = -1,
+            self = this;
+        headers.each(function (h) {
+            i++;
+            var hClasses = h.get('class').split(' ').filter(function (cls) {
+                if(self.options.useHeaders)
+                  return cls.test('jxColHead-');
+                else
+                  return cls.test('jxCol-');
+            });
+            hClasses.each(function (cls) {
+                if (cls.test(name)) {
+                    c = i;
+                }
+            });
+        }, this);
+        return c;
+    }
+
+});
+/*
+---
+
+name: Jx.Row
+
+description: Holds information related to display of rows in the grid.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Row]
+
+...
+ */
+// $Id: row.js 986 2010-09-15 19:01:47Z pagameba $
+/**
+ * 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({
+
+  Family: 'Jx.Row',
+    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 : 40,
+        /**
+         * Option: headerColumn
+         * The name of the column in the model to use as the header
+         */
+        headerColumn : null
+    },
+    /**
+     * Property: grid
+     * A reference to the grid that this row model belongs to
+     */
+    grid : null,
+    /**
+     * Property: heights
+     * This will hold the calculated height of each row in the grid.
+     */
+    heights: [],
+    /**
+     * Property: rules
+     * A hash that will hold all of the CSS rules for the rows.
+     */
+    rules: $H(),
+
+    parameters: ['options','grid'],
+
+    /**
+     * 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 (row, text) {
+        var o = this.options,
+            rc = o.rowClasses,
+            c = o.alternateRowColors ?(row % 2 ? rc.even : rc.odd) : rc.all,
+            tr = new Element('tr', {
+              'class' : 'jxGridRow'+row+' '+ c,
+              html: text || ''
+            });
+        return tr;
+    },
+    /**
+     * Method: getRowHeaderCell
+     * creates the TH for the row's header
+     */
+    getRowHeaderCell : function (text) {
+      text = text ? '<span class="jxGridCellContent">'+text + '</span>' : '';
+      return new Element('th', {
+        'class' : 'jxGridRowHead',
+        html: text
+      });
+    },
+    /**
+     * APIMethod: getRowHeaderWidth
+     * determines the row header's width.
+     */
+    getRowHeaderWidth : function () {
+      var col, width;
+      if (this.options.headerColumn) {
+        col = this.grid.columns.getByName(this.options.headerColumn);
+        if (!$defined(col.getWidth())) {
+          col.calculateWidth(true);
+        }
+        width = col.getWidth();
+      } else {
+        width = this.options.headerWidth;
+      }
+      return width;
+    },
+
+    /**
+     * APIMethod: getHeight
+     * determines and returns the height of a row
+     */
+    getHeight : function (row) {
+      var h = this.options.rowHeight,
+          rowEl;
+      //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.
+      if ($defined(this.heights[row])) {
+        h = this.heights[row];
+      } else if ($defined(this.options.rowHeight)) {
+        if (this.options.rowHeight == 'auto') {
+          // this.calculateHeight(row);
+          h = 20; // TODO calculate?
+          rowEl = this.grid.gridTableBody.rows[row]
+          if (rowEl) {
+            h = rowEl.getContentBoxSize().height; 
+          }
+        } else if (Jx.type(this.options.rowHeight) !== 'number') {
+          h = 20; // TODO calculate?
+        }
+      }
+      return h;
+    },
+    /**
+     * Method: calculateHeights
+     */
+    calculateHeights : function () {
+      if (this.options.rowHeight === 'auto' ||
+          !$defined(this.options.rowHeight)) {
+        //grab all rows in the grid body
+        document.id(this.grid.gridTableBody).getChildren().each(function(row){
+          row = document.id(row);
+          var data = row.retrieve('jxRowData');
+          var s = row.getContentBoxSize();
+          this.heights[data.row] = s.height;
+        },this);
+        document.id(this.grid.rowTableHead).getChildren().each(function(row){
+          row = document.id(row);
+          var data = row.retrieve('jxRowData');
+          if (data) {
+            var s = row.getContentBoxSize();
+            this.heights[data.row] = Math.max(this.heights[data.row],s.height);
+            if (Browser.Engine.webkit) {
+                //for some reason webkit (Safari and Chrome)
+                this.heights[data.row] -= 1;
+            }
+          }
+        },this);
+      } else {
+        document.id(this.grid.rowTableHead).getChildren().each(function(row,idx){
+          this.heights[idx] = this.options.rowHeight;
+        }, this);
+      }
+    },
+
+    /**
+     * 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
+     *
+     * Parameters:
+     * list - Jx.List instance to add the header to
+     */
+    getRowHeader : function (list) {
+        var th = this.getRowHeaderCell();
+        //if (this.grid.model.getPosition() === 0) {
+        //    var rowWidth = this.getRowHeaderWidth();
+        //    th.setStyle("width", rowWidth);
+        //}
+        th.store('jxCellData', {
+            rowHeader: true,
+            row: this.grid.model.getPosition()
+        });
+        list.add(th);
+    },
+    /**
+     * APIMethod: getRowHeaderColumn
+     * returns the name of the column that is used for the row header
+     */
+    getRowHeaderColumn : function () {
+        return this.options.headerColumn;
+    }
+});
+/*
+---
+
+name: Jx.Plugin
+
+description: Base class for all plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Plugin]
+
+...
+ */
+// $Id: plugin.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.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({
+
+    Family: "Jx.Plugin",
+
+    Extends: Jx.Object,
+
+    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);
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid
+
+description: Namespace for grid plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Grid]
+
+...
+ */
+// $Id: plugin.grid.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Grid
+
+description: A tabular control that has fixed scrolling headers on the rows and columns like a spreadsheet.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.Styles
+ - Jx.Layout
+ - Jx.Columns
+ - Jx.Row
+ - Jx.Plugin.Grid
+ - Jx.Store
+ - Jx.List
+
+provides: [Jx.Grid]
+
+css:
+ - grid
+
+images:
+ - table_col.png
+ - table_row.png
+
+...
+ */
+// $Id: grid.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Grid
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A tabular control that has fixed, optional, 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 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. 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
+ * Editor - allows editing of cells if the column permits editing
+ *
+ * Jx.Grid renders data that comes from an external source.  This external
+ * source, called the store, must be a Jx.Store or extended from it.
+ *
+ * Events:
+ * gridCellEnter(cell, list) - called when the mouse enters a cell
+ * gridCellLeave(cell, list) - called when the mouse leaves a cell
+ * gridCellClick(cell) - called when a cell is clicked
+ * gridRowEnter(cell, list) - called when the mouse enters a row header
+ * gridRowLeave(cell, list) - called when the mouse leaves a row header
+ * gridRowClick(cell) - called when a row header is clicked
+ * gridColumnEnter(cell, list) - called when the mouse enters a column header
+ * gridColumnLeave(cell, list) - called when the mouse leaves a column header
+ * gridColumnClick(cell) - called when a column header is clicked
+ * gridMouseLeave() - called when the mouse leaves the grid at any point.
+ *
+ * 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',
+  Extends: Jx.Widget,
+  Binds: ['storeLoaded', 'clickColumnHeader', 'moveColumnHeader', 'clickRowHeader', 'moveRowHeader', 'clickCell', 'dblclickCell', 'moveCell', 'leaveGrid', 'resize', 'drawStore', 'scroll', 'addRow', 'removeRow', 'removeRows', 'updateRow', 'storeChangesCompleted'],
+
+  /**
+   * Property: pluginNamespace
+   * the required variable for plugins
+   */
+  pluginNamespace: 'Grid',
+  
+  options: {
+    /**
+     * Option: parent
+     * the HTML element to create the grid inside. The grid will resize
+     * to fill the domObj.
+     */
+    parent: null,
+    
+    template: "<div class='jxWidget'><div class='jxGridContainer jxGridRowCol'></div><div class='jxGridContainer jxGridColumnsContainer'><table class='jxGridTable jxGridHeader jxGridColumns'><thead class='jxGridColumnHead'></thead></table></div><div class='jxGridContainer jxGridHeader jxGridRowContainer'><table class='jxGridTable jxGridRows'><thead class='jxGridRowBody'></thead></table></div><div class='jxGridContainer jxGridContentContainer'><table class='jxGridTable jxGridContent'><tbody class='jxGridTableBody'></tbody></table></div></div>",
+    
+    /**
+     * 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.
+     */
+    columns: null,
+    
+    /**
+     * Option: row
+     * Either a Jx.Grid.Row object or a json object defining options for
+     * the class
+     */
+    row : null,
+
+    /**
+     * Option: store
+     * An instance of Jx.Store
+     */
+    store: null
+  },
+   
+  classes: $H({
+    domObj: 'jxWidget',
+    columnContainer: 'jxGridColumnsContainer',
+    colObj: 'jxGridColumns',
+    colTableBody: 'jxGridColumnHead',
+    rowContainer: 'jxGridRowContainer',
+    rowObj: 'jxGridRows',
+    rowColContainer: 'jxGridRowCol',
+    rowTableBody: 'jxGridRowBody',
+    contentContainer: 'jxGridContentContainer',
+    gridObj: 'jxGridContent',
+    gridTableBody: 'jxGridTableBody'
+  }),
+  
+  /**
+   * Property: columns
+   * holds a reference to the columns object
+   */
+  columns: null,
+  
+  /**
+   * Property: row
+   * Holds a reference to the row object
+   */
+  row: null,
+  
+  parameters: ['store', 'options'],
+  
+  /**
+   * Property: store
+   * holds a reference to the <Jx.Store> that is the store for this
+   * grid
+   */
+  store: null,
+  
+  /**
+   * Property: styleSheet
+   * the name of the dynamic style sheet to use for manipulating styles
+   */
+  styleSheet: 'JxGridStyles',
+  
+  /**
+   * Property: hooks
+   * a {Hash} of event names for tracking which events have actually been attached
+   * to the grid.
+   */
+  hooks: null,
+  
+  /**
+   * Property: uniqueId
+   * an auto-generated id that is assigned as a class name to the grid's
+   * container for scoping generated CSS rules to just this grid
+   */
+  uniqueId: null,
+  
+  /**
+   * Constructor: Jx.Grid
+   */
+  init: function() {
+    this.uniqueId = this.generateId('jxGrid_');
+    this.store = this.options.store;
+    var options = this.options,
+        opts;
+
+    if ($defined(options.row)) {
+      if (options.row instanceof Jx.Row) {
+        this.row = options.row;
+        this.row.grid = this;
+      } else if (Jx.type(options.row) == 'object') {
+        this.row = new Jx.Row($extend({grid: this}, options.row));
+      }
+    } else {
+      this.row = new Jx.Row({grid: this});
+    }
+
+    if ($defined(options.columns)) {
+        if (options.columns instanceof Jx.Columns) {
+            this.columns = options.columns;
+            this.columns.grid = this;
+        } else if (Jx.type(options.columns) === 'object') {
+            this.columns = new Jx.Columns($extend({grid:this}, options.columns));
+        }
+    } else {
+      this.columns = new Jx.Columns({grid: this});
+    }
+    
+    this.hooks = $H({
+      'gridScroll': false,
+      'gridColumnEnter': false,
+      'gridColumnLeave': false,
+      'gridColumnClick': false,
+      'gridRowEnter': false,
+      'gridRowLeave': false,
+      'gridRowClick': false,
+      'gridCellClick': false,
+      'gridCellDblClick': false,
+      'gridCellEnter': false,
+      'gridCellLeave': false,
+      'gridMouseLeave': false
+    });
+    
+    this.storeEvents = {
+      'storeDataLoaded': this.storeLoaded,
+      // 'storeSortFinished': this.drawStore,
+      'storeRecordAdded': this.addRow,
+      'storeColumnChanged': this.updateRow,
+      'storeRecordRemoved': this.removeRow,
+      'storeMultipleRecordsRemoved': this.removeRows,
+      'storeChangesCompleted': this.storeChangesCompleted
+    };
+    
+    
+    this.parent();
+  },
+  
+  wantEvent: function(eventName) {
+    var hook = this.hooks.get(eventName);
+    if (hook === false) {
+      switch(eventName) {
+        case 'gridColumnEnter':
+        case 'gridColumnLeave':
+          this.colObj.addEvent('mousemove', this.moveColumnHeader);
+          this.hooks.set({
+            'gridColumnEnter': true,
+            'gridColumnLeave': true
+          });
+          break;
+        case 'gridColumnClick':
+          this.colObj.addEvent('click', this.clickColumnHeader);
+          this.hooks.set({
+            'gridColumnClick': true
+          });
+          break;
+        case 'gridRowEnter':
+        case 'gridRowLeave':
+          this.rowObj.addEvent('mousemove', this.moveRowHeader);
+          this.hooks.set({
+            'gridRowEnter': true,
+            'gridRowLeave': true
+          });
+          break;
+        case 'gridRowClick':
+          this.rowObj.addEvent('click', this.clickRowHeader);
+          this.hooks.set({
+            'gridRowClick': true
+          });
+          break;
+        case 'gridCellEnter':
+        case 'gridCellLeave':
+          this.gridObj.addEvent('mousemove', this.moveCell);
+          this.hooks.set({
+            'gridCellEnter': true,
+            'gridCellLeave': true
+          });
+          break;
+        case 'gridCellClick':
+          this.gridObj.addEvent('click', this.clickCell);
+          this.hooks.set('gridCellClick', true);
+          break;
+        case 'gridCellDblClick':
+          this.gridObj.addEvent('dblclick', this.dblclickCell);
+          this.hooks.set('gridCellDblClick', true);
+          break;
+        case 'gridMouseLeave':
+          this.rowObj.addEvent('mouseleave', this.leaveGrid);
+          this.colObj.addEvent('mouseleave', this.leaveGrid);
+          this.gridObj.addEvent('mouseleave', this.leaveGrid);
+          this.hooks.set('gridMouseLeave', true);
+          break;
+        case 'gridScroll':
+          this.contentContainer.addEvent('scroll', this.scroll);
+        default:
+          break;
+      }
+    }
+  },
+  
+  /**
+   * Method: scroll
+   * handle the grid scrolling by updating the position of the headers
+   */
+  scroll : function () {
+      this.columnContainer.scrollLeft = this.contentContainer.scrollLeft;
+      this.rowContainer.scrollTop = this.contentContainer.scrollTop;
+  },
+  
+  /**
+   * APIMethod: render
+   * Create the grid for the current model
+   */
+  render: function() {
+    if (this.domObj) {
+      this.redraw();
+      return;
+    }
+    this.parent();
+    var store = this.store;
+    
+    this.domObj.addClass(this.uniqueId);
+    new Jx.Layout(this.domObj, {
+      onSizeChange: this.resize
+    });
+    
+    if (store instanceof Jx.Store) {
+      store.addEvents(this.storeEvents);
+      if (store.loaded) {
+        this.storeLoaded(store);
+      }
+    }
+    if (!this.columns.useHeaders()) {
+      this.columnContainer.dispose();
+    } else {
+      this.wantEvent('gridScroll');
+    }
+    
+    if (!this.row.useHeaders()) {
+      this.rowContainer.dispose();
+    } else {
+      this.wantEvent('gridScroll');
+    }
+
+    this.contentContainer.setStyle('overflow', 'auto');
+    
+    // todo: very hacky!  can plugins 'wantEvent' between init and render?
+    this.hooks.each(function(value, key) {
+      if (value) {
+        this.hooks.set(key, false);
+        this.wantEvent(key);
+      }
+    }, this);
+    
+    if (document.id(this.options.parent)) {
+      this.addTo(this.options.parent);
+      this.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() {
+    var p = this.domObj.getParent(),
+        parentSize = p.getSize(),
+        colHeaderHeight = 0,
+        rowHeaderWidth = 0;
+    
+    if (this.columns.useHeaders()) {
+      colHeaderHeight = this.columns.getHeaderHeight();
+    }
+    
+    if (this.row.useHeaders()) {
+      rowHeaderWidth = this.row.getRowHeaderWidth();
+    }
+    
+    this.rowColContainer.setBorderBoxSize({
+        width : rowHeaderWidth,
+        height : colHeaderHeight
+    });
+    
+    this.columnContainer.setStyles({
+      top: 0,
+      left: rowHeaderWidth
+    }).setBorderBoxSize({
+      width: parentSize.x - rowHeaderWidth,
+      height: colHeaderHeight
+    });
+
+    this.rowContainer.setStyles({
+      top: colHeaderHeight,
+      left: 0
+    }).setBorderBoxSize({
+      width: rowHeaderWidth,
+      height: parentSize.y - colHeaderHeight
+    });
+
+    this.contentContainer.setStyles({
+      top: colHeaderHeight,
+      left: rowHeaderWidth
+    }).setBorderBoxSize({
+      width: parentSize.x - rowHeaderWidth,
+      height: parentSize.y - colHeaderHeight
+    });
+  },
+  
+  /**
+   * APIMethod: setStore
+   * set the store for the grid to display.  If a store is attached to the
+   * grid it is removed and the new store is displayed.
+   *
+   * Parameters:
+   * store - {Object} the store to use for this grid
+   */
+  setStore: function(store) {
+    if (this.store) {
+      this.store.removeEvents(this.storeEvents);
+    }
+    if (store instanceof Jx.Store) {
+      this.store = store;
+      store.addEvents(this.storeEvents);
+      if (store.loaded) {
+        this.storeLoaded(store);
+      }
+      this.render();
+      this.domObj.resize();
+    } else {
+      this.destroyGrid();
+    }
+  },
+  
+  /**
+   * APIMethod: getStore
+   * gets the store set for this grid.
+   */
+  getStore: function() { 
+    return this.store;
+  },
+  
+  storeLoaded: function(store) {
+    this.redraw();
+  },
+  
+  /**
+   */
+  storeChangesCompleted: function(results) {
+    if (results && results.successful) {
+      
+    }
+  },
+  
+  redraw: function() {
+    var store = this.store,
+        template = '',
+        tr,
+        columns = [],
+        useRowHeaders = this.row.useHeaders();
+    this.fireEvent('beginCreateGrid');
+    
+    this.gridObj.getElement('tbody').empty();
+    
+    this.hoverColumn = this.hoverRow = this.hoverCell = null;
+    
+    // TODO: consider moving whole thing into Jx.Columns ??
+    // create a suitable column representation for everything
+    // in the store that doesn't already have a representation
+    store.options.columns.each(function(col, index) {
+      if (!this.columns.getByName(col.name)) {
+        var renderer = new Jx.Grid.Renderer.Text(),
+            format = $defined(col.format) ? col.format : null,
+            template = "<span class='jxGridCellContent'>"+ ($defined(col.label) ? col.label : col.name).capitalize() + "</span>",
+            column;
+        if ($defined(col.renderer)) {
+          if ($type(col.renderer) == 'string') {
+            if (Jx.Grid.Renderer[col.renderer.capitalize()]) {
+              renderer = new Jx.Grid.Renderer[col.renderer.capitalize()]();
+            }
+          } else if ($type(col.renderer) == 'object' && 
+                     $defined(col.renderer.type) && 
+                     Jx.Grid.Renderer[col.renderer.type.capitalize()]) {
+            renderer = new Jx.Grid.Renderer[col.renderer.type.capitalize()](col.renderer);
+          }
+        }
+        if (format) {
+          if ($type(format) == 'string' && 
+              $defined(Jx.Formatter[format.capitalize()])) {
+            renderer.options.formatter = new Jx.Formatter[format.capitalize()]();
+          } else if ($type(format) == 'object' && 
+                     $defined(format.type) && 
+                     $defined(Jx.Formatter[format.type.capitalize()])) {
+             renderer.options.formatter = new Jx.Formatter[format.type.capitalize()](format);
+          }
+        }
+        column = new Jx.Column({
+          grid: this,
+          template: template,
+          renderMode: $defined(col.renderMode) ? col.renderMode : $defined(col.width) ? 'fixed' : 'fit',
+          width: $defined(col.width) ? col.width : null,
+          isEditable: $defined(col.editable) ? col.editable : false,
+          isSortable: $defined(col.sortable) ? col.sortable : false,
+          isResizable: $defined(col.resizable) ? col.resizable : false,
+          isHidden: $defined(col.hidden) ? col.hidden : false,
+          name: col.name || '',
+          renderer: renderer 
+        });
+        columns.push(column);
+      }
+    }, this);
+    this.columns.addColumns(columns);
+    if (this.columns.useHeaders()) {
+      tr = new Element('tr');
+      this.columns.getHeaders(tr);
+      tr.adopt(new Element('th', {
+        'class': 'jxGridColHead',
+        'html': '&nbsp',
+        styles: {
+          width: 1000
+        }
+      }))
+      this.colObj.getElement('thead').empty().adopt(tr);
+    }
+    this.columns.calculateWidths();
+    this.columns.createRules(this.styleSheet+'Columns', '.'+this.uniqueId);
+    this.drawStore();
+    this.fireEvent('doneCreateGrid');
+  },
+  
+  /**
+   * APIMethod: addRow
+   * Adds a row to the table. Can add to either the beginning or the end 
+   * based on passed flag
+   */
+  addRow: function (store, record, position) {
+    if (this.store.loaded) {
+      if (position === 'bottom') {
+        this.store.last();
+      } else {
+        this.store.first();
+      }
+      this.drawRow(record, this.store.index, position);
+    }
+  },
+  
+  /**
+   * APIMethod: updateRow
+   * update a single row in the grid
+   *
+   * Parameters:
+   * index - the row to update
+   */
+  updateRow: function(index) {
+    var record = this.store.getRecord(index);
+    this.drawRow(record, index, 'replace');
+  },
+  
+  /**
+   * APIMethod: removeRow
+   * remove a single row from the grid
+   *
+   * Parameters:
+   * store
+   * index
+   */
+  removeRow: function (store, index) {
+    this.gridObj.deleteRow(index);
+    this.rowObj.deleteRow(index);
+  },
+  
+  /**
+   * APIMethod: removeRows
+   * removes multiple rows from the grid
+   *
+   * Parameters:
+   * store
+   * index
+   */
+  removeRows: function (store, first, last) {
+    for (var i = first; i <= last; i++) {
+        this.removeRow(store, first);
+    }
+  },
+  
+  /**
+   * APIMethod: setColumnWidth
+   * set the width of a column in pixels
+   *
+   * Parameters:
+   * column
+   * width
+   */
+  setColumnWidth: function(column, width) {
+    if (column) {
+      column.width = width;
+      if (column.rule) {
+        column.rule.style.width = width + 'px';
+      }
+      if (column.cellRule) {
+        column.cellRule.style.width = width + 'px';
+      }
+    }
+  },
+  
+  /**
+   * Method: drawStore
+   * clears the grid and redraws the store.  Does not draw the column headers,
+   * that is handled by the render() method
+   */
+  drawStore: function() {
+    var useHeaders = this.row.useHeaders(), 
+        blank;
+    this.domObj.resize();
+    this.gridTableBody.empty();
+    if (useHeaders) {
+      this.rowTableBody.empty();
+    }
+    this.store.each(function(record,index) {
+      this.store.index = index;
+      this.drawRow(record, index);
+    }, this);
+    if (useHeaders) {
+      blank = new Element('tr', {
+        styles: { height: 1000 }
+      });
+      blank.adopt(new Element('th', {
+        'class':'jxGridRowHead', 
+        html: '&nbsp'
+      }));
+      this.rowTableBody.adopt(blank);
+    }
+  },
+  
+  /**
+   * Method: drawRow
+   * this method does the heavy lifting of drawing a single record into the
+   * grid
+   *
+   * Parameters:
+   * record - {Jx.Record} the record to render
+   * index - {Integer} the row index of the record in the store
+   * position - {String} 'top' or 'bottom' (default 'bottom') position to put
+   *     the new row in the grid.
+   */
+  drawRow: function(record, index, position) {
+    var columns = this.columns,
+        body = this.gridTableBody,
+        row = this.row,
+        store = this.store,
+        rowHeaders = row.useHeaders(),
+        autoRowHeight = row.options.rowHeight == 'auto',
+        rowBody = this.rowTableBody,
+        rowHeaderColumn,
+        rowHeaderColumnIndex,
+        renderer,
+        formatter, 
+        getData,
+        tr,
+        th,
+        text = index + 1,
+        rh;
+    if (!$defined(position) || !['top','bottom','replace'].contains(position)) {
+      position = 'bottom';
+    }
+    tr = row.getGridRowElement(index, '');
+    if (position == 'replace' && index < body.childNodes.length) {
+      tr.inject(body.childNodes[index], 'after');
+      body.childNodes[index].dispose();
+    } else {
+      tr.inject(body, position);
+    }
+    columns.getRow(tr, record);
+    if (rowHeaders) {
+      if (row.options.headerColumn) {
+        rowHeaderColumn = columns.getByName(row.options.headerColumn);
+        renderer = rowHeaderColumn.options.renderer;
+        if (!renderer.domInsert) {
+          formatter = rowHeaderColumn.options.formatter;
+          rowHeaderColumnIndex = columns.columns.indexOf(rowHeaderColumn);
+          getData = function(record) {
+            var data = {},
+                text = '';
+            if (renderer.options.textTemplate) {
+              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
+            } else {
+              text = record.data.get(rowHeaderColumn.name);
+            }
+            data['col'+rowHeaderColumnIndex] = text;
+            return data;
+          };
+          text = rowHeaderColumn.getTemplate(rowHeaderColumnIndex).substitute(getData(record));
+        } else {
+          text = '';
+        }
+      }
+      th = row.getRowHeaderCell(text);
+      if (row.options.headerColumn && renderer.domInsert) {
+        th.adopt(rowHeaderColumn.getHTML());
+      }
+      rh = new Element('tr').adopt(th);
+      if (position == 'replace' && index < rowBody.childNodes.length) {
+        rh.inject(rowBody.childNodes[index], 'after');
+        rowBody.childNodes[index].dispose();
+      } else {
+        rh.inject(rowBody, position);
+      }
+      if (autoRowHeight) {
+        // th.setBorderBoxSize({height: tr.childNodes[0].getBorderBoxSize().height});
+        rh.setBorderBoxSize({height: tr.getBorderBoxSize().height});
+      }
+    }
+    this.fireEvent('gridDrawRow', [index, record]);
+  },
+  
+  /**
+   * Method: clickColumnHeader
+   * handle clicks on the column header
+   */
+  clickColumnHeader: function(e) {
+    var target = e.target;
+    if (target.getParent('thead')) {
+      target = target.tagName == 'TH' ? target : target.getParent('th');
+      this.fireEvent('gridColumnClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveColumnHeader
+   * handle the mouse moving over the column header
+   */
+  moveColumnHeader: function(e) {
+    var target = e.target;
+    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridColHead');
+    if (target) {
+      if (this.hoverColumn != target) {
+        if (this.hoverColumn) {
+          this.fireEvent('gridColumnLeave', this.hoverColumn);
+        }
+        if (!target.hasClass('jxGridColHead')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverColumn = target;
+          this.fireEvent('gridColumnEnter', target);
+        }
+      }
+    }
+  },
+
+  /**
+   * Method: clickRowHeader
+   * handle clicks on the row header
+   */
+  clickRowHeader: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TH' ? target : target.getParent('th');
+      this.fireEvent('gridRowClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveRowHeader
+   * handle the mouse moving over the row header
+   */
+  moveRowHeader: function(e) {
+    var target = e.target;
+    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridRowHead');
+    if (target) {
+      if (this.hoverRow != target) {
+        if (this.hoverRow) {
+          this.fireEvent('gridRowLeave', this.hoverRow);
+        }
+        if (!target.hasClass('jxGridRowHead')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverRow = target;
+          this.fireEvent('gridRowEnter', target);
+        }
+      }
+    }
+  },
+  
+  /**
+   * Method: clickCell
+   * handle clicks on cells in the grid
+   */
+  clickCell: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TD' ? target : target.getParent('td');
+      this.fireEvent('gridCellClick', target);
+    }
+  },
+  
+  /**
+   * Method: dblclickCell
+   * handle doubleclicks on cells in the grid
+   */
+  dblclickCell: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TD' ? target : target.getParent('td');
+      this.fireEvent('gridCellDblClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveCell
+   * handle the mouse moving over cells in the grid
+   */
+  moveCell: function(e) {
+    var target = e.target,
+        data,
+        body,
+        row,
+        index,
+        column;
+    target = target.tagName == 'TD' ? target : target.getParent('td.jxGridCell');
+    if (target) {
+      if (this.hoverCell != target) {
+        if (this.hoverCell) {
+          this.fireEvent('gridCellLeave', this.hoverCell);
+        }
+        if (!target.hasClass('jxGridCell')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverCell = target;
+          this.getCellData(target);
+          this.fireEvent('gridCellEnter', target);
+        }
+      }
+    }
+  },
+  
+  getCellData: function(cell) {
+    var data = null,
+        index,
+        column,
+        row;
+    if (!cell.hasClass('jxGridCell')) {
+      cell = cell.getParent('td.jxGridCell');
+    }
+    if (cell) {
+      body = this.gridTableBody;
+      row = body.getChildren().indexOf(cell.getParent('tr'));
+      this.columns.columns.some(function(col,idx){
+        if (cell.hasClass('jxGridCol'+idx)) {
+          index = idx;
+          column = col;
+          return true;
+        }
+        return false;
+      });
+      data = {
+        row: row,
+        column: column,
+        index: index
+      };
+      cell.store('jxCellData', data);
+    }
+    return data;
+  },
+  
+  /**
+   * Method: leaveGrid
+   * handle the mouse leaving the grid
+   */
+  leaveGrid: function(e) {
+    this.hoverCell = null;
+    this.fireEvent('gridMouseLeave');
+  },
+  
+  /**
+   * Method: changeText
+   * rerender the grid when the language changes
+   */
+  changeText : function(lang) {
+      this.parent();
+      this.render();
+  },
+  
+  /**
+   * Method: addEvent
+   * override default addEvent to also trigger wanting the event
+   * which will then cause the underlying events to be registered
+   */
+  addEvent: function(name, fn) {
+    this.wantEvent(name);
+    this.parent(name, fn);
+  }
+});
+/*
+---
+
+name: Jx.Grid.Renderer
+
+description: Base class for all renderers. Used to create the contents of column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid
+
+provides: [Jx.Grid.Renderer]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer
+ * This is the base class and namespace for all grid renderers.
+ * 
+ * Extends: <Jx.Widget>
+ * We extended Jx.Widget to take advantage of templating support.
+ */
+Jx.Grid.Renderer = new Class({
+  
+  Family: 'Jx.Grid.Renderer',
+  Extends: Jx.Widget,
+  
+  parameters: ['options'],
+  
+  options: {
+    deferRender: true,
+    /**
+     * Option: template
+     * The template for rendering this cell. Will be processed as per
+     * the Jx.Widget standard.
+     */
+    template: '<span class="jxGridCellContent"></span>'
+  },
+    /**
+     * APIProperty: attached
+     * tells whether this renderer is used in attached mode
+     * or not. Should be set by renderers that get a reference to
+     * the store.
+     */
+  attached: null,
+  
+  /**
+   * Property: domInsert
+   * boolean, indicates if the renderer needs to insert a DOM element
+   * instead of just outputing some templated HTML.  Renderers that
+   * do use domInsert will be slower.
+   */
+  domInsert: false,
+
+  classes: $H({
+    domObj: 'jxGridCellContent'
+  }),
+
+  column: null,
+
+  init: function () {
+    this.parent();
+    this.attached = false;
+  },
+  
+  render: function () {
+    this.parent();
+  },
+  
+  setColumn: function (column) {
+    if (column instanceof Jx.Column) {
+      this.column = column;
+    }
+  }
+  
+});/*
+---
+
+name: Jx.Grid.Renderer.Text
+
+description: Renders data as straight text.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+
+provides: [Jx.Grid.Renderer.Text]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.Text
+ * This is the default renderer for grid cells. It works the same as the
+ * original column implementation. It needs a store, a field name, and an
+ * optional formatter as well as other options.
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Text = new Class({
+
+  Family: 'Jx.Grid.Renderer.Text',
+  Extends: Jx.Grid.Renderer,
+
+  options: {
+        /**
+         * Option: formatter
+         * an instance of <Jx.Formatter> or one of its subclasses which
+         * will be used to format the data in this column. It can also be
+         * an object containing the name (This should be the part after
+         * Jx.Formatter in the class name. For instance, to get a currency
+         * formatter, specify 'Currency' as the name.) and options for the
+         * needed formatter (see individual formatters for options).
+         * (code)
+         * {
+         *    name: 'formatter name',
+         *    options: {}
+         * }
+         * (end)
+         */
+        formatter: null,
+        /**
+         * Option: textTemplate
+         * Will be used for creating the text that goes iside the template. Use
+         * placeholders for indicating the field(s). You can add as much text
+         * as you want. for example, if you wanted to display someone's full
+         * name that is brokem up in the model with first and last names you
+         * can write a template like '{lastName}, {firstName}' and as long as
+         * the text between { and } are field names in the store they will be
+         * substituted properly.
+         */
+        textTemplate: null,
+        /**
+         * Option: css
+         * A string or function to use in adding classes to the text
+         */
+        css: null
+  },
+
+  store: null,
+
+  columnsNeeded: null,
+
+  init: function () {
+      this.parent();
+      var options = this.options,
+          t;
+      //check the formatter
+      if ($defined(options.formatter) &&
+          !(options.formatter instanceof Jx.Formatter)) {
+          t = Jx.type(options.formatter);
+          if (t === 'object') {
+              // allow users to leave the options object blank
+              if(!$defined(options.formatter.options)) {
+                  options.formatter.options = {};
+              }
+              options.formatter = new Jx.Formatter[options.formatter.name](
+                      options.formatter.options);
+          }
+      }
+  },
+
+  setColumn: function (column) {
+    this.parent();
+
+    this.store = column.grid.getStore();
+    this.attached = true;
+
+    if ($defined(this.options.textTemplate)) {
+      this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+    }
+  },
+
+  render: function () {
+    this.parent();
+
+    var text = '';
+    if ($defined(this.options.textTemplate)) {
+        if (!$defined(this.columnsNeeded) || (Jx.type(this.columnsNeeded) === 'array' && this.columnsNeeded.length === 0)) {
+            this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+        }
+        text = this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded);
+    }
+    if ($defined(this.options.formatter)) {
+        text = this.options.formatter.format(text);
+    }
+
+    this.domObj.set('html',text);
+
+    if ($defined(this.options.css) && Jx.type(this.options.css) === 'function') {
+      this.domObj.addClass(this.options.css.run(text));
+    } else if ($defined(this.options.css) && Jx.type(this.options.css) === 'string'){
+      this.domObj.addClass(this.options.css);
+    }
+
+  }
+
+});/*
+---
+
+name: Jx.Grid.Renderer.Checkbox
+
+description: Renders a checkbox in a column. Can be connected to a store column or as a standalone check column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+ - Jx.Field.Checkbox
+
+provides: [Jx.Grid.Renderer.Checkbox]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.CheckBox
+ * Renders a checkbox into the cell. Allows options for connecting the cell
+ * to a model field and propogating changes back to the store.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Checkbox = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Checkbox',
+  Extends: Jx.Grid.Renderer,
+  
+  Binds: ['onBlur','onChange'],
+  
+  options: {
+    useStore: false,
+    field: null,
+    updateStore: false,
+    checkboxOptions: {
+      template : '<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',
+      name: ''
+    }
+  },
+  
+  domInsert: true,
+  
+  init: function () {
+    this.parent();
+  },
+  
+  render: function () {
+    this.parent();
+    var checkbox = new Jx.Field.Checkbox(this.options.checkboxOptions);
+    this.domObj.adopt(document.id(checkbox));
+    
+    if (this.options.useStore) {
+      //set initial state
+      checkbox.setValue(this.store.get(this.options.field));
+    }
+    
+    //hook up change and blur events to change store field
+    checkbox.addEvents({
+      'blur': this.onBlur,
+      'change': this.onChange
+    });
+  },
+  
+  setColumn: function (column) {
+    this.column = column;
+    
+    if (this.options.useStore) {
+      this.store = this.column.grid.getStore();
+      this.attached = true;
+    }
+  },
+  
+  onBlur: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  onChange: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.fireEvent('change',[this.column, field]);
+  },
+  
+  updateStore: function (field) {
+    var newValue = field.getValue();
+    
+    var data = document.id(field).getParent().retrieve('jxCellData');
+    var row = data.row;
+    
+    if (this.store.get(this.options.field, row) !== newValue) {
+      this.store.set(this.options.field, newValue, row);
+    }
+  }
+  
+  
+});/*
+---
+
+name: Jx.Grid.Renderer.Button
+
+description: "Renders one or more buttons in a single column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+ - Jx.Button
+
+
+provides: [Jx.Grid.Renderer.Button]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.Button
+ * Renders a <Jx.Button> into the cell. You can add s many buttons as you'd like per column by passing button configs
+ * in as an array option to options.buttonOptions
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Button = new Class({
+
+    Family: 'Jx.Grid.Renderer.Button',
+    Extends: Jx.Grid.Renderer,
+
+    Binds: [],
+
+    options: {
+        template: '<span class="buttons"></span>',
+        /**
+         * Option: buttonOptions
+         * an array of option configurations for <Jx.Button>
+         */
+        buttonOptions: null
+    },
+    
+    domInsert: true,
+
+    classes:  $H({
+        domObj: 'buttons'
+    }),
+
+    init: function () {
+        this.parent();
+    },
+
+    render: function () {
+        this.parent();
+
+        $A(this.options.buttonOptions).each(function(opts){
+            var button = new Jx.Button(opts);
+            this.domObj.grab(document.id(button));
+        },this);
+
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid.Selector
+
+description: Allows selecting rows, columns, and cells in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Selector]
+
+...
+ */
+// $Id: grid.selector.js 1003 2010-12-17 20:58:01Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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({
+
+    Family: 'Jx.Plugin.Grid.Selector',
+    Extends : Jx.Plugin,
+    
+    name: 'Selector',
+
+    Binds: ['select','checkSelection','checkAll','afterGridRender','onCellClick', 'sort', 'updateCheckColumn', 'updateSelectedRows'],
+
+    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,
+        /**
+         * Option: multiple
+         * Allow multiple selections
+         */
+        multiple: false,
+        /**
+         * Option: useCheckColumn
+         * Whether to use a check box column as the row header or as the
+         * first column in the grid and use it for manipulating selections.
+         */
+        useCheckColumn: false,
+        /**
+         * Option: checkAsHeader
+         * Determines if the check column is the header of the rows
+         */
+        checkAsHeader: false,
+        /**
+         * Option: sortableColumn
+         * Determines if the check column is sortable
+         */
+        sortableColumn: false
+    },
+    
+    domInsert: true,
+    
+    /**
+     * Property: selected
+     * Holds arrays of selected rows and/or columns and their headers
+     */
+    selected: null,
+
+    /**
+     * 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.selected = $H({
+            cells: [],
+            columns: [],
+            rows: [],
+            rowHeads: [],
+            columnHeads: []
+        });
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * Parameters:
+     * grid - The instance of Jx.Grid to attach to
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+            return;
+        }
+        this.parent(grid);
+        var options = this.options,
+            template;
+        this.grid = grid;
+        
+        this.grid.addEvent('gridSortFinished', this.updateSelectedRows);
+        
+        //setup check column if needed
+        if (options.useCheckColumn) {
+          grid.addEvent('gridDrawRow', this.updateCheckColumn);
+          template = '<span class="jxGridCellContent">';
+          if (options.multiple) {
+            template += '<span class="jxInputContainer jxInputContainerCheck"><input class="jxInputCheck" type="checkbox" name="checkAll" id="checkAll"/></span>';
+          } else {
+            template += '</span>';
+          }
+
+          template += "</span>";
+
+          this.checkColumn = new Jx.Column({
+            template: template,
+            renderMode: 'fixed',
+            width: 20,
+            renderer: null,
+            name: 'selection',
+            isSortable: options.sortableColumn || false,
+            sort: options.sortableColumn ? this.sort : null
+          }, grid);
+          this.checkColumn.options.renderer = this;
+          grid.columns.columns.reverse();
+          grid.columns.columns.push(this.checkColumn);
+          grid.columns.columns.reverse();
+
+          if (options.checkAsHeader) {
+              this.oldHeaderColumn = grid.row.options.headerColumn;
+              grid.row.options.useHeaders = true;
+              grid.row.options.headerColumn = 'selection';
+
+              if (options.multiple) {
+                  grid.addEvent('doneCreateGrid', this.afterGridRender);
+              }
+          }
+          //attach event to header
+          if (options.multiple) {
+              document.id(this.checkColumn).getElement('input').addEvents({
+                  'change': this.checkAll
+              });
+          }
+        } else {
+          grid.addEvent('gridCellClick', this.onCellClick);
+        }
+    },
+    
+    /**
+     * Method: render
+     * required for the renderer interface
+     */
+    render: function() {
+      this.domObj = new Element('span', {
+        'class': 'jxGridCellContent'
+      });
+      new Element('input', {
+        'class': 'jxGridSelector',
+        type: 'checkbox',
+        events: {
+          change: this.checkSelection
+        }
+      }).inject(this.domObj);
+    },
+    
+    /**
+     * Method: toElement
+     * required for the Renderer interface
+     */
+    toElement: function() {
+      return this.domObj;
+    },
+    
+    /**
+     * Method: updateCheckColumn
+     * check to see if a row needs to have its checkbox updated after its been drawn
+     *
+     * Parameters:
+     * index - {Integer} the row that was just rendered
+     * record - {<Jx.Record>} the record that was rendered into that row
+     */
+    updateCheckColumn: function(index, record) {
+      var state = this.selected.get('rows').contains(index),
+          r = this.grid.gridTableBody.rows,
+          tr = document.id((index >= 0 && index < r.length) ? r[index] : null);
+      
+      if (tr) {
+        tr.store('jxRowData', {row: index});
+        if (state) {
+          tr.addClass('jxGridRowSelected');
+        } else {
+          tr.removeClass('jxGridRowSelected');
+        }
+        this.setCheckField(index, state);
+      }
+    },
+
+    /**
+     * Method: afterGridRender
+     */
+    afterGridRender: function () {
+        if (this.options.checkAsHeader) {
+            var chkCol = document.id(this.checkColumn).clone();
+            chkCol.getElement('input').addEvent('change',this.checkAll);
+            this.grid.rowColContainer.adopt(chkCol);
+        }
+        this.grid.removeEvent('doneCreateGrid',this.afterGridRender);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        var grid = this.grid,
+            options = this.options,
+            col;
+        if (grid) {
+            grid.gridTableBody.removeEvents({
+              click: this.onCellClick
+            });
+            if (this.checkColumn) {
+                grid.columns.columns.erase(this.checkColumn);
+                this.checkColumn.destroy();
+                this.checkColumn = null;
+            }
+            if (options.useCheckColumn) {
+                grid.removeEvent('gridDrawRow', this.updateCheckColumn);
+                if (options.checkAsHeader) {
+                    grid.row.options.headerColumn = this.oldHeaderColumn;
+                }
+            }
+        }
+        this.grid.removeEvent('gridSortFinished', this.updateSelectedRows);
+        
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning selections on.
+     *
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'column', or 'row'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning selections off.
+     *
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'column', or 'row'
+     */
+    deactivate: function (opt) {
+        var gridTableRows = this.grid.gridTableBody.rows,
+            selected = this.selected,
+            i;
+        this.options[opt] = false;
+        if (opt === 'cell') {
+            selected.get('cells').each(function(cell) {
+              cell.removeClass('jxGridCellSelected');
+            });
+            selected.set('cells',[]);
+        } else if (opt === 'row') {
+          this.getSelectedRows().each(function(row){
+            idx = row.retrieve('jxRowData').row;
+            row.removeClass('jxGridRowSelected');
+            this.setCheckField(idx,false);
+          }, this);
+          selected.set('rows',[]);
+          selected.get('rowHeads').each(function(rowHead){
+            rowHead.removeClass('jxGridRowHeaderSelected');
+          });
+          selected.set('rowHeads',[]);
+        } else {
+            selected.get('columns').each(function(column){
+                for (i = 0; i < gridTableRows.length; i++) {
+                    gridTableRows[i].cells[column].removeClass('jxGridColumnSelected');
+                }
+            });
+            selected.set('columns',[]);
+
+            selected.get('columnHeads').each(function(rowHead){
+            rowHead.removeClass('jxGridColumnHeaderSelected');
+          },this);
+          selected.set('columnHeads',[]);
+        }
+    },
+    
+    /**
+     * Method: onCellClick
+     * dispatch clicking on a table cell
+     */
+    onCellClick: function(cell) {
+        if (cell) {
+            this.select(cell);
+        }
+    },
+    
+    /**
+     * Method: select
+     * dispatches the grid click to the various selection methods
+     */
+    select : function (cell) {
+        var data = cell.retrieve('jxCellData'),
+            options = this.options,
+            col;
+
+        if (options.cell && $defined(data.row) && $defined(data.index)) {
+          this.selectCell(cell);
+        }
+        
+        if (options.row && $defined(data.row)) {
+            this.selectRow(data.row);
+        }
+
+        if (options.column && $defined(data.index)) {
+            if (this.grid.row.useHeaders()) {
+                this.selectColumn(data.index - 1);
+            } else {
+                this.selectColumn(data.index);
+            }
+        }
+    },
+    
+    /**
+     * Method: selectCell
+     * select a cell
+     *
+     * Parameters: 
+     * cell - {DOMElement} the cell element to select
+     */
+    selectCell: function(cell) {
+        if (!this.options.cell) { return; }
+        var cells = this.selected.get('cells');
+        if (cell.hasClass('jxGridCellSelected')) {
+          cell.removeClass('jxGridCellSelected');
+          cells.erase(cell);
+          this.fireEvent('unselectCell', cell);
+        } else {
+          cell.addClass('jxGridCellSelected');
+          cells.push(cell);
+          this.fireEvent('selectCell', cell);
+        }
+    },
+    
+    updateSelectedRows: function() {
+      if (!this.options.row) { return; }
+      var options = this.options,
+          r = this.grid.gridTableBody.rows,
+          rows = [];
+          
+      for (var i=0; i<r.length; i++) {
+        if (r[i].hasClass('jxGridRowSelected')) {
+          rows.push(i);
+        }
+      }
+      this.selected.set('rows', rows);
+    },
+    
+    /**
+     * Method: selectRow
+     * Select a row and apply the jxGridRowSelected style to it.
+     *
+     * Parameters:
+     * row - {Integer} the row to select
+     */
+    selectRow: function (row, silently) {
+        if (!this.options.row) { return; }
+        var options = this.options,
+            r = this.grid.gridTableBody.rows,
+            tr = document.id((row >= 0 && row < r.length) ? r[row] : null),
+            rows = this.selected.get('rows'),
+            silently = $defined(silently) ? silently : false;
+        if (tr) {
+            if (tr.hasClass('jxGridRowSelected')) {
+                tr.removeClass('jxGridRowSelected');
+                this.setCheckField(row, false);
+                if (options.multiple && options.useCheckColumn) {
+                    if (options.checkAsHeader) {
+                        document.id(this.grid.rowColContainer).getElement('input').removeProperty('checked');
+                    } else {
+                        document.id(this.checkColumn).getElement('input').removeProperty('checked');
+                    }
+                }
+                //search array and remove this item
+                rows.erase(row);
+                if (!silently) {
+                  this.fireEvent('unselectRow', row);
+                }
+            } else {
+                tr.store('jxRowData', {row: row});
+                rows.push(row);
+                tr.addClass('jxGridRowSelected');
+                this.setCheckField(row, true);
+                if (!silently) {
+                  this.fireEvent('selectRow', row);
+                }
+            }
+
+            if (!this.options.multiple) {
+                var unselected = [];
+                this.getSelectedRows().each(function(row) {
+                  var idx;
+                  if (row !== tr) {
+                    idx = row.retrieve('jxRowData').row;
+                    row.removeClass('jxGridRowSelected');
+                    this.setCheckField(idx,false);
+                    rows.erase(row);
+                    unselected.push(idx);
+                    if (!silently) {
+                      this.fireEvent('unselectRow', row);
+                    }
+                  }
+                  
+                }, this);
+                if (unselected.length && !silently) {
+                  this.fireEvent('unselectRows', [unselected]);
+                }
+            }
+        }
+        this.selectRowHeader(row);
+    },
+
+    /**
+     * Method: setCheckField
+     */
+    setCheckField: function (row, checked) {
+        var grid = this.grid,
+            options = this.options,
+            check,
+            col,
+            cell;
+        if (options.useCheckColumn) {
+            if (options.checkAsHeader) {
+              cell = document.id(grid.rowTableBody.rows[row].cells[0]);
+            } else {
+              col = grid.columns.getIndexFromGrid(this.checkColumn.name);
+              cell = document.id(grid.gridTableBody.rows[row].cells[col]);
+            }
+            check = cell.getElement('.jxGridSelector')
+            check.set('checked', checked);
+        }
+    },
+    /**
+     * Method: selectRowHeader
+     * Apply the jxGridRowHeaderSelected 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 rows = this.grid.rowTableBody.rows,
+            cell = document.id((row >= 0 && row < rows.length) ? 
+                              rows[row].cells[0] : null),
+            cells;
+
+        if (!cell) {
+            return;
+        }
+        cells = this.selected.get('rowHeads');
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridRowHeaderSelected');
+            cells.erase(cell);
+        } else {
+          cell.addClass('jxGridRowHeaderSelected');
+          cells.push(cell);
+        }
+
+        if (!this.options.multiple) {
+          cells.each(function(c){
+            if (c !== cell) {
+              c.removeClass('jxGridRowHeaderSelected');
+              cells.erase(c);
+            }
+          },this);
+        }
+
+    },
+    /**
+     * Method: selectColumn
+     * Select a column.
+     * This deselects a previously selected column.
+     *
+     * Parameters:
+     * col - {Integer} the column to select
+     */
+    selectColumn: function (col) {
+        var gridTable = this.grid.gridTableBody,
+            cols = this.selected.get('columns'),
+            m = '',
+            i;
+        if (col >= 0 && col < gridTable.rows[0].cells.length) {
+            if (cols.contains(col)) {
+                //deselect
+                m = 'removeClass';
+                cols.erase(col);
+                this.fireEvent('unselectColumn', col);
+            } else {
+                //select
+                m = 'addClass';
+                cols.push(col);
+                this.fireEvent('selectColumn', col);
+            }
+            for (i = 0; i < gridTable.rows.length; i++) {
+                gridTable.rows[i].cells[col][m]('jxGridColumnSelected');
+            }
+
+            if (!this.options.multiple) {
+                cols.each(function(c){
+                  if (c !== col) {
+                      for (i = 0; i < gridTable.rows.length; i++) {
+                          gridTable.rows[i].cells[c].removeClass('jxGridColumnSelected');
+                      }
+                      cols.erase(c);
+                      this.fireEvent('unselectColumn', c);
+                  }
+                }, this);
+            }
+            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) {
+        var rows = this.grid.colTableBody;
+        if (rows.length === 0 || !this.grid.row.useHeaders()) {
+            return;
+        }
+
+        var cell = (col >= 0 && col < rows[0].cells.length) ?
+            rows[0].cells[col] : null;
+
+        if (cell === null) {
+            return;
+        }
+
+        cell = document.id(cell);
+        cells = this.selected.get('columnHeads');
+
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridColumnHeaderSelected');
+            cells.erase(cell);
+        } else {
+          cell.addClass('jxGridColumnHeaderSelected');
+          cells.push(cell);
+        }
+
+        if (!this.options.multiple) {
+          cells.each(function(c){
+            if (c !== cell) {
+              c.removeClass('jxGridColumnHeaderSelected');
+              cells.erase(c);
+            }
+          });
+        }
+    },
+    /**
+     * Method: checkSelection
+     * Checks whether a row's check box is/isn't checked and modifies the
+     * selection appropriately.
+     *
+     * Parameters:
+     * column - <Jx.Column> that created the checkbox
+     * field - <Jx.Field.Checkbox> instance that was checked/unchecked
+     * created the checkbox
+     */
+    checkSelection: function (event) {
+      var cell =  event.target.getParent('tr'),
+          row;
+      if (cell) {
+        row = cell.getParent().getChildren().indexOf(cell);
+        this.selectRow(row);
+      }
+    },
+    /**
+     * Method: checkAll
+     * Checks all checkboxes in the column the selector inserted.
+     */
+    checkAll: function () {
+        var grid = this.grid,
+            col,
+            rows,
+            selection = [],
+            checked = this.options.checkAsHeader ? 
+                          grid.rowColContainer.getElement('input').get('checked') :
+                          this.checkColumn.domObj.getElement('input').get('checked'),
+            event = checked ? 'selectRows' : 'unselectRows';
+
+        if (this.options.checkAsHeader) {
+            col = 0;
+            rows = grid.rowTableBody.rows;
+        } else {
+            col = grid.columns.getIndexFromGrid(this.checkColumn.name);
+            rows = grid.gridTableBody.rows;
+        }
+
+        $A(rows).each(function(row, idx) {
+            var check = row.cells[col].getElement('input');
+            if ($defined(check)) {
+                var rowChecked = check.get('checked');
+                if (rowChecked !== checked) {
+                    this.selectRow(idx, true);
+                    selection.push(idx);
+                }
+            }
+        }, this);
+        
+        this.fireEvent(event, [selection]);
+    },
+    
+    sort: function(dir) {
+      var grid = this.grid,
+          store = grid.store,
+          data = store.data,
+          gridTableBody= grid.gridTableBody,
+          gridParent = gridTableBody.getParent(),
+          useHeaders = grid.row.useHeaders(),
+          rowTableBody = grid.rowTableBody,
+          rowParent = rowTableBody.getParent(),
+          selected = this.getSelectedRows();
+      
+      // sorting only works for rows and when more than zero are selected
+      // in fact it is probably only useful if multiple selections are also enabled
+      // but that is not a hard rule for this method
+      if (!this.options.row || selected.length == 0) {
+        console.log('not sorting by selection, nothing to sort');
+        return;
+      }
+      
+      store.each(function(record, index) {
+        record.dom = {
+          cell: gridTableBody.childNodes[index],
+          row: useHeaders ? rowTableBody.childNodes[index] : null
+        };
+      });
+
+      gridTableBody.dispose();
+      if (useHeaders) {
+        rowTableBody.dispose();
+      }
+      selected.sort(function(a,b) {
+        return a.retrieve('jxRowData').row - b.retrieve('jxRowData').row;
+      }).each(function(row) {
+        console.log('moving row ' + row.retrieve('jxRowData').row + ' to beginning of array');
+        data.unshift(data.splice(row.retrieve('jxRowData').row,1)[0]);
+      });
+
+      if (dir == 'desc') {
+        data.reverse();
+      }
+
+      store.each(function(record, index) {
+        record.dom.cell.inject(gridTableBody);
+        record.dom.cell.store('jxRowData', {row: index});
+        if (useHeaders) {
+          record.dom.row.inject(rowTableBody);
+        }
+      });
+
+      if (gridParent) {
+        gridParent.adopt(gridTableBody);
+      }
+      if (useHeaders && rowParent) {
+        rowParent.adopt(rowTableBody);
+      }
+    },
+    
+    getSelectedRows: function() {
+      var rows = [],
+          selected = this.selected.get('rows'),
+          r = this.grid.gridTableBody.rows;
+      selected.each(function(row) {
+        var tr = document.id((row >= 0 && row < r.length) ? r[row] : null);
+        if (tr) {
+          rows.push(tr);
+        }
+      });
+      return rows;
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Grid.Prelighter
+
+description: Highlights rows, columns, cells, and headers in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Prelighter]
+
+...
+ */
+// $Id: grid.prelighter.js 981 2010-09-13 12:18:35Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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,
+    
+    name: 'Prelighter',
+    
+    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
+    },
+    /**
+     * 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.lighton = this.lighton.bind(this);
+        this.bound.lightoff = this.lightoff.bind(this);
+        this.bound.mouseleave = this.mouseleave.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.parent(grid);
+        this.grid = grid;
+        // this.grid.wantEvent('gridCellEnter');
+        // this.grid.wantEvent('gridCellLeave');
+        // this.grid.wantEvent('gridRowEnter');
+        // this.grid.wantEvent('gridRowLeave');
+        // this.grid.wantEvent('gridColumnEnter');
+        // this.grid.wantEvent('gridColumnLeave');
+        // this.grid.wantEvent('gridMouseLeave');
+        
+        this.grid.addEvent('gridCellEnter', this.bound.lighton);
+        this.grid.addEvent('gridCellLeave', this.bound.lightoff);
+        this.grid.addEvent('gridRowEnter', this.bound.lighton);
+        this.grid.addEvent('gridRowLeave', this.bound.lightoff);
+        this.grid.addEvent('gridColumnEnter', this.bound.lighton);
+        this.grid.addEvent('gridColumnLeave', this.bound.lightoff);
+        this.grid.addEvent('gridMouseLeave', this.bound.mouseleave);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridCellEnter', this.bound.lighton);
+            this.grid.removeEvent('gridCellLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridRowEnter', this.bound.lighton);
+            this.grid.removeEvent('gridRowLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridColumnEnter', this.bound.lighton);
+            this.grid.removeEvent('gridColumnLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridMouseLeave', this.bound.mouseleave);
+        }
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning prelighting on.
+     * 
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning prelighting off.
+     * 
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    deactivate: function (opt) {
+        this.options[opt] = false;
+    },
+    /**
+     * Method: lighton
+     */
+    lighton : function (cell) {
+        this.light(cell, true);
+
+    },
+    /**
+     * Method: lightoff
+     */
+    lightoff : function (cell) {
+        this.light(cell, false);
+
+    },
+    /**
+     * Method: light
+     * dispatches the event to the various prelight methods.
+     */
+    light: function (cell, on) {
+        var parent = cell.getParent(),
+            rowIndex = parent.getParent().getChildren().indexOf(parent),
+            colIndex = cell.getParent().getChildren().indexOf(cell);
+
+        if (this.options.cell) {
+            this.prelightCell(cell, on);
+        }
+        if (this.options.row) {
+            this.prelightRow(rowIndex, on);
+        }
+        if (this.options.column) {
+            this.prelightColumn(colIndex, on);
+        }
+        if (this.options.rowHeader) {
+            this.prelightRowHeader(rowIndex, on);
+        }
+        if (this.options.columnHeader) {
+            this.prelightColumnHeader(colIndex, on);
+        }
+    },
+
+    /**
+     * Method: prelightRowHeader
+     * apply the jxGridRowHeaderPrelight style to the header cell of a row.
+     * This removes the style from the previously pre-lit row header.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light the header cell of
+     */
+    prelightRowHeader : function (row, on) {
+        if ($defined(this.prelitRowHeader) && !on) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        } else if (on) {
+            this.prelitRowHeader = (row >= 0 && row < this.grid.rowTableBody.rows.length) ? this.grid.rowTableBody.rows[row].cells[0] : null;
+            if (this.prelitRowHeader) {
+                this.prelitRowHeader.addClass('jxGridRowHeaderPrelight');
+            }
+        }
+    },
+    /**
+     * Method: prelightColumnHeader
+     * apply the jxGridColumnHeaderPrelight style to the header cell of a column.
+     * This removes the style from the previously pre-lit column header.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light the header cell of
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumnHeader : function (col, on) {
+        if (this.grid.colTableBody.rows.length === 0) {
+            return;
+        }
+
+        if ($defined(this.prelitColumnHeader) && !on) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        } else if (on) {
+            this.prelitColumnHeader = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col] : null;
+            if (this.prelitColumnHeader) {
+                this.prelitColumnHeader.addClass('jxGridColumnHeaderPrelight');
+            }
+        }
+
+    },
+    /**
+     * Method: prelightRow
+     * apply the jxGridRowPrelight style to row.
+     * This removes the style from the previously pre-lit row.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightRow : function (row, on) {
+       if ($defined(this.prelitRow) && !on) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        } else if (on) {
+            this.prelitRow = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
+            if (this.prelitRow) {
+                this.prelitRow.addClass('jxGridRowPrelight');
+            }
+        }
+        this.prelightRowHeader(row, on);
+    },
+    /**
+     * Method: prelightColumn
+     * apply the jxGridColumnPrelight style to a column.
+     * This removes the style from the previously pre-lit column.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumn : function (col, on) {
+        if (col >= 0 && col < this.grid.gridTableBody.rows[0].cells.length) {
+            if ($defined(this.prelitColumn) && !on) {
+                for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                    this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+                }
+            } else if (on) {
+                this.prelitColumn = col;
+                for (i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                    this.grid.gridTableBody.rows[i].cells[col].addClass('jxGridColumnPrelight');
+                }
+            }
+            this.prelightColumnHeader(col, on);
+        }
+    },
+    /**
+     * Method: prelightCell
+     * apply the jxGridCellPrelight style to a cell.
+     * This removes the style from the previously pre-lit cell.
+     *
+     * Parameters:
+     * cell - the cell to lighton/off
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightCell : function (cell, on) {
+        if ($defined(this.prelitCell) && !on) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        } else if (on) {
+            this.prelitCell = cell;
+            if (this.prelitCell) {
+                this.prelitCell.addClass('jxGridCellPrelight');
+            }
+        }
+    },
+    
+    mouseleave: function() {
+        //turn off all prelights when the mouse leaves the grid
+        if ($defined(this.prelitCell)) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        }
+        if ($defined(this.prelitColumn)) {
+            for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+            }
+        }
+        if ($defined(this.prelitRow)) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        }
+        if ($defined(this.prelitColumnHeader)) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        }
+        if ($defined(this.prelitRowHeader)) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        }
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Grid.Sorter
+
+description: Enables column sorting in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Sorter]
+
+images:
+ - emblems.png
+...
+ */
+// $Id: grid.sorter.js 1002 2010-12-17 20:57:31Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.Sorter
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to sort the grid by a single column.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Sorter = new Class({
+  Family: 'Jx.Plugin.Grid.Sorter',
+  Extends: Jx.Plugin,
+  name: 'Sorter',
+
+  Binds: ['sort', 'modifyHeaders'],
+
+  /**
+   * Property: current
+   * refernce to the currently sorted column
+   */
+  current: null,
+
+  /**
+   * Property: direction
+   * tell us what direction the sort is in (either 'asc' or 'desc')
+   */
+  direction: null,
+
+  options: {
+    sortableClass: 'jxColSortable',
+    ascendingClass: 'jxGridColumnSortedAsc',
+    descendingClass: 'jxGridColumnSortedDesc'
+  },
+
+  /**
+   * 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.parent(grid);
+
+    this.grid = grid;
+
+    // this.grid.wantEvent('gridColumnClick');
+    this.grid.addEvent('gridColumnClick', this.sort);
+    this.grid.addEvent('doneCreateGrid', this.modifyHeaders);
+  },
+
+  /**
+   * APIMethod: detach
+   */
+  detach: function() {
+    if (this.grid) {
+        this.grid.removeEvent('gridColumnClick', this.sort);
+    }
+    this.grid = null;
+  },
+
+  /**
+   * Method: modifyHeaders
+   */
+  modifyHeaders: function() {
+    var grid = this.grid,
+        columnTable = grid.colObj,
+        store = grid.store,
+        c = this.options.sortableClass;
+    if (grid.columns.useHeaders()) {
+      grid.columns.columns.each(function(col, index) {
+        if (!col.isHidden() && col.isSortable()) {
+          var th = columnTable.getElement('.jxGridCol'+index);
+          th.addClass(c);
+        }
+      });
+    }
+  },
+
+  /**
+   * Method: sort
+   * called when a grid header is clicked.
+   *
+   * Parameters:
+   * cell - The cell clicked
+   */
+  sort: function(el) {
+    var current = this.current,
+        grid = this.grid,
+        gridTableBody = grid.gridTableBody,
+        gridParent = gridTableBody.getParent(),
+        rowTableBody = grid.rowTableBody,
+        rowParent = rowTableBody.getParent(),
+        useHeaders = grid.row.useHeaders(),
+        store = grid.store,
+        sorter = store.getStrategy('sort'),
+        data = el.retrieve('jxCellData'),
+        dir = 'asc',
+        opt = this.options;
+    
+    if ($defined(data.column) && data.column.isSortable()){
+      if (el.hasClass(opt.ascendingClass)) {
+        el.removeClass(opt.ascendingClass).addClass(opt.descendingClass);
+        dir = 'desc';
+      } else if (el.hasClass(opt.descendingClass)) {
+        el.removeClass(opt.descendingClass).addClass(opt.ascendingClass);
+      } else {
+        el.addClass(opt.ascendingClass);
+      }
+      if (current && el != current) {
+        current.removeClass(opt.ascendingClass).removeClass(opt.descendingClass);
+      }
+      this.current = el;
+      
+      this.grid.fireEvent('gridSortStarting');
+      
+      if ($defined(data.column.options.sort) && Jx.type(data.column.options.sort) == 'function') {
+        data.column.options.sort(dir);
+      } else {
+        if (sorter) {
+          gridTableBody.dispose();
+          if (useHeaders) {
+            rowTableBody.dispose();
+          }
+          store.each(function(record, index) {
+            record.dom = {
+              cell: gridTableBody.childNodes[index],
+              row: useHeaders ? rowTableBody.childNodes[index] : null
+            };
+          });
+    
+          sorter.sort(data.column.name, null, dir);
+    
+          store.each(function(record, index) {
+            record.dom.cell.inject(gridTableBody);
+            if (useHeaders) {
+              record.dom.row.inject(rowTableBody);
+            }
+          });
+    
+          if (gridParent) {
+            gridParent.adopt(gridTableBody);
+          }
+          if (useHeaders && rowParent) {
+            rowParent.adopt(rowTableBody);
+          }
+        }
+      }
+      this.grid.fireEvent('gridSortFinished');
+    }
+  }
+});/*
+---
+
+name: Jx.Plugin.Grid.Resize
+
+description: Enables column resizing in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Resize]
+
+...
+ */
+// $Id: grid.resize.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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,
+    
+    name: 'Resize',
+    
+    Binds: ['createHandles','removeHandles'],
+    options: {
+        /**
+         * Option: column
+         * set to true to make column widths resizeable
+         */
+        column: false,
+        /**
+         * Option: row
+         * set to true to make row heights resizeable
+         */
+        row: false,
+        /**
+         * Option: tooltip
+         * the tooltip to display for the draggable portion of the
+         * cell header, localized with MooTools.lang.get('Jx','plugin.resize').tooltip for default
+         */
+        tooltip: ''
+    },
+    /**
+     * Property: els
+     * the DOM elements by which the rows/columns are resized.
+     */
+    els: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * Property: drags
+     * the Drag instances
+     */
+    drags: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and connects it to the grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+          return;
+      }
+      this.parent(grid);
+      this.grid = grid;
+      if (grid.columns.useHeaders()) {
+        this.grid.addEvent('doneCreateGrid', this.createHandles);
+        this.grid.addEvent('beginCreateGrid', this.removeHandles);
+        this.createHandles();
+      }
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+      this.parent();
+      if (this.grid) {
+          this.grid.removeEvent('doneCreateGrid', this.createHandles);
+          this.grid.removeEvent('beginCreateGrid', this.removeHandles);
+      }
+      this.grid = null;
+    },
+
+    /**
+     * APIMethod: activate
+     */
+    activate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = true;
+        }
+        if (this.grid.columns.useHeaders()) {
+          this.createHandles();
+        }
+    },
+
+    /**
+     * APIMethod: deactivate
+     */
+    deactivate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = false;
+        }
+        this.createHandles();
+    },
+    /**
+     * Method: removeHandles
+     * clean up any handles we created
+     */
+    removeHandles: function() {
+        ['column','row'].each(function(option) {
+          this.els[option].each(function(el) { el.dispose(); } );
+          this.els[option] = [];
+          this.drags[option].each(function(drag){ drag.detach(); });
+          this.drags[option] = [];
+        }, this);
+    },
+    /**
+     * Method: createHandles
+     * create handles that let the user drag to resize columns and rows
+     */
+    createHandles: function() {
+      var grid = this.grid,
+          store = grid.store;
+      this.removeHandles();
+      if (this.options.column && grid.columns.useHeaders()) {
+        grid.columns.columns.each(function(col, idx) {
+          if (col.isResizable() && !col.isHidden()) {
+            var colEl = grid.colObj.getElement('.jxGridCol'+idx+ ' .jxGridCellContent');
+            var el = new Element('div', {
+              'class':'jxGridColumnResize',
+              title: this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip),
+              events: {
+                dblclick: function() {
+                  // size to fit?
+                }
+              }
+            }).inject(colEl);
+            this.els.column.push(el);
+            this.drags.column.push(new Drag(el, {
+                limit: {y:[0,0]},
+                snap: 2,
+                onBeforeStart: function(el) {
+                  var l = el.getPosition(el.parentNode).x.toInt();
+                  el.setStyles({
+                    left: l,
+                    right: null
+                  });
+
+                },
+                onStart: function(el) {
+                  var l = el.getPosition(el.parentNode).x.toInt();
+                  el.setStyles({
+                    left: l,
+                    right: null
+                  });
+                },
+                onDrag: function(el) {
+                    var w = el.getPosition(el.parentNode).x.toInt();
+                    col.setWidth(w);
+                },
+                onComplete: function(el) {
+                  el.setStyle('left', null);
+                }
+            }));
+          }
+        }, this);
+      }
+      //if (this.options.row && this.grid.row.useHeaders()) {}
+    },
+    /**
+     * Method: createText
+     * respond to a language change by updating the tooltip
+     */
+    changeText: function (lang) {
+      this.parent();
+      var txt = this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip);
+      ['column','row'].each(function(option) {
+        this.els[option].each(function(el) { el.set('title',txt); } );
+      }, this);
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid.Editor
+
+description: Enables inline editing in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+ - More/Keyboard
+
+provides: [Jx.Plugin.Grid.Editor]
+
+images:
+ - icons.png
+...
+ */
+// $Id: grid.editor.js 981 2010-09-13 12:18:35Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.Editor
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to enable inline editing within a cell
+ *
+ * Original selection code from Jx.Grid's original class
+ *
+ * License:
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Conrad Barthelmes.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Editor = new Class({
+
+    Extends : Jx.Plugin,
+    
+    name: 'Editor',
+    
+    Binds: ['activate','deactivate','changeText','onCellClick'],
+
+    options : {
+      /**
+       * Option: enabled
+       * Determines if inline editing is avaiable
+       */
+      enabled : true,
+      /**
+       * Option: blurDelay
+       * Set the time in miliseconds when the inputfield/popup shall hide. When
+       * the user refocuses the input/popup within this time, the timeout will be cleared
+       *
+       * set to 'false' if no hiding on blur is wanted
+       */
+      blurDelay : 500,
+      /**
+       * Option: popup
+       *
+       * Definitions for a PopUp to use.
+       * - use        - determines whether to use a PopUp or simply the input
+       * - useLabel   - determines whether to use labels on top of the input.
+       *                Text will be the column header
+       * - useButtons - determines whether to use Submit and Cancel Buttons
+       * - buttonLabel.submit - Text for Submit Button, uses MooTools.lang.get('Jx', 'plugin.editor').submitButton for default
+       * - buttonLabel.cancel - Text for Cancel Button, uses MooTools.lang.get('Jx', 'plugin.editor').cancelButton for default
+       */
+      popup : {
+        use           : true,
+        useLabels     : false,
+        useButtons    : true,
+        button        : {
+          submit : {
+            label : '',
+            image : 'images/accept.png'
+          },
+          cancel : {
+            label : '',
+            image : 'images/cancel.png'
+          }
+        },
+        template: '<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'
+      },
+      /**
+       * Option {boolean} validate
+       * - set to true to have all editable input fields as mandatory field
+       *   if they don't have 'mandatory:true' in their colOptions
+       */
+      validate : true,
+      /**
+       * Option: {Array} fieldOptions with objects
+       * Contains objects with options for the Jx.Field instances to show up.
+       * Default options will be added automatically if custom options are entered.
+       *
+       * Preferences:
+       *   field             - Default * for all types or the name of the column in the store (Jx.Store)
+       *   type              - Input type to show (Text, Password, Textarea, Select, Checkbox)
+       *   options           - All Jx.Field options for this column. More options depend on what type you are using.
+       *                       See Jx.Form.[yourField] for details
+       *   validatorOptions: - See Jx.Plugin.Field.Validator Options for details
+       *                       will only be used if this.options.validate is set to true
+       */
+      fieldOptions : [
+        {
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        }
+      ],
+      /**
+       * Option: {Boolean} fieldFormatted
+       * Displays the cell value also inside the input field as formatted
+       */
+      fieldFormatted : true,
+      /**
+       * Option cellChangeFx
+       * set use to false if no highlighting effect is wanted.
+       *
+       * this is just an idea how successfully changing could be highlighed for the user
+       */
+      cellChangeFx : {
+        use     : true,
+        success : '#090',
+        error   : '#F00'
+      },
+      /**
+       * Option cellOutline
+       * shows an outline style to the currently active cell to make it easier to see
+       * which cell is active
+       */
+      cellOutline : {
+        use   : true,
+        style : '2px solid #88c3e7'
+      },
+      /**
+       * Option: useKeyboard
+       * Set to false if no keyboard support is needed
+       */
+      useKeyboard : true,
+      /**
+       * Option: keys
+       * Contains the event codes for several commands that can be used when
+       * a field is active. Syntax is the same like for the Mootools Keyboard Class
+       * http://mootools.net/docs/more/Interface/Keyboard
+       */
+      keys : {
+        'ctrl+shift+enter' : 'saveNGoUp',
+        'tab'              : 'saveNGoRight',
+        'ctrl+enter'       : 'saveNGoDown',
+        'shift+tab'        : 'saveNGoLeft',
+        'enter'            : 'saveNClose',
+        'ctrl+up'          : 'cancelNGoUp',
+        'ctrl+right'       : 'cancelNGoRight',
+        'ctrl+down'        : 'cancelNGoDown',
+        'ctrl+left'        : 'cancelNGoLeft',
+        'esc'              : 'cancelNClose',
+        'up'               : 'valueIncrement',
+        'down'             : 'valueDecrement'
+      },
+      /**
+       * Option: keyboardMethods
+       *
+       * can be used to overwrite existing keyboard methods that are used inside
+       * this.options.keys - also possible to add new ones.
+       * Functions are bound to the editor plugin when using 'this'
+       *
+       * example:
+       *  keys : {
+       *    'ctrl+u' : 'cancelNGoRightNDown'
+       *  },
+       *  keyboardMethods: {
+       *    'cancelNGoRightNDown' : function(ev){
+       *      ev.preventDefault();
+       *      this.getNextCellInRow(false);
+       *      this.getNextCellInCol(false);
+       *    }
+       *  }
+       */
+      keyboardMethods : {},
+      /**
+       * Option: keypressLoop
+       * loop through the grid when pressing TAB (or some other method that uses
+       * this.getNextCellInRow() or this.getPrevCellInRow()). If set to false,
+       * the input field/popup will not start at the opposite site of the grid
+       * Defaults to true
+       */
+      keypressLoop : true,
+      /**
+       * Option: linkClickListener
+       * disables all click events on links that are formatted with Jx.Formatter.Uri
+       * - otherwise the link will open directly instead of open the input editor)
+       * - hold [ctrl] to open the link in a new tab
+       */
+      linkClickListener : true
+    },
+    classes: ['jxGridEditorPopup', 'jxGridEditorPopupInnerWrapper'],
+    /**
+     * Property: activeCell
+     *
+     * Containing Objects:
+     *   field        : Reference to the Jx.Field instance that will be created
+     *   cell         : Reference to the cell inside the table 
+     *   span         : Reference to the Dom Element inside the selected cell of the grid
+     *   oldValue     : Old value of the cell from the grid's store
+     *   newValue     : Object with <data> and <error> for better validation possibilites
+     *   timeoutId    : TimeoutId if the focus blurs the input.
+     *   data         : Reference to the cell data
+     *   fieldOptions : Reference to the field options of this column
+     */
+    activeCell : {
+      field       : null,
+      cell        : null,
+      span        : null,
+      oldValue    : null,
+      newValue    : { data: null, error: false },
+      timeoutId   : null,
+      data        : {},
+      fieldOptions: {}
+    },
+    /**
+     * Property : popup
+     *
+     * References to all contents within a popup (only 1 popup for 1 grid initialization)
+     *
+     * COMMENT: I don't know how deep we need to go into that.. innerWrapper and closeLink probably don't need
+     * own references.. I just made them here in case they are needed at some time..
+     *
+     * Containing Objects:
+     *   domObj         : Reference to the Dom Element of the popup (absolutely positioned)
+     *   innerWrapper   : Reference to the inner Wrapper inside the popup to provide relative positioning
+     *   closeIcon      : Reference to the Dom Element of a little [x] in the upper right to close it (not saving)
+     *   buttons        : References to all Jx.Buttons used inside the popup
+     *   buttons.submit : Reference to the Submit Button
+     *   buttons.cancel : Reference to the Cancel Button
+     */
+    popup : {
+      domObj       : null,
+      innerWarpper : null,
+      closeIcon    : null,
+      button       : {
+        submit : null,
+        cancel : null
+      }
+    },
+    /**
+     * Property: keyboard
+     * Instance of a Mootols Keyboard Class
+     */
+    keyboard : null,
+    /**
+     * Property keyboardMethods
+     * Editing and grid functions for keyboard functionality.
+     * Methods are defined and implemented inside this.attach() because of referencing troubles
+     */
+    keyboardMethods : {},
+    /**
+     * APIMethod: init
+     * construct a new instance of the plugin.  The plugin must be attached
+     * to a Jx.Grid instance to be useful though.
+     */
+    init: function() {
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * @var {Object} grid - Instance of Class Jx.Grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+        return;
+      }
+      this.parent(grid);
+      this.grid = grid;
+
+      //this.grid.gridTableBody.addEvent('click', this.onCellClick);
+      // this.grid.wantEvent('gridCellClick');
+      this.grid.addEvent('gridCellClick', this.onCellClick);
+
+      /*
+       * add default field options to the options in case some new options were entered
+       * to be still able to use them for the rest of the fields
+       */
+      if(this.getFieldOptionsByColName('*').field != '*') {
+        this.options.fieldOptions.unshift({
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        });
+      }
+
+      /**
+       * set the keyboard methods here to have a correct reference to the instance of
+       * the editor plugin
+       *
+       * @todo other names maybe? or even completely different way of handling the keyboard events?
+       * @todo more documentation than method name
+       */
+      var self = this;
+      this.keyboardMethods = {
+        saveNClose     : function(ev) {
+          if(self.activeCell.fieldOptions.type != 'Textarea' || (self.activeCell.fieldOptions.type == 'Textarea' && ev.key != 'enter')) {
+            self.deactivate();
+          }
+        },
+        saveNGoUp      : function(ev) {ev.preventDefault();self.getPrevCellInCol();},
+        saveNGoRight   : function(ev) {ev.preventDefault();self.getNextCellInRow();},
+        saveNGoDown    : function(ev) {ev.preventDefault();self.getNextCellInCol();},
+        saveNGoLeft    : function(ev) {ev.preventDefault();self.getPrevCellInRow();},
+        cancelNClose   : function(ev) {ev.preventDefault();self.deactivate(false);},
+        cancelNGoUp    : function(ev) {ev.preventDefault();self.getPrevCellInCol(false);},
+        cancelNGoRight : function(ev) {ev.preventDefault();self.getNextCellInRow(false);},
+        cancelNGoDown  : function(ev) {ev.preventDefault();self.getNextCellInCol(false);},
+        cancelNGoLeft  : function(ev) {ev.preventDefault();self.getPrevCellInRow(false);},
+        valueIncrement : function(ev) {ev.preventDefault();self.cellValueIncrement(true);},
+        valueDecrement : function(ev) {ev.preventDefault();self.cellValueIncrement(false);}
+      };
+
+      var keyboardEvents = {};
+      for(var i in this.options.keys) {
+        if($defined(this.keyboardMethods[this.options.keys[i]])) {
+          keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+        }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+          keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+        }else if(Jx.type(this.options.keys[i]) == 'function') {
+          keyboardEvents[i] = this.options.keys[i].bind(self);
+        }else{
+          $defined(console) ? console.warn("keyboard method %o not defined", this.options.keys[i]) : false;
+        }
+      }
+
+      // initalize keyboard support but do NOT activate it (this is done inside this.activate()).
+      this.keyboard = new Keyboard({
+        events: keyboardEvents
+      });
+
+      this.addFormatterUriClickListener();
+    },
+    /**
+     * APIMethod: detach
+     * detaches from the grid
+     * 
+     * @return void
+     */
+    detach: function() {
+      if (this.grid) {
+        this.grid.removeEvent('gridCellClick', this.onCellClick);
+      }
+      this.grid = null;
+      this.keyboard = null;
+    },
+    /**
+     * APIMethod: enable
+     * enables the grid 'externally'
+     *
+     * @return void
+     */
+    enable : function () {
+      this.options.enabled = true;
+    },
+    /**
+     * APIMethod: disable
+     * disables the grid 'externally'
+     *
+     * @var Boolean close - default true: also closes the currently open input/popup
+     * @var Boolean save - default false: also changes the currently open input/popup
+     * @return void
+     */
+    disable : function(close, save) {
+      close = $defined(close) ? close : true;
+      save = $defined(save) ? save : false;
+      if(close && this.activeCell.cell != null) {
+        this.deactivate(save);
+      }
+      this.options.enabled = false;
+    },
+
+    /**
+     * Method: onCellClick
+     * dispatch clicking on a table cell
+     */
+    onCellClick: function(cell) {
+      this.activate(cell);
+    },
+    /**
+     * Method: activate
+     * activates the input field or breaks up if conditions are not fulfilled
+     *
+     * @todo Field validation
+     *
+     * Parameters:
+     * @var {Object} cell Table Element
+     * @return void
+     */
+    activate: function(cell) {
+      // if not enabled or the cell is null, do nothing at all
+      if(!this.options.enabled || !cell)
+        return;
+
+      // activate can be called by clicking on the same cell or a
+      // different one
+      if (this.activeCell.cell) {
+        if (this.activeCell.cell != cell) {
+          if (!this.deactivate()) {
+            return;
+          }
+        } else {
+          // they are the same, ignore?
+          return;
+        }
+      }
+      
+      var data  = this.grid.getCellData(cell); //.retrieve('jxCellData');
+
+      if (!data || !$defined(data.row) || !$defined(data.column)) {
+        if($defined(console)) {
+          console.warn('out of grid %o',cell);
+          console.warn('data was %o', data);
+        }
+        return;
+      }
+
+      // column marked as not editable
+      if (!data.column.options.isEditable) {
+        return;
+      }
+
+      if (this.activeCell.timeoutId) {
+        clearTimeout(activeCell.timeoutId);
+      }
+
+      // set active record index to selected row
+      this.grid.store.moveTo(data.row);
+
+      // set up the data objects we need
+      var options = this.options,
+          grid = this.grid,
+          store = grid.getStore(),
+          index = grid.columns.getIndexFromGrid(data.column.name),
+          colOptions = data.column.options,
+          activeCell = {
+            oldValue      : store.get(data.column.name),
+            newValue      : {data: null, error: false},
+            fieldOptions  : this.getFieldOptionsByColName(data.column.name),
+            data          : data,
+            cell          : cell,
+            span          : cell.getElement('span.jxGridCellContent'),
+            validator     : null,
+            field         : null,
+            timeoutId     : null
+          },
+          jxFieldOptions = activeCell.fieldOptions.options,
+          oldValue,
+          groups,
+          k,
+          n;
+
+      // check if this column has special validation settings - 
+      // otherwise use default from this.options.validate
+      if(!$defined(data.column.options.validate) || typeof(data.column.options.validate) != 'boolean') {
+        data.column.options.validate = options.validate;
+        cell.store('jxCellData', data);
+      }
+
+      // check for different input field types
+      switch(activeCell.fieldOptions.type) {
+        case 'Text':
+        case 'Color':
+        case 'Password':
+        case 'File':
+          jxFieldOptions.value = activeCell.oldValue;
+          break;
+        case 'Textarea':
+          jxFieldOptions.value = activeCell.oldValue.replace(/<br \/>/gi, '\n');
+          break;
+        case 'Select':
+          // find out which visible value fits to the value inside
+          // <option>{value}</option> and set it to selected
+          jxFieldOptions.value = oldValue  = activeCell.oldValue.toString();
+          function setCombos(opts, oldValue) {
+            for(var i = 0, j = opts.length; i < j; i++) {
+              if(opts[i].value == oldValue) {
+                opts[i].selected = true;
+              }else{
+                opts[i].selected = false;
+              }
+            }
+            return opts;
+          }
+
+          if(jxFieldOptions.comboOpts) {
+            jxFieldOptions.comboOpts = setCombos(jxFieldOptions.comboOpts, oldValue);
+          }else if(jxFieldOptions.optGroups) {
+            groups = jxFieldOptions.optGroups;
+            for(k = 0, n = groups.length; k < n; k++) {
+              groups[k].options = setCombos(groups[k].options, oldValue);
+            }
+            jxFieldOptions.optGroups = groups;
+          }
+          break;
+        case 'Radio':
+        case 'Checkbox':
+        default:
+          $defined(console) ? console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.", activeCell.fieldOptions.type) : false;
+          return;
+          break;
+      }
+
+      // update the 'oldValue' to the formatted style, to compare the new value with the formatted one instead with the non-formatted-one
+      if(options.fieldFormatted && colOptions.renderer.options.formatter != null) {
+        if(!$defined(colOptions.fieldFormatted) || colOptions.fieldFormatted == true ) {
+          jxFieldOptions.value = colOptions.renderer.options.formatter.format(jxFieldOptions.value);
+          activeCell.oldValue = jxFieldOptions.value;
+        }
+      }
+
+      // create jx.field
+      activeCell.field = new Jx.Field[activeCell.fieldOptions.type.capitalize()](jxFieldOptions);
+      // create validator
+      if(options.validate && colOptions.validate) {
+        activeCell.validator = new Jx.Plugin.Field.Validator(activeCell.fieldOptions.validatorOptions);
+        activeCell.validator.attach(activeCell.field);
+      }
+
+      // store properties of the active cell
+      this.activeCell = activeCell;
+      this.setStyles(cell);
+
+      if(options.useKeyboard) {
+        this.keyboard.activate();
+      }
+
+      // convert a string to an integer if somebody entered a numeric value in quotes, if it failes: make false
+      if(typeof(options.blurDelay) == 'string') {
+        options.blurDelay = options.blurDelay.toInt() ? options.blurDelay.toInt() : false;
+      }
+
+      // add a onblur() and onfocus() event to the input field if enabled.
+      if(options.blurDelay !== false && typeof(options.blurDelay) == 'number') {
+        activeCell.field.field.addEvents({
+          // activate the timeout to close the input/poup
+          'blur' : function() {
+            // @todo For some reason, webkit does not clear the timeout correctly when navigating through the grid with keyboard
+            clearTimeout(activeCell.timeoutId);
+            activeCell.timeoutId = this.deactivate.delay(this.options.blurDelay);
+          }.bind(this),
+          // clear the timeout when the user focusses again
+          'focus' : function() {
+            clearTimeout(activeCell.timeoutId);
+          }, 
+          // clear the timeout when the user puts the mouse over the input
+          'mouseover' : function() {
+            clearTimeout(activeCell.timeoutId);
+          }
+        });
+        if(this.popup.domObj != null) {
+          this.popup.domObj.addEvent('mouseenter', function() {
+            clearTimeout(activeCell.timeoutId);
+          });
+        }
+      }
+
+      activeCell.field.field.focus();
+    }, 
+    /**
+     * APIMethod: deactivate
+     * hides the currently active field and stores the new entered data if the
+     * value has changed
+     *
+     * Parameters:
+     * @var {Boolean} save (Optional, default: true) - force aborting
+     * @return true if no data error occured, false if error (popup/input stays visible)
+     */
+    deactivate: function(save) {
+      var newValue = {data : null, error : false},
+          index,
+          activeCell = this.activeCell,
+          grid = this.grid,
+          store = grid.store,
+          options = this.options,
+          highlighter,
+          cellBg;
+
+      clearTimeout(activeCell.timeoutId);
+
+      if(activeCell.field !== null) {
+        save = $defined(save) ? save : true;
+
+
+        // update the value in the column
+        if(save && activeCell.field.getValue().toString() != activeCell.oldValue.toString()) {
+          store.moveTo(activeCell.data.row);
+          /*
+           * @todo webkit shrinks the rows when the value is updated... but refreshing the grid
+           *       immidiately returns in a wrong calculating of the cell position (getCoordinates)
+           */
+          switch (activeCell.fieldOptions.type) {
+            case 'Select':
+              index = activeCell.field.field.selectedIndex;
+              newValue.data = document.id(activeCell.field.field.options[index]).get('value');
+              break;
+            case 'Textarea':
+              newValue.data = activeCell.field.getValue().replace(/\n/gi, '<br />');
+              break;
+            default:
+              newValue.data = activeCell.field.getValue();
+              break;
+          }
+          if (save) {
+            activeCell.newValue.data = newValue.data;
+          }
+          // validation only if it should be saved!
+          if (activeCell.validator != null && !activeCell.validator.isValid()) {
+            newValue.error = true;
+            activeCell.field.field.focus.delay(50, activeCell.field.field);
+          }
+        } else {
+          activeCell.span.show();
+        }
+
+        // var data = activeCell.cell.retrieve('jxCellData');
+        if (save && newValue.data != null && newValue.error == false) {
+          store.set(activeCell.data.column.name, newValue.data);
+          this.addFormatterUriClickListener();
+        // else show error message and cell
+        } else if (newValue.error == true) {
+          activeCell.span.show();
+        }
+
+        // update reference to activeCell
+        if ($defined(activeCell.data.row) && $defined(activeCell.data.index)) {
+          var colIndex = grid.row.useHeaders() ? activeCell.data.index-1 : activeCell.data.index;
+          this.activeCell.cell = grid.gridTableBody.rows[this.activeCell.data.row].cells[colIndex];
+        }
+
+        if (options.useKeyboard) {
+          activeCell.field.removeEvent('keypress', this.setKeyboard);
+        }
+
+        /**
+         * COMMENT: this is just an idea how changing a value could be visualized
+         * we could also pass an Fx.Tween element?
+         * the row could probably be highlighted as well?
+         */
+        if(options.cellChangeFx.use) {
+          highlighter = new Fx.Tween(this.activeCell.cell, {
+            duration: 250,
+            onComplete: function(ev) {
+              this.element.removeProperty('style');
+            }
+          });
+          cellBg = activeCell.cell.getStyle('background-color');
+          cellBg = cellBg == 'transparent' ? '#fff' : cellBg;
+          if (newValue.data != null && newValue.error == false) {
+            highlighter.start('background-color',options.cellChangeFx.success, cellBg);
+          } else if (newValue.error){
+            highlighter.start('background-color',options.cellChangeFx.error, cellBg);
+          }
+        }
+
+        // check for error and keep input field alive
+        if (newValue.error) {
+          if(options.cellChangeFx.use) {
+            activeCell.field.field.highlight(options.cellChangeFx.error);
+          }
+          activeCell.field.field.setStyle('border','1px solid '+options.cellChangeFx.error);
+          activeCell.field.field.focus();
+          return false;
+        // otherwise hide it
+        }else{
+          this.keyboard.deactivate();
+          this.unsetActiveField();
+          return true;
+        }
+      }
+    },
+    /**
+     * Method: setStyles
+     * 
+     * sets some styles for the Jx.Field elements...
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     * @return void
+     */
+    setStyles : function(cell) {
+      var styles, 
+          size,
+          options = this.options,
+          activeCell = this.activeCell;
+      // popup
+      if (options.popup.use) {
+        if (options.popup.useLabels) {
+          activeCell.field.options.label = activeCell.data.column.options.header;
+          activeCell.field.render();
+        }
+        styles = {
+          field : {
+            'width'  : activeCell.field.type == 'Select' ?
+                         cell.getContentBoxSize().width + 5 + "px" :
+                         cell.getContentBoxSize().width - 14 + "px",
+            'margin' : 'auto 0'
+          }
+        };
+        activeCell.field.field.setStyles(styles.field);
+        this.showPopUp(cell);
+      // No popup
+      } else {
+        size   = cell.getContentBoxSize();
+        styles = {
+          domObj : {
+            position: 'absolute'
+          },
+          field : {
+            width : size.width + "px",
+            'margin-left' : 0
+          }
+        };
+
+        activeCell.field.domObj.setStyles(styles.domObj);
+        activeCell.field.field.setStyles(styles.field);
+
+        activeCell.field.domObj.inject(document.body);
+        Jx.Widget.prototype.position(activeCell.field.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+
+        activeCell.span.hide();
+      }
+
+      // COMMENT: an outline of the cell helps identifying the currently active cell
+      if(options.cellOutline.use) {
+        cell.setStyle('outline', options.cellOutline.style);
+      }
+    },
+    /**
+     * Method: showPopUp
+     *
+     * Shows the PopUp of of the editor if it already exists, otherwise calls Method
+     * this.createPopUp
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    showPopUp : function(cell) {
+      if(this.popup.domObj != null) {
+        Jx.Widget.prototype.position(this.popup.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+        this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+        this.popup.domObj.show();
+        this.setPopUpButtons();
+        this.setPopUpStylesAfterRendering();
+      }else{
+        this.createPopUp(cell);
+      }
+    },
+    /**
+     * Method: createPopUp
+     *
+     * creates the popup for the requested cell.
+     *
+     * COMMENT: this could also be an jx.dialog..? if we use jx.dialog, maybe without a title element?
+     *          Maybe a jx.dialog is too much for this little thing?
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    createPopUp : function(cell) {
+      var coords = cell.getCoordinates(),
+          self      = this, popup  = null, innerWrapper = null,
+          closeIcon = null, submit = null, cancel       = null,
+          template  = Jx.Widget.prototype.processTemplate(this.options.popup.template, this.classes);
+
+      popup = template.jxGridEditorPopup;
+
+      innerWrapper = template.jxGridEditorPopupInnerWrapper;
+      /**
+       * COMMENT: first positioning is always in the top left of the grid..
+       * don't know why
+       * manual positioning is needed..?
+       */
+      popup.setStyles({
+        'left' : coords.left+'px',
+        'top'  : coords.top +'px'
+      });
+      /*
+      Jx.Widget.prototype.position(popup, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+      });
+      */
+
+      this.popup.domObj         = popup;
+      this.popup.innerWrapper   = innerWrapper;
+      this.popup.closeIcon      = closeIcon;
+      this.setPopUpButtons();
+
+      this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+      this.popup.domObj.inject(document.body);
+
+      this.setPopUpStylesAfterRendering();
+    },
+    /**
+     * Method: setPopUpStylesAfterRendering
+     *
+     * - measures the widths of the buttons to set a new min-width for the popup
+     *   because custom labels could break the min-width and force a line-break
+     * - resets the size of the field to make it fit inside the popup (looks nicer)
+     *
+     * @return void
+     */
+    setPopUpStylesAfterRendering: function() {
+      if(this.options.popup.useButtons && this.popup.button.submit != null && this.popup.button.cancel != null) {
+        this.popup.domObj.setStyle('min-width', this.popup.button.submit.domObj.getSize().x + this.popup.button.cancel.domObj.getSize().x + "px");
+      }else{
+        if(this.popup.button.submit != null)
+          this.popup.button.submit.domObj.hide();
+        if(this.popup.button.cancel != null)
+          this.popup.button.cancel.domObj.hide();
+      }
+      this.activeCell.field.field.setStyle('width',
+        this.activeCell.field.type == 'Select' ?
+          this.popup.domObj.getSize().x - 7 + "px" :
+          this.popup.domObj.getSize().x - 17 + "px");
+    },
+    /**
+     * Method: setPopUpButtons
+     * creates the PopUp Buttons if enabled in options or deletes them if set to false
+     *
+     * @return void
+     */
+    setPopUpButtons : function() {
+      var self = this,
+          button = {
+            submit : null,
+            cancel : null
+          };
+      // check if buttons are needed, innerWrapper exists and no buttons already exist
+      if(this.options.popup.useButtons && this.popup.innerWrapper != null && this.popup.button.submit == null) {
+        button.submit = new Jx.Button({
+          label : this.options.popup.button.submit.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'submitButton'}) :
+                    this.getText(this.options.popup.button.submit.label),
+          image : this.options.popup.button.submit.image,
+          onClick: function() {
+            self.deactivate(true);
+          }
+        }).addTo(this.popup.innerWrapper);
+        button.cancel = new Jx.Button({
+          label : this.options.popup.button.cancel.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'cancelButton'}) :
+                    this.getText(this.options.popup.button.cancel.label),
+          image : this.options.popup.button.cancel.image,
+          onClick: function() {
+            self.deactivate(false);
+          }
+        }).addTo(this.popup.innerWrapper);
+      }else if(this.options.popup.useButtons && this.popup.button.submit != null) {
+        button = {
+          submit : this.popup.button.submit,
+          cancel : this.popup.button.cancel
+        };
+      // check if buttons are not needed and buttons already exist to remove them
+      }else if(this.options.popup.useButtons == false && this.popup.button.submit != null) {
+        this.popup.button.submit.cleanup();
+        this.popup.button.cancel.cleanup();
+      }
+
+      this.popup.button = button;
+    },
+    /**
+     * Method: unsetActiveField
+     * resets the activeField and hides the popup
+     *
+     * @return void
+     */
+    unsetActiveField: function() {
+      this.activeCell.field.destroy();
+      if(this.popup.domObj != null) {
+        this.popup.domObj.removeEvent('mouseenter');
+        this.popup.domObj.hide();
+      }
+
+      this.activeCell.cell.setStyle('outline', '0px');
+
+      this.activeCell = {
+        field         : null,
+        oldValue      : null,
+        newValue      : { data: null, error: false},
+        cell          : null,
+        span          : null,
+        timeoutId     : null,
+        //popup         : null,   // do not destroy the popup, it might be used again
+        data           : {},
+        fieldOptions  : {},
+        validator     : null
+      };
+    },
+    /**
+     * Method: unsetPopUp
+     * resets the popup manually to be able to use it with different settings
+     */
+    unsetPopUp : function() {
+      if(this.popup.domObj != null) {
+        this.popup.domObj.destroy();
+        this.popup.innerWrapper   = null;
+        this.popup.closeIcon      = null;
+        this.popup.button.submit = null;
+        this.popup.button.cancel = null;
+      }
+    },
+    /**
+     * APIMethod: getNextCellInRow
+     * activates the next cell in a row if it is editable
+     * otherwise the focus jumps to the next editable cell in the next row
+     * or starts at the beginning
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      var nextCell = true,
+          nextRow = true,
+          sumCols = this.grid.columns.columns.length,
+          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
+          i = 0,
+          data,
+          cell = this.activeCell.cell,
+          options = this.options;
+      if (this.activeCell.cell != null) {
+        do {
+          nextCell = i > 0 ? nextCell.getNext(jxCellClass) : cell.getNext(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if (nextCell == null) {
+            nextRow  = cell.getParent('tr').getNext();
+            // check if this was the last row in the table
+            if (nextRow == null && options.keypressLoop) {
+              nextRow = cell.getParent('tbody').getFirst();
+            } else if(nextRow == null && !options.keypressLoop){
+              return;
+            }
+            nextCell = nextRow.getFirst(jxCellClass);
+          }
+          data = this.grid.getCellData(nextCell);
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if (i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        } while(data && !data.column.options.isEditable);
+
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInRow
+     * activates the previous cell in a row if it is editable
+     * otherwise the focus jumps to the previous editable cell in the previous row
+     * or starts at the last cell in the last row at the end
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      var prevCell, 
+          prevRow, 
+          i = 0,
+          data,
+          row,
+          index,
+          cell = this.activeCell.cell,
+          sumCols = this.grid.columns.columns.length,
+          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
+          options = this.options;
+      if(cell != null) {
+        do {
+          prevCell = i > 0 ? prevCell.getPrevious(jxCellClass) : cell.getPrevious(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(prevCell == null) {
+            prevRow  = cell.getParent('tr').getPrevious();
+            // check if this was the last row in the table
+            if(prevRow == null && options.keypressLoop) {
+              prevRow = cell.getParent('tbody').getLast();
+            }else if(prevRow == null && !options.keypressLoop) {
+              return;
+            }
+            prevCell = prevRow.getLast(jxCellClass);
+          }
+          data  = this.grid.getCellData(prevCell);
+          row   = data.row;
+          index = data.index;
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(data && !data.column.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.activate(prevCell);
+      }
+    },
+    /**
+     * APIMethod: getNextCellInCol
+     * activates the next cell in a column under the currently active one
+     * if the active cell is in the last row, the first one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInCol : function(save) {
+      var nextRow,
+          nextCell,
+          activeCell = this.activeCell;
+      save = $defined(save) ? save : true;
+      if (activeCell.cell != null) {
+        nextRow = activeCell.cell.getParent().getNext();
+        if (nextRow == null) {
+          nextRow = activeCell.cell.getParent('tbody').getFirst();
+        }
+        nextCell = nextRow.getElement('td.jxGridCol'+activeCell.data.index);
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInCol
+     * activates the previous cell in a column above the currently active one
+     * if the active cell is in the first row, the last one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInCol : function(save) {
+      var prevRow,
+          prevCell,
+          activeCell = this.activeCell;
+      save = $defined(save) ? save : true;
+      if (activeCell.cell != null) {
+        prevRow = activeCell.cell.getParent().getPrevious();
+        if (prevRow == null) {
+          prevRow = activeCell.cell.getParent('tbody').getLast();
+        }
+        prevCell = prevRow.getElement('td.jxGridCol'+activeCell.data.index);
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(prevCell);
+      }
+    },
+    /**
+     * Method: cellValueIncrement
+     * Whether increments or decrements the value of the active cell if the dataType is numeric
+     *
+     * Parameters
+     * @var {Boolean} bool
+     * @return void
+     */
+    cellValueIncrement : function(bool) {
+      var activeCell = this.activeCell,
+          dataType = activeCell.data.column.options.dataType,
+          valueNew = null,
+          formatter;
+      switch (dataType) {
+        case 'numeric':
+        case 'currency':
+          valueNew = activeCell.field.getValue().toInt();
+          if (typeof(valueNew) == 'number') {
+            if (bool) {
+              valueNew++;
+            } else {
+              valueNew--;
+            }
+          }
+          break;
+        case 'date':
+          valueNew = Date.parse(activeCell.field.getValue());
+          if (valueNew instanceof Date) {
+            if (bool) {
+              valueNew.increment();
+            } else {
+              valueNew.decrement();
+            }
+            formatter = new Jx.Formatter.Date();
+            valueNew = formatter.format(valueNew);
+          }
+          break;
+      }
+      if (valueNew != null) {
+        activeCell.field.setValue(valueNew);
+      }
+    },
+    /**
+     * Method: cellIsInGrid
+     * determins if the given coordinates are within the grid
+     *
+     * Parameters:
+     * @var {Integer} row
+     * @var {Integer} index
+     * @return {Boolean}
+     */
+    cellIsInGrid: function(row, index) {
+      if($defined(row) && $defined(index)) {
+        //console.log("Row %i - max Rows: %i, Col %i - max Cols %i", row, this.grid.gridTableBody.rows.length, index, this.grid.gridTableBody.rows[row].cells.length);
+        if( row >= 0 && index >= 0 &&
+            row <= this.grid.gridTableBody.rows.length &&
+            index <= this.grid.gridTableBody.rows[row].cells.length
+        ) {
+          return true;
+        }else{
+          return false;
+        }
+      }else{
+        return false;
+      }
+    },
+    /**
+     * APIMethod: getFieldOptionsByColName
+     * checks for the name of a column inside the fieldOptions and returns
+     * the object if found, otherwise the default options for the field
+     *
+     * Parameters:
+     * @var {String} colName
+     * @return {Object} default field options
+     */
+    getFieldOptionsByColName : function(colName) {
+      var fo = this.options.fieldOptions,
+          r  = this.options.fieldOptions[0];
+      for(var i = 0, j = fo.length; i < j; i++) {
+        if(fo[i].field == colName) {
+          r = fo[i];
+          break;
+        }
+      }
+      return r;
+    },
+    /**
+     * Method: addFormatterUriClickListener
+     *
+     * looks up for Jx.Formatter.Uri columns to disable the link and open the
+     * inline editor instead when CTRL is NOT pressed.
+     * set option linkClickListener to false to disable this
+     *
+     */
+    addFormatterUriClickListener : function() {
+      if(this.options.linkClickListener) {
+        // prevent a link from beeing opened if the editor should appear and the uri formatter is activated
+        var uriCols = [], tableCols, anchor;
+        // find out which columns are using a Jx.Formatter.Uri
+        this.grid.columns.columns.each(function(col,i) {
+          if(col.options.renderer.options.formatter != null && col.options.renderer.options.formatter instanceof Jx.Formatter.Uri) {
+            uriCols.push(i);
+          }
+        });
+        // add an event to all anchors inside these columns
+        this.grid.gridObj.getElements('tr').each(function(tr,i) {
+          tableCols = tr.getElements('td.jxGridCell');
+          for(var j = 0, k = uriCols.length; j < k; j++) {
+            anchor = tableCols[uriCols[j]-1].getElement('a');
+            if(anchor) {
+              anchor.removeEvent('click');
+              anchor.addEvent('click', function(ev) {
+                // open link if ctrl was clicked
+                if(!ev.control) {
+                  ev.preventDefault();
+                }
+              });
+            }
+          }
+        });
+      }
+    },
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.options.popup.use && this.options.popup.useButtons) {
+        if(this.popup.button.submit != null) {
+          this.popup.button.submit.cleanup();
+          this.popup.button.cancel.cleanup();
+          this.popup.button.submit = null;
+          this.popup.button.cancel = null;
+          this.setPopUpButtons();
+        }
+    	}
+    }
+}); 
+/*
+---
+
+name: Jx.Plugin.DataView
+
+description: Namespace for DataView plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.DataView]
+...
+ */
+/**
+ * Namespace: Jx.Plugin.DataView
+ * The namespace for all dataview plugins
+ */
+Jx.Plugin.DataView = {};/*
+---
+
+name: Jx.Slide
+
+description: A class that shows and hides elements using a slide effect. Does not use a wrapper element or require a fixed width or height.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Core/Fx.Tween
+
+provides: [Jx.Slide]
+
+...
+ */
+// $Id: slide.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Slide',
+    Implements: Jx.Object,
+    Binds: ['handleClick'],
+    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
+    },
+    /**
+     * Method: init
+     * sets up the slide
+     */
+    init: function () {
+
+        this.target = document.id(this.options.target);
+
+        this.target.set('tween', {onComplete: this.setDisplay.bind(this)});
+
+        if ($defined(this.options.trigger)) {
+            this.trigger = document.id(this.options.trigger);
+            this.trigger.addEvent('click', this.handleClick);
+        }
+
+        this.target.store('slider', this);
+
+    },
+    /**
+     * Method: 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 {
+            this.slide('out');
+        }
+    },
+    /**
+     * 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:
+     * dir - the direction to slide (either "in" or "out")
+     */
+    slide: function (dir) {
+        var h;
+        if (dir === 'in') {
+            h = this.target.retrieve(this.options.type);
+            this.target.setStyles({
+                overflow: 'hidden',
+                display: 'block'
+            });
+            this.target.setStyles(this.options.type, 0);
+            this.target.tween(this.options.type, h);
+        } else {
+            if (this.options.type === 'height') {
+                h = this.target.getMarginBoxSize().height;
+            } else {
+                h = this.target.getMarginBoxSize().width;
+            }
+            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);
+        }
+    }
+});/*
+---
+
+name: Jx.Plugin.DataView.GroupFolder
+
+description: Enables closing and opening groups in a group dataview
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.DataView
+ - Jx.Slide
+
+provides: [Jx.Plugin.DataView.GroupFolder]
+
+...
+ */
+/**
+ * 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
+    },
+    /**
+     * Property: headerState
+     * Hash that holds the open/closed state of each header
+     */
+    headerState: null,
+    init: function() {
+      this.headerState = new Hash();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this plugin to a dataview
+     */
+    attach: function (dataView) {
+        if (!$defined(dataView) && !(dataview instanceof Jx.Panel.DataView)) {
+            return;
+        }
+
+        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: onSlideIn
+     * Called when a group opens.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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: onSlideOut
+     * Called when a group closes.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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');
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Field
+
+description: Namespace for Jx.Field plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Field]
+
+...
+ */
+// $Id: plugin.field.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Plugin.Field.Validator
+
+description: Provides validation services for Jx.Field subclasses
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Field
+ - More/Form.Validator
+ - More/Form.Validator.Extras
+
+provides: [Jx.Plugin.Field.Validator]
+
+...
+ */
+// $Id: field.validator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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
+    },
+    /**
+     * Property: valid
+     * tells whether this field passed validation or not.
+     */
+    valid: null,
+    /**
+     * Property: errors
+     * array of errors found on this field
+     */
+    errors: null,
+    validators : null,
+    /**
+     * 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.errors = [];
+        this.validators = new Hash();
+        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;
+        }
+        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();
+        }
+        //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.set(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);
+    },
+    /**
+     * APIMethod: detach
+     */
+    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;
+    },
+
+    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('jxFieldSuccess').addClass('jxFieldError');
+            this.fireEvent('fieldValidationFailed', [this.field, this]);
+        } else {
+            this.field.domObj.removeClass('jxFieldError').addClass('jxFieldSuccess');
+            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('jxFieldError').removeClass('jxFieldSuccess');
+    },
+    /**
+     * APIMethod: getErrors
+     * USe this method to retrieve all of the errors noted for this field.
+     */
+    getErrors: function () {
+        return this.errors;
+    }
+
+
+});
+/*
+---
+
+name: Jx.Plugin.Form
+
+description: Namespace for Jx.Form plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Form]
+
+...
+ */
+// $Id: plugin.form.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Plugin.Form.Validator
+
+description: Provides validation services for Jx.Form
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Form
+ - Jx.Plugin.Field.Validator
+
+provides: [Jx.Plugin.Form.Validator]
+
+...
+ */
+// $Id: form.validator.js 970 2010-08-23 12:20:57Z pagameba $
+/**
+ * Class: Jx.Plugin.Form.Validator
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Form plugin for enforcing validation on the fields 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.Form.Validator = new Class({
+
+    Extends : Jx.Plugin,
+    name: 'Form.Validator',
+
+    options: {
+        /**
+         * Option: fields
+         * This will be key/value pairs for each of the fields as shown here:
+         * (code)
+         * {
+         *     fieldID: {
+         *          ... options for Field.Validator plugin ...
+         *     },
+         *     fieldID: {...
+         *     }
+         * }
+         * (end)
+         */
+        fields: null,
+        /**
+         * Option: fieldDefaults
+         * {Object} contains named defaults for field validators to be
+         * triggered on blur or change.  Default is:
+         * (code)
+         * {
+         *    validateOnBlur: true
+         *    validateOnChange: false
+         * }
+         * (end)
+         */
+        fieldDefaults: {
+            validateOnBlur: true,
+            validateOnChange: true
+        },
+        /**
+         * Option: validateOnSubmit
+         * {Boolean} default true.  Trigger validation on submission of
+         * form if true.
+         */
+        validateOnSubmit: true,
+        /**
+         * Option: suspendSubmit
+         * {Boolean} default false.  Stop form submission when validator is
+         * attached.
+         */
+        suspendSubmit: false
+    },
+    /**
+     * Property: errorMessagess
+     * element holding
+     */
+    errorMessage: null,
+    /**
+     * 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);
+    },
+    /**
+     * 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,
+            options = this.options;
+        //override the isValid function in the form
+        form.isValid = function () {
+            return plugin.isValid();
+        };
+
+        if (options.validateOnSubmit && !options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', this.bound.validate);
+        } else if (options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', function (ev) {
+                ev.stop();
+            });
+        }
+
+        this.plugins = $H();
+
+        //setup the fields
+        $H(options.fields).each(function (val, key) {
+            var opts = $merge(this.options.fieldDefaults, val),
+                fields = this.form.getFieldsByName(key).
+                p;
+            if (fields && fields.length) {
+                p = new Jx.Plugin.Field.Validator(opts);
+                this.plugins.set(key, p);
+                p.attach(fields[0]);
+                p.addEvent('fieldValidationFailed', this.bound.failed);
+                p.addEvent('fieldValidationPassed', this.bound.passed);
+            }
+        }, this);
+
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.form) {
+            document.id(this.form).removeEvent('submit');
+        }
+        this.form = null;
+        this.plugins.each(function(plugin){
+            plugin.detach();
+            plugin = null;
+        },this);
+        this.plugins = null;
+    },
+    /**
+     * 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]);
+        }
+        return valid;
+    },
+    /**
+     * Method: fieldFailed
+     * Refires the fieldValidationFailed event from the field validators it contains
+     */
+    fieldFailed: function (field, validator) {
+        this.fireEvent('fieldValidationFailed', [field, validator]);
+    },
+    /**
+     * Method: fieldPassed
+     * 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;
+    }
+
+
+});
+/*
+---
+
+name: Jx.Plugin.ToolbarContainer
+
+description: Namespace for Jx.Toolbar.Container
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.ToolbarContainer]
+
+...
+ */
+/**
+ * Class: Jx.Plugin.Toolbar
+ * Toolbar plugin namespace
+ *
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer = {};/*
+---
+
+name: Jx.Plugin.ToolbarContainer.TabMenu
+
+description: Adds a menu of tabs to the toolbar container for easy access to all tabs.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.ToolbarContainer
+
+provides: [Jx.Plugin.ToolbarContainer.TabMenu]
+
+...
+ */
+/**
+ * Class: Jx.Plugin.ToolbarContainer.TabMenu
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * This plugin provides a menu of tabs in a toolbar (similar to the button in firefox at the end of the row of tabs).
+ * It is designed to be used only when the toolbar contains tabs and only when the container is allowed to scroll. Also,
+ * this plugin must be added directly to the Toolbar container. You can get a reference to the container for a
+ * <Jx.TabBox> by doing
+ *
+ * (code)
+ * var tabbox = new Jx.TabBox();
+ * var toolbarContainer = document.id(tabBox.tabBar).getParent('.jxBarContainer').retrieve('jxBarContainer');
+ * (end)
+ *
+ * You can then use the attach method to connect the plugin. Otherwise, you can add it via any normal means to a
+ * directly instantiated Container.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer.TabMenu = new Class({
+
+    Family: 'Jx.Plugin.ToolbarContainer.TabMenu',
+    Extends: Jx.Plugin,
+
+    Binds: ['addButton'],
+
+    options: {
+    },
+    /**
+     * Property: tabs
+     * holds all of the tabs that we're tracking
+     */
+    tabs: [],
+
+    init: function () {
+        this.parent();
+    },
+
+    attach: function (toolbarContainer) {
+        this.parent(toolbarContainer);
+
+        this.container = toolbarContainer;
+
+        //we will only be used if the container is allowed to scroll
+        if (!this.container.options.scroll) {
+            return;
+        }
+
+        this.menu = new Jx.Menu({},{
+            buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'
+        }).addTo(this.container.controls,'bottom');
+        document.id(this.menu).addClass('jxTabMenuRevealer');
+        this.container.update();
+
+        //go through all of the existing tabs and add them to the menu
+        //grab the toolbar...
+        var tb = document.id(this.container).getElement('ul').retrieve('jxToolbar');
+        tb.list.each(function(item){
+            this.addButton(item);
+        },this);
+
+        //connect to the add event of the toolbar list to monitor the addition of buttons
+        tb.list.addEvent('add',this.addButton);
+    },
+
+    detach: function () {
+        this.parent();
+    },
+
+    addButton: function (item) {
+        var tab;
+        tab = (item instanceof Jx.Tab) ? item : document.id(item).getFirst().retrieve('jxTab');
+
+
+        var l = tab.getLabel();
+        if (!$defined(l)) {
+            l = '';
+        }
+        var mi = new Jx.Menu.Item({
+            label: l,
+            image: tab.options.image,
+            onClick: function() {
+                if (tab.isActive()) {
+                    this.container.scrollIntoView(tab);
+                } else {
+                    tab.setActive(true);
+                }
+            }.bind(this)
+        });
+
+        document.id(tab).store('menuItem', mi);
+
+        tab.addEvent('close', function() {
+            this.menu.remove(mi);
+        }.bind(this));
+
+        this.menu.add([mi]);
+    }
+});/*
+---
+
+name: Jx.Adaptor
+
+description: Base class for all Adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Adaptor]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor
+ * Base class for all adaptor implementations. Provides a place to locate all
+ * common code and the Jx.Adaptor namespace.  Since it extends <Jx.Plugin> all
+ * adaptors will be able to be used as plugins for their respective classes.
+ * Also as such, they must have the attach() and detach() methods.
+ *
+ * Adaptors are specifically used to conform a <Jx.Store> to any one of
+ * the different widgets (i.e. Jx.Tree, Jx.ListView, etc...) that could
+ * benefit from integration with the store. This approach was taken to minimize
+ * data access code in the widgets themselves. Widgets should have no idea where
+ * the data/items come from so that they will be usable in the broadest number
+ * of situations.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor = new Class({
+
+
+  Extends: Jx.Plugin,
+  Family: 'Jx.Adaptor',
+
+  name: 'Jx.Adaptor',
+
+  options: {
+        /**
+         * Option: template
+         * The text template to use in creating the items for this adaptor
+         */
+      template: '',
+        /**
+         * Option: useTemplate
+         * Whether or not to use the text template above. Defaults to true.
+         */
+      useTemplate: true,
+        /**
+         * Option: store
+         * The store to use with the adaptor.
+         */
+      store: null
+  },
+    /**
+     * Property: columnsNeeded
+     * Will hold an array of the column names needed for processing the
+     * template
+     */
+  columnsNeeded: null,
+
+  init: function () {
+      var options = this.options;
+      this.parent();
+
+      this.store = options.store;
+
+      if (options.useTemplate && $defined(this.store.getColumns())) {
+          this.columnsNeeded = this.store.parseTemplate(options.template);
+      }
+  },
+
+  attach: function (widget) {
+    this.parent(widget);
+    this.widget = widget;
+  },
+
+  detach: function () {
+    this.parent();
+  }
+
+});/*
+---
+
+name: Jx.Adaptor.Tree
+
+description: Base class for all adaptors that fill Jx.Tree widgets. Also acts as the namespace for other Jx.Tree adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor
+
+provides: [Jx.Adaptor.Tree]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor.Tree
+ * This base class is used to change a store (a flat list of records) into the
+ * data structure needed for a Jx.Tree. It will have 2 subclasses:
+ * <Jx.Adapter.Tree.Mptt> and <Jx.Adapter.Tree.Parent>.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree = new Class({
+
+
+    Extends: Jx.Adaptor,
+    Family: 'Jx.Adaptor.Tree',
+
+    Binds: ['fill','checkFolder'],
+
+    options: {
+        /**
+         * Option: monitorFolders
+         * Determines if this adapter should use monitor the TreeFolder items in
+         * order to request any items they should contain if they are empty.
+         */
+        monitorFolders: false,
+        /**
+         * Option: startingNodeKey
+         * The store primary key to use as the node that we're requesting.
+         * Initially set to -1 to indicate that we're request the first set of
+         * data
+         */
+        startingNodeKey: -1,
+        /**
+         * Option: folderOptions
+         * A Hash containing the options for <Jx.TreeFolder>. Defaults to null.
+         */
+        folderOptions: null,
+        /**
+         * Option: itemOptions
+         * A Hash containing the options for <Jx.TreeItem>. Defaults to null.
+         */
+        itemOptions: null
+    },
+    /**
+     * Property: folders
+     * A Hash containing all of the <Jx.TreeFolders> in this tree.
+     */
+    folders: null,
+    /**
+     * Property: currentRecord
+     * An integer indicating the last position we were at in the store. Used to
+     * allow the adaptor to pick up rendering items after we request additional
+     * data.
+     */
+    currentRecord: -1,
+    init: function() {
+      this.folders = new Hash();
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this adaptor to a specific tree instance.
+     *
+     * Parameters:
+     * tree - an instance of <Jx.Tree>
+     */
+    attach: function (tree) {
+        this.parent(tree);
+
+        this.tree = tree;
+
+        if (this.options.monitorFolders) {
+            this.strategy = this.store.getStrategy('progressive');
+
+            if (!$defined(this.strategy)) {
+                this.strategy = new Jx.Store.Strategy.Progressive({
+                    dropRecords: false,
+                    getPaginationParams: function () { return {}; }
+                });
+                this.store.addStrategy(this.strategy);
+            } else {
+                this.strategy.options.dropRecords = false;
+                this.strategy.options.getPaginationParams = function () { return {}; };
+            }
+
+        }
+
+        this.store.addEvent('storeDataLoaded', this.fill);
+
+
+    },
+    /**
+     * APIMethod: detach
+     * removes this adaptor from the current tree.
+     */
+    detach: function () {
+      this.parent();
+      this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+    /**
+     * APIMethod: firstLoad
+     * Method used to start the first store load.
+     */
+    firstLoad: function () {
+      //initial store load
+      this.busy = 'tree';
+      this.tree.setBusy(true);
+        this.store.load({
+            node: this.options.startingNodeKey
+        });
+    },
+
+    /**
+     * APIMethod: fill
+     * This function will start at this.currentRecord and add the remaining
+     * items to the tree.
+     */
+    fill: function () {
+      var i,
+          template,
+          item,
+          p,
+          folder,
+          options = this.option;
+
+      if (this.busy == 'tree') {
+        this.tree.setBusy(false);
+        this.busy = 'none';
+      } else if (this.busy == 'folder') {
+        this.busyFolder.setBusy(false);
+        this.busy = 'none';
+      }
+        var l = this.store.count() - 1;
+        for (i = this.currentRecord + 1; i <= l; i++) {
+            template = this.store.fillTemplate(i,options.template,this.columnsNeeded);
+
+            if (this.hasChildren(i)) {
+                //add as folder
+                item = new Jx.TreeFolder($merge(options.folderOptions, {
+                    label: template
+                }));
+
+                if (options.monitorFolders) {
+                  item.addEvent('disclosed', this.checkFolder);
+                }
+
+                this.folders.set(i,item);
+            } else {
+                //add as item
+                item = new Jx.TreeItem($merge(options.itemOptions, {
+                    label: template
+                }));
+            }
+            document.id(item).store('index', i).store('jxAdaptor', this);
+            //check for a parent
+            if (this.hasParent(i)) {
+                //add as child of parent
+                var p = this.getParentIndex(i);
+                var folder = this.folders.get(p);
+                folder.add(item);
+            } else {
+                //otherwise add to the tree itself
+                this.tree.add(item);
+            }
+        }
+        this.currentRecord = l;
+    },
+    /**
+     * Method: checkFolder
+     * Called by the disclose event of the tree to determine if we need to
+     * request additional items for a branch of the tree.
+     */
+    checkFolder: function (folder) {
+        var items = folder.items(),
+            index,
+            node;
+        if (!$defined(items) || items.length === 0) {
+            //get items via the store
+          index = document.id(folder).retrieve('index');
+          node = this.store.get('primaryKey', index);
+          this.busyFolder = folder;
+          this.busyFolder.setBusy(true);
+          this.busy = 'folder';
+            this.store.load({
+                node: node
+            });
+        }
+    },
+    /**
+     * Method: hasChildren
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has any children.
+     */
+    hasChildren: $empty,
+    /**
+     * Method: hasParent
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has a parent node.
+     */
+    hasParent: $empty,
+    /**
+     * Method: getParentIndex
+     * Virtual method to be overridden by sublcasses. Determines the store index
+     * of the parent node.
+     */
+    getParentIndex: $empty
+});/*
+---
+
+name: Jx.Adaptor.Tree.Mptt
+
+description: Fills a Jx.Tree instance from a remote table that represents an MPTT (Modified Preorder Table Traversal) data source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Tree
+
+provides: [Jx.Adaptor.Tree.Mptt]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor.Tree.Mptt
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ *
+ * This class requires an MPTT (Modified Preorder Tree Traversal) table. The MPTT
+ * has a 'left' and a 'right' column that indicates the order of nesting. For
+ * more details see the sitepoint.com article at
+ * http://articles.sitepoint.com/article/hierarchical-data-database
+ *
+ * if useAjax option is set to true then this adapter will send an Ajax request
+ * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
+ * to request additional nodes.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Mptt = new Class({
+
+
+    Family: 'Jx.Adaptor.Tree.Mptt',
+    Extends: Jx.Adaptor.Tree,
+
+    name: 'tree.mptt',
+
+    options: {
+        left: 'left',
+        right: 'right'
+    },
+
+    /**
+     * APIMethod: hasChildren
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    hasChildren: function (index) {
+        var l = this.store.get(this.options.left, index).toInt(),
+            r = this.store.get(this.options.right, index).toInt();
+        return (l + 1 !== r);
+    },
+
+    /**
+     * APIMethod: hasParent
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    hasParent: function (index) {
+        var i = this.getParentIndex(index),
+            result = false;
+        if ($defined(i)) {
+            result = true;
+        }
+        return result;
+    },
+
+    /**
+     * APIMethod: getParentIndex
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        var store = this.store,
+            options = this.options,
+            l,
+            r,
+            i,
+            pl,
+            pr;
+        l = store.get(options.left, index).toInt();
+        r = store.get(options.right, index).toInt();
+        for (i = index-1; i >= 0; i--) {
+            pl = store.get(options.left, i).toInt();
+            pr = store.get(options.right, i).toInt();
+            if (pl < l && pr > r) {
+                return i;
+            }
+        }
+        return null;
+    }
+});/*
+---
+
+name: Jx.Adaptor.Tree.Parent
+
+description: Fills a Jx.Tree instance from a standard parent/child/folder style data table.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Tree
+
+provides: [Jx.Adaptor.Tree.Parent]
+
+
+...
+ */
+/**
+ * Class: Jx.Adapter.Tree.Parent
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * Basically, the store needs to have a column that will indicate the
+ * parent of each row. The root(s) of the tree should be indicated by a "-1" 
+ * in this column. The name of the "parent" column is configurable in the 
+ * options.
+ * 
+ * if the monitorFolders option is set to true then this adapter will send
+ * an Ajax request to the server, through the store's strategy (should be
+ * Jx.Store.Strategy.Progressive) to request additional nodes. Also, a column
+ * indicating whether this is a folder needs to be set as there is no way to
+ * tell if a node has children without it.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Parent = new Class({
+    
+
+    Extends: Jx.Adaptor.Tree,
+    Family: 'Jx.Adaptor.Tree.Parent',
+    
+    options: {
+        parentColumn: 'parent',
+        folderColumn: 'folder'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+    	return this.store.get(this.options.folderColumn, index);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        if (this.store.get(this.options.parentColumn, index).toInt() !== -1) {
+            return true;
+        } 
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        //get the parent based on the index
+        var pk = this.store.get(this.options.parentColumn, index);
+        return this.store.findByColumn('primaryKey', pk);
+    }
+});/*
+---
+
+name: Jx.Adaptor.Combo
+
+description: Namespace for all Jx.Combo adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor
+
+provides: [Jx.Adaptor.Combo]
+
+...
+*/
+/**
+ * Class: Jx.Adaptor.Combo
+ * The namespace for all combo adaptors
+ */
+Jx.Adaptor.Combo = {};/*
+---
+
+name: Jx.Adaptor.Combo.Fill
+
+description: Loads data into a Jx.Combo instance from designated column(s) of a data source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Combo
+
+provides: [Jx.Adaptor.Combo.Fill]
+
+...
+ */
+Jx.Adaptor.Combo.Fill = new Class({
+
+    Family: 'Jx.Adaptor.Combo.Fill',
+    Extends: Jx.Adaptor,
+    name: 'combo.fill',
+    Binds: ['fill'],
+
+    /**
+     * Note: option.template is used for constructing the text for the label
+     */
+    options: {
+        /**
+         * Option: imagePathColumn
+         * points to a store column that holds the image information
+         * for the combo items.
+         */
+        imagePathColumn: null,
+        /**
+         * Option: imageClassColumn
+         * Points to a store column that holds the image class
+         * information for the combo items
+         */
+        imageClassColumn: null,
+        /**
+         * Option: selectedFn
+         * This should be a function that could be run to determine if
+         * an item should be selected. It will get passed the current store
+         * record as the only parameter. It should return either true or false.
+         */
+        selectedFn: null,
+        /**
+         * Option: noRepeats
+         * This option allows you to use any store even if it has duplicate
+         * values in it. With this option set to true the adaptor will keep
+         * track of all of teh labels it adds and will not add anything that's
+         * a duplicate.
+         */
+        noRepeats: false
+    },
+
+    labels: null,
+
+    init: function () {
+        this.parent();
+
+        if (this.options.noRepeat) {
+            this.labels = [];
+        }
+    },
+
+    attach: function (combo) {
+        this.parent(combo);
+
+        this.store.addEvent('storeDataLoaded', this.fill);
+        if (this.store.loaded) {
+            this.fill();
+        }
+    },
+
+    detach: function () {
+        this.parent();
+
+        this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+
+    fill: function () {
+        var template,
+            items=[],
+            selected,
+            obj,
+            options = this.options,
+            noRepeat = this.options.noRepeat;
+        //empty the combo
+        this.widget.empty();
+        //reset the store and cycle through creating the objects
+        //to pass to combo.add()
+        this.store.first();
+        items = [];
+        this.store.each(function(record){
+            template = this.store.fillTemplate(record,options.template,this.columnsNeeded);
+            if (!noRepeat || (noRepeat && !this.labels.contains(template))) {
+                selected = false;
+                if ($type(options.selectedFn) == 'function') {
+                    selected = options.selectedFn.run(record);
+                }
+                obj = {
+                    label: template,
+                    image: record.get(options.imagePathColumn),
+                    imageClass: record.get(options.imageClassColumn),
+                    selected: selected
+                };
+                items.push(obj);
+
+                if (noRepeat) {
+                    this.labels.push(template);
+                }
+            }
+
+        },this);
+        //pass all of the objects at once
+        this.widget.add(items);
+    }
+});/*
+---
+
+name: Jx.Menu.Context
+
+description: A Jx.Menu that has no button but can be opened at a specific browser location to implement context menus (for instance).
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Context]
+
+css:
+ - menu
+
+...
+ */
+// $Id: context.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Menu.Context
+ *
+ * Extends: Jx.Menu
+ *
+ * A <Jx.Menu> that has no button but can be opened at a specific
+ * browser location to implement context menus (for instance).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * TODO - add open/close events?
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Context = new Class({
+    Family: 'Jx.Menu.Context',
+    Extends: Jx.Menu,
+
+    parameters: ['id'],
+
+    /**
+     * APIMethod: render
+     * create a new context menu
+     */
+    render: function() {
+        this.id = document.id(this.options.id);
+        if (this.id) {
+            this.id.addEvent('contextmenu', this.show.bindWithEvent(this));
+        }
+        this.parent();
+    },
+    /**
+     * Method: show
+     * Show the context menu at the location of the mouse click
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    show : function(e) {
+        if (this.list.count() ==0) {
+            return;
+        }
+        
+        this.target = e.target;
+
+        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.setStyles({
+           width: null,
+           height: null
+         });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+        this.position(this.contentContainer, document.body, {
+            horizontal: [e.page.x + ' left'],
+            vertical: [e.page.y + ' top', e.page.y + ' bottom'],
+            offsets: this.chromeOffsets
+        });
+
+        this.contentContainer.setStyle('visibility','');
+        this.showChrome(this.contentContainer);
+
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keyup', this.bound.keypress);
+
+        e.stop();
+    }
+});/*
+---
+
+name: Jx.Menu.Separator
+
+description: Convenience class to create a visual separator in a menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Separator]
+
+images:
+ - toolbar_separator_v.png
+
+...
+ */
+// $Id: menu.separator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Menu.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A convenience class to create a visual separator in a menu.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Separator = new Class({
+    Family: 'Jx.Menu.Separator',
+    Extends: Jx.Widget,
+    /**
+     * Property: domObj
+     * {HTMLElement} the HTML element that the separator is contained
+     * within
+     */
+    domObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu>, <Jx.Menu.SubMenu>} the menu that the separator is in.
+     */
+    owner: null,
+    options: {
+        template: "<li class='jxMenuItemContainer jxMenuItem'><span class='jxMenuSeparator'>&nbsp;</span></li>"
+    },
+    classes: new Hash({
+        domObj: 'jxMenuItem'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of a menu separator
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the ownder of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: $empty,
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty
+});/*
+---
+
+name: Jx.Menu.SubMenu
+
+description: A sub menu contains menu items within a main menu or another sub menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu.Item
+ - Jx.Menu
+
+provides: [Jx.Menu.SubMenu]
+
+...
+ */
+// $Id: submenu.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Menu.SubMenu
+ *
+ * Extends: <Jx.Menu.Item>
+ *
+ * Implements: <Jx.AutoPosition>, <Jx.Chrome>
+ *
+ * A sub menu contains menu items within a main menu or another
+ * sub menu.
+ *
+ * The structure of a SubMenu is the same as a <Jx.Menu.Item> with
+ * an additional unordered list element appended to the container.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.SubMenu = new Class({
+    Family: 'Jx.Menu.SubMenu',
+    Extends: Jx.Menu.Item,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML container for the sub menu.
+     */
+    subDomObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu> or <Jx.SubMenu>} the menu or sub menu that this sub menu
+     * belongs
+     */
+    owner: null,
+    /**
+     * Property: visibleItem
+     * {<Jx.MenuItem>} the visible item within the menu
+     */
+    visibleItem: null,
+    /**
+     * Property: list
+     * {<Jx.List>} a list to manage menu items
+     */
+    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']
+        }
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.SubMenu
+     */
+    render: function() {
+        this.parent();
+        this.open = false;
+
+        this.menu = new Jx.Menu(null, {
+            position: this.options.position
+        });
+        this.menu.domObj = this.domObj;
+    },
+    cleanup: function() {
+      this.menu.domObj = null;
+      this.menu.destroy();
+      this.menu = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this sub menu
+     *
+     * Parameters:
+     * obj - {Object} the owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: show
+     * Show the sub menu
+     */
+    show: function() {
+        if (this.open || this.menu.list.count() == 0) {
+            return;
+        }
+        this.menu.show();
+        this.open = true;
+        // this.setActive(true);
+    },
+
+    eventInMenu: function(e) {
+        if (this.visibleItem &&
+            this.visibleItem.eventInMenu &&
+            this.visibleItem.eventInMenu(e)) {
+            return true;
+        }
+        return document.id(e.target).descendantOf(this.domObj) ||
+               this.menu.eventInMenu(e);
+    },
+
+    /**
+     * Method: hide
+     * Hide the sub menu
+     */
+    hide: function() {
+        if (!this.open) {
+            return;
+        }
+        this.open = false;
+        this.menu.hide();
+        this.visibleItem = null;
+    },
+    /**
+     * Method: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     * can be added by passing multiple arguments to this function.
+     */
+    add: function(item, position) {
+        this.menu.add(item, position, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a menu item from the menu
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to remove
+     */
+    remove: function(item) {
+        this.menu.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.menu.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the sub menu
+     */
+    empty: function() {
+      this.menu.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the sub menu
+     *
+     * Parameters:
+     * e - {Event} the event that triggered the menu being
+     * deactivated.
+     */
+    deactivate: function(e) {
+        if (this.owner) {
+            this.owner.deactivate(e);
+        }
+    },
+    /**
+     * Method: isActive
+     * Indicate if this sub menu is active
+     *
+     * Returns:
+     * {Boolean} true if the <Jx.Menu> that ultimately contains
+     * this sub menu is active, false otherwise.
+     */
+    isActive: function() {
+        if (this.owner) {
+            return this.owner.isActive();
+        } else {
+            return false;
+        }
+    },
+    /**
+     * Method: setActive
+     * Set the active state of the <Jx.Menu> that contains this sub menu
+     *
+     * Parameters:
+     * isActive - {Boolean} the new active state
+     */
+    setActive: function(isActive) {
+        if (this.owner && this.owner.setActive) {
+            this.owner.setActive(isActive);
+        }
+    },
+    /**
+     * Method: setVisibleItem
+     * Set a sub menu of this menu to be visible and hide the previously
+     * visible one.
+     *
+     * Parameters:
+     * obj - {<Jx.SubMenu>} the sub menu that should be visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    }
+});/*
+---
+
+name: Jx.Splitter.Snap
+
+description: A helper class to create an element that can snap a split panel open or closed.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Splitter
+
+provides: [Jx.Splitter.Snap]
+
+...
+ */
+// $Id: snap.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Splitter.Snap
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class to create an element that can snap a split panel open or
+ * closed.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+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
+     * clicked).
+     */
+    snap: null,
+    /**
+     * Property: element
+     * {HTMLElement} An element of the <Jx.Splitter> that gets controlled
+     * by this snap
+     */
+    element: null,
+    /**
+     * Property: splitter
+     * {<Jx.Splitter>} the splitter that this snap is associated with.
+     */
+    splitter: null,
+    /**
+     * Property: layout
+     * {String} track the layout of the splitter for convenience.
+     */
+    layout: 'vertical',
+    /**
+     * 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.
+     */
+    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.layout = this.splitter.options.layout;
+        var jxo = jxl.options;
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            this.originalSize = size.height;
+            this.minimumSize = jxo.minHeight ? jxo.minHeight : 0;
+        } else {
+            this.originalSize = size.width;
+            this.minimumSize = jxo.minWidth ? jxo.minWidth : 0;
+        }
+        this.events.each(function(eventName) {
+            this.snap.addEvent(eventName, this.toggleElement.bind(this));
+        }, this);
+    },
+
+    /**
+     * Method: toggleElement
+     * Snap the element open or closed.
+     */
+    toggleElement: function() {
+        var size = this.element.getContentBoxSize();
+        var newSize = {};
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                newSize.height = this.originalSize;
+            } else {
+                this.originalSize = size.height;
+                newSize.height = this.minimumSize;
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                newSize.width = this.originalSize;
+            } else {
+                this.originalSize = size.width;
+                newSize.width = this.minimumSize;
+            }
+        }
+        this.element.resize(newSize);
+        this.splitter.sizeChanged();
+    },
+
+    /**
+     * Method: sizeChanged
+     * Handle the size of the element changing to see if the
+     * toggle state has changed.
+     */
+    sizeChange: function() {
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        }
+    }
+});/*
+---
+
+name: Jx.Tab
+
+description: A single tab in a tab set.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.Layout
+
+provides: [Jx.Tab]
+
+css:
+ - tab
+
+images:
+ - tab_top.png
+ - tab_bottom.png
+ - tab_left.png
+ - tab_right.png
+ - tab_close.png
+
+...
+ */
+// $Id: tab.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Tab
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * actual tab itself) in order to be useful.  Alternately, you can use
+ * a <Jx.TabBox> which combines both into a single control at the cost of
+ * some flexibility in layout options.
+ *
+ * A tab is a <Jx.ContentLoader> and you can specify the initial content of
+ * the tab using any of the methods supported by
+ * <Jx.ContentLoader::loadContent>.  You can acccess the actual DOM element
+ * that contains the content (if you want to dynamically insert content
+ * for instance) via the <Jx.Tab::content> property.
+ *
+ * A tab is a button of type *toggle* which means that it emits the *up*
+ * and *down* events.
+ *
+ * Example:
+ * (code)
+ * var tab1 = new Jx.Tab({
+ *     label: 'tab 1',
+ *     content: 'content1',
+ *     onDown: function(tab) {
+ *         console.log('tab became active');
+ *     },
+ *     onUp: function(tab) {
+ *         console.log('tab became inactive');
+ *     }
+ * });
+ * (end)
+ *
+ *
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Tab = new Class({
+    Family: 'Jx.Tab',
+    Extends: Jx.Button,
+    /**
+     * Property: content
+     * {HTMLElement} The content area that is displayed when the tab is
+     * active.
+     */
+    content: null,
+
+    options: {
+        /* Option: toggleClass
+         * the CSS class to use for the button, 'jxTabToggle' by default
+         */
+        toggleClass: 'jxTabToggle',
+        /* Option: pressedClass
+         * the CSS class to use when the tab is pressed, 'jxTabPressed' by
+         * default
+         */
+        pressedClass: 'jxTabPressed',
+        /* Option: activeClass
+         * the CSS class to use when the tab is active, 'jxTabActive' by 
+         * default.
+         */
+        activeClass: 'jxTabActive',
+        /* Option: activeTabClass
+         * the CSS class to use on the content area of the active tab,
+         * 'tabContentActive' by default.
+         */
+        activeTabClass: 'tabContentActive',
+        /* Option: template
+         * the HTML template for a tab
+         */
+        template: '<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon" src="'+Jx.aPixel.src+'"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></a></span>',
+        /* Option: contentTemplate
+         * the HTML template for a tab's content area
+         */
+        contentTemplate: '<div class="tabContent"></div>',
+        /* Option: close
+         * {Boolean} can the tab be closed by the user?  False by default.
+         */
+        close: false,
+        /* Option: shouldClose
+         * {Mixed} when a tab is closeable, the shouldClose option is checked
+         * first to see if the tab should close.  You can provide a function
+         * for this option that can be used to return a boolean value.  This
+         * is useful if your tab contains something the user can edit and you
+         * want to see if they want to discard the changes before closing.
+         * The default value is true, meaning the tab will close immediately.
+         * (code)
+         * new Jx.Tab({
+         *   label: 'test close',
+         *   close: true,
+         *   shouldClose: function() {
+         *     return window.confirm('Are you sure?');
+         *   }
+         * });
+         * (end)
+         */
+        shouldClose: true
+    },
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxTabContainer',
+        domA: 'jxTab',
+        domImg: 'jxTabIcon',
+        domLabel: 'jxTabLabel',
+        domClose: 'jxTabClose',
+        content: 'tabContent'
+    }),
+
+    /**
+     * Method: render
+     * Create a new instance of Jx.Tab.  Any layout options passed are used
+     * to create a <Jx.Layout> for the tab content area.
+     */
+    render : function( ) {
+        this.options = $merge(this.options, {toggle:true});
+        this.parent();
+        this.domObj.store('jxTab', this);
+        this.processElements(this.options.contentTemplate, this.classes);
+        new Jx.Layout(this.content, this.options);
+        
+        // load content onDemand if needed
+        if(!this.options.loadOnDemand || this.options.active) {
+          this.loadContent(this.content);
+          // set active if needed
+          if(this.options.active) {
+            this.clicked();
+          }
+        }else{
+          this.addEvent('contentLoaded', function(ev) {
+            this.setActive(true);
+          }.bind(this));
+        }
+        this.addEvent('down', function(){
+            this.content.addClass(this.options.activeTabClass);
+        }.bind(this));
+        this.addEvent('up', function(){
+            this.content.removeClass(this.options.activeTabClass);
+        }.bind(this));
+
+        //remove the close button if necessary
+        if (this.domClose) {
+            if (this.options.close) {
+                this.domObj.addClass('jxTabClose');
+                this.domClose.addEvent('click', (function(){
+                  var shouldClose = true;
+                  if ($defined(this.options.shouldClose)) {
+                    if (typeof this.options.shouldClose == 'function') {
+                      shouldClose = this.options.shouldClose();
+                    } else {
+                      shouldClose = this.options.shouldClose;
+                    }
+                  }
+                  if (shouldClose) {
+                    this.fireEvent('close');
+                  }
+                }).bind(this));
+            } else {
+                this.domClose.dispose();
+            }
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     */
+    clicked : function(evt) {
+      if(this.options.enabled) {
+        // just set active when caching is enabled
+        if(this.contentIsLoaded && this.options.cacheContent) {
+          this.setActive(true);
+        // load on demand or reload content if caching is disabled
+        }else if(this.options.loadOnDemand || !this.options.cacheContent){
+          this.loadContent(this.content);
+        }else{
+          this.setActive(true);
+        }
+      }
+    }
+});
+
+/* keep the old location temporarily */
+Jx.Button.Tab = new Class({
+  Extends: Jx.Tab,
+  init: function() {
+    if (console.warn) {
+      console.warn('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    } else {
+      console.log('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    }
+    this.parent();
+  }
+});/*
+---
+
+name: Jx.TabSet
+
+description: A TabSet manages a set of Jx.Tab content areas by ensuring that only one of the content areas is visible (i.e. the active tab).
+
+license: MIT-style license.
+
+requires:
+ - Jx.Tab
+
+provides: [Jx.TabSet]
+
+...
+ */
+// $Id: tabset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TabSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A TabSet manages a set of <Jx.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.Tab> that are to be managed
+ * as a set have to be added to both a TabSet and a <Jx.Toolbar>.  The content
+ * areas of the <Jx.Tab>s are sized to fit the content area that the TabSet
+ * is managing.
+ *
+ * Example:
+ * (code)
+ * var tabBar = new Jx.Toolbar('tabBar');
+ * var tabSet = new Jx.TabSet('tabArea');
+ *
+ * var tab1 = new Jx.Tab('tab 1', {contentID: 'content1'});
+ * var tab2 = new Jx.Tab('tab 2', {contentID: 'content2'});
+ * var tab3 = new Jx.Tab('tab 3', {contentID: 'content3'});
+ * var tab4 = new Jx.Tab('tab 4', {contentURL: 'test_content.html'});
+ *
+ * tabSet.add(t1, t2, t3, t4);
+ * tabBar.add(t1, t2, t3, t4);
+ * (end)
+ *
+ * Events:
+ * tabChange - the current tab has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabSet = new Class({
+    Family: 'Jx.TabSet',
+    Extends: Jx.Object,
+    /**
+     * Property: tabs
+     * {Array} array of tabs that are managed by this tab set
+     */
+    tabs: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} The HTML element that represents this tab set in the DOM.
+     * The content areas of each tab are sized to fill the domObj.
+     */
+    domObj : null,
+    /**
+     * 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.
+     */
+    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 = document.id(this.options.domObj);
+        if (!this.domObj.hasClass('jxTabSetContainer')) {
+            this.domObj.addClass('jxTabSetContainer');
+        }
+        this.setActiveTabFn = this.setActiveTab.bind(this);
+    },
+    /**
+     * Method: resizeTabBox
+     * Resize the tab set content area and propogate the changes to
+     * each of the tabs managed by the tab set.
+     */
+    resizeTabBox: function() {
+        if (this.activeTab && this.activeTab.content.resize) {
+            this.activeTab.content.resize({forceResize: true});
+        }
+    },
+
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab set.  More
+     * than one tab can be added by passing extra parameters to this method.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(tab) {
+            if (tab instanceof Jx.Tab) {
+                tab.addEvent('down',this.setActiveTabFn);
+                tab.tabSet = this;
+                this.domObj.appendChild(tab.content);
+                this.tabs.push(tab);
+                if ((!this.activeTab || tab.options.active) && tab.options.enabled) {
+                    tab.options.active = false;
+                    tab.setActive(true);
+                }
+            }
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from this TabSet.  Note that it is the caller's responsibility
+     * to remove the tab from the <Jx.Toolbar>.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove: function(tab) {
+        if (tab instanceof Jx.Tab && this.tabs.indexOf(tab) != -1) {
+            this.tabs.erase(tab);
+            if (this.activeTab == tab) {
+                if (this.tabs.length) {
+                    this.tabs[0].setActive(true);
+                }
+            }
+            tab.removeEvent('down',this.setActiveTabFn);
+            tab.content.dispose();
+        }
+    },
+    /**
+     * Method: setActiveTab
+     * Set the active tab to the one passed to this method
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to make active.
+     */
+    setActiveTab: function(tab) {
+        if (this.activeTab && this.activeTab != tab) {
+            this.activeTab.setActive(false);
+        }
+        this.activeTab = tab;
+        if (this.activeTab.content.resize) {
+          this.activeTab.content.resize({forceResize: true});
+        }
+        this.fireEvent('tabChange', [this, tab]);
+    }
+});
+
+
+
+/*
+---
+
+name: Jx.TabBox
+
+description: A convenience class to handle the common case of a single toolbar directly attached to the content area of the tabs.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+ - Jx.Panel
+ - Jx.TabSet
+
+provides: [Jx.TabBox]
+
+images:
+ - tabbar.png
+ - tabbar_bottom.png
+ - tabbar_left.png
+ - tabbar_right.png
+
+...
+ */
+// $Id: tabbox.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TabBox
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * a TabBox, then tabs only have to be added to the TabBox rather than to
+ * both a <Jx.TabSet> and a <Jx.Toolbar>.
+ *
+ * Example:
+ * (code)
+ * var tabBox = new Jx.TabBox('subTabArea', 'top');
+ *
+ * var tab1 = new Jx.Button.Tab('Tab 1', {contentID: 'content4'});
+ * var tab2 = new Jx.Button.Tab('Tab 2', {contentID: 'content5'});
+ *
+ * tabBox.add(tab1, tab2);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabBox = new Class({
+    Family: 'Jx.TabBox',
+    Extends: Jx.Widget,
+    options: {
+        /* Option: parent
+         * a DOM element to add the tab box to
+         */
+        parent: null,
+        /* Option: position
+         * the position of the tab bar in the box, one of 'top', 'right',
+         * 'bottom' or 'left'.  Top by default.
+         */
+        position: 'top',
+        /* Option: height
+         * a fixed height in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        height: null,
+        /* Option: width
+         * a fixed width in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        width: null,
+        /* Option: scroll
+         * should the tab bar scroll its tabs if there are too many to fit
+         * in the toolbar, true by default
+         */
+        scroll:true
+    },
+
+    /**
+     * Property: tabBar
+     * {<Jx.Toolbar>} the toolbar for this tab box.
+     */
+    tabBar: null,
+    /**
+     * Property: tabSet
+     * {<Jx.TabSet>} the tab set for this tab box.
+     */
+    tabSet: null,
+    /**
+     * APIMethod: render
+     * Create a new instance of a TabBox.
+     */
+    render : function() {
+        this.parent();
+        this.tabBar = new Jx.Toolbar({
+            position: this.options.position,
+            scroll: this.options.scroll
+        });
+        this.panel = new Jx.Panel({
+            toolbars: [this.tabBar],
+            hideTitle: true,
+            height: this.options.height,
+            width: this.options.width
+        });
+        this.panel.domObj.addClass('jxTabBox');
+        this.tabSet = new Jx.TabSet(this.panel.content);
+        this.tabSet.addEvent('tabChange', function(tabSet, tab) {
+            this.showItem(tab);
+        }.bind(this.tabBar));
+        this.domObj = this.panel.domObj;
+        /* when the panel changes size, the tab set needs to update
+         * the content areas.
+         */
+         this.panel.addEvent('sizeChange', (function() {
+             this.tabSet.resizeTabBox();
+             this.tabBar.domObj.getParent('.jxBarContainer').retrieve('jxBarContainer').update();
+             this.tabBar.domObj.getParent('.jxBarContainer').addClass('jxTabBar'+this.options.position.capitalize());
+         }).bind(this));
+        /* when tabs are added or removed, we might need to layout
+         * the panel if the toolbar is or becomes empty
+         */
+        this.tabBar.addEvents({
+            add: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this),
+            remove: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this)
+        });
+        /* trigger an initial resize when first added to the DOM */
+        this.addEvent('addTo', function() {
+            this.domObj.resize({forceResize: true});
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabBox.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab box.  More
+     * than one tab can be added by passing extra parameters to this method.
+     * Unlike <Jx.TabSet>, tabs do not have to be added to a separate
+     * <Jx.Toolbar>.
+     */
+    add : function() {
+        this.tabBar.add.apply(this.tabBar, arguments);
+        this.tabSet.add.apply(this.tabSet, arguments);
+        $A(arguments).flatten().each(function(tab){
+            tab.addEvents({
+                close: (function(){
+                    this.tabBar.remove(tab);
+                    this.tabSet.remove(tab);
+                }).bind(this)
+            });
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove : function(tab) {
+        this.tabBar.remove(tab);
+        this.tabSet.remove(tab);
+    }
+});
+/*
+---
+
+name: Jx.Toolbar.Separator
+
+description:  A helper class that represents a visual separator in a Jx.Toolbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+
+provides: [Jx.Toolbar.Separator]
+
+images:
+ - toolbar_separator_h.png
+ - toolbar_separator_v.png
+
+...
+ */
+// $Id: toolbar.separator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Toolbar.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class that represents a visual separator in a <Jx.Toolbar>
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar.Separator = new Class({
+    Family: 'Jx.Toolbar.Separator',
+    Extends: Jx.Widget,
+    /**
+     * APIMethod: render
+     * Create a new Jx.Toolbar.Separator
+     */
+    render: function() {
+        this.domObj = new Element('li', {'class':'jxToolItem'});
+        this.domSpan = new Element('span', {'class':'jxBarSeparator'});
+        this.domObj.appendChild(this.domSpan);
+    }
+});
+/*
+---
+
+name: Jx.Tree
+
+description: Jx.Tree displays hierarchical data in a tree structure of folders and nodes.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.List
+
+provides: [Jx.Tree]
+
+css:
+ - tree
+
+images:
+ - tree.png
+ - tree_vert_line.png
+...
+ */
+// $Id: tree.js 1011 2011-01-24 18:18:42Z pagameba $
+/**
+ * Class: Jx.Tree
+ *
+ * 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'],
+    pluginNamespace: 'Tree',
+    /**
+     * APIProperty: selection
+     * {<Jx.Selection>} the selection object for this tree.
+     */
+    selection: null,
+    /**
+     * Property: ownsSelection
+     * {Boolean} indicates if this object created the <Jx.Selection> object
+     * or not.  If true then the selection object will be destroyed when the
+     * tree is destroyed, otherwise the selection object will not be
+     * destroyed.
+     */
+    ownsSelection: false,
+    /**
+     * Property: list
+     * {<Jx.List>} the list object is used to manage the DOM elements of the
+     * items added to the tree.
+     */
+    list: null,
+    dirty: true,
+    /**
+     * APIProperty: domObj
+     * {HTMLElement} the DOM element that contains the visual representation
+     * of the tree.
+     */
+    domObj: null,
+    options: {
+        /**
+         * Option: select
+         * {Boolean} are items in the tree selectable?  See <Jx.Selection>
+         * for other options relating to selections that can be set here.
+         */
+        select: true,
+        /**
+         * Option: template
+         * the default HTML template for a tree can be overridden
+         */
+        template: '<ul class="jxTreeRoot"></ul>'
+    },
+    /**
+     * APIProperty: classes
+     * {Hash} a hash of property to CSS class names for extracting references
+     * to DOM elements from the supplied templates.  Requires
+     * domObj element, anything else is optional.
+     */
+    classes: new Hash({domObj: 'jxTreeRoot'}),
+    
+    frozen: false,
+    
+    /**
+     * APIMethod: render
+     * Render the Jx.Tree.
+     */
+    render: function() {
+        this.parent();
+        if ($defined(this.options.container) &&
+            document.id(this.options.container)) {
+            this.domObj = this.options.container;
+        }
+
+        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.bound.select = function(item) {
+            this.fireEvent('select', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.unselect = function(item) {
+            this.fireEvent('unselect', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.onAdd = function(item) {this.update();}.bind(this);
+        this.bound.onRemove = function(item) {this.update();}.bind(this);
+
+        if (this.selection && this.ownsSelection) {
+            this.selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+
+        this.list = new Jx.List(this.domObj, {
+                hover: true,
+                press: true,
+                select: true,
+                onAdd: this.bound.onAdd,
+                onRemove: this.bound.onRemove
+            }, this.selection);
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * APIMethod: freeze
+     * stop the tree from processing updates every time something is added or
+     * removed.  Used for bulk changes, call thaw() when done updating.  Note
+     * the tree will still display the changes but it will delay potentially
+     * expensive recursion across the entire tree on every change just to
+     * update visual styles.
+     */
+    freeze: function() { this.frozen = true; },
+    /**
+     * APIMethod: thaw
+     * unfreeze the tree and recursively update styles
+     */
+    thaw: function() { this.frozen = false; this.update(true); },
+    
+    setDirty: function(state) {
+      this.dirty = state;
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+
+    /**
+     * APIMethod: add
+     * add one or more items to the tree at a particular position in the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        if ($type(item) == 'array') {
+            item.each(function(what){ this.add(what, position); }.bind(this) );
+            return;
+        }
+        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);
+        item.owner = this;
+        this.list.add(item, position);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        item.removeEvents('add');
+        item.removeEvents('remove');
+        item.removeEvents('disclose');
+        item.owner = null;
+        this.list.remove(item);
+        item.setSelection(null);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        item.owner = null;
+        withItem.owner = this;
+        this.list.replace(item, withItem);
+        withItem.setSelection(this.selection);
+        item.setSelection(null);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+
+    /**
+     * Method: cleanup
+     * Clean up a Jx.Tree instance
+     */
+    cleanup: function() {
+        // stop updates when removing existing items.
+        this.freeze();
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+            this.selection.destroy();
+            this.selection = null;
+        }
+        this.list.removeEvents({
+          remove: this.bound.onRemove,
+          add: this.bound.onAdd
+        });
+        this.list.destroy();
+        this.list = null;
+        this.bound.select = null;
+        this.bound.unselect = null;
+        this.bound.onRemove = null;
+        this.bound.onAdd = null;
+        this.parent();
+    },
+    
+    /**
+     * 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?
+     */
+    update: function(shouldDescend, isLast) {
+        // since the memory leak cleanup, it seems that update gets called
+        // from the bound onRemove event after the list has been cleaned
+        // up.  I suspect that there is a delayed function call for IE in
+        // event handling (or some such thing) PS
+        if (!this.list) return;
+        if (this.frozen) return;
+        
+        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);
+            }
+        });
+        this.setDirty(false);
+    },
+
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.list.items().map(function(item) {
+            return item.retrieve('jxTreeItem');
+        });
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this tree and any folders in it
+     */
+    empty: function() {
+        this.list.items().each(function(item){
+          var f = item.retrieve('jxTreeItem');
+          if (f && f instanceof Jx.TreeFolder) {
+            f.empty();
+          }
+          if (f && f instanceof Jx.TreeItem) {
+            this.remove(f);
+            f.destroy();
+          }
+        }, this);
+        this.setDirty(true);
+    },
+
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return false;
+        }
+        //path has more than one thing in it, find a folder and descend into it
+        var name = path[0];
+        var result = false;
+        this.list.items().some(function(item) {
+            var treeItem = item.retrieve('jxTreeItem');
+            if (treeItem && treeItem.getLabel() == name) {
+                if (path.length > 0) {
+                    var folder = item.retrieve('jxTreeFolder');
+                    if (folder && path.length > 1) {
+                        result = folder.findChild(path.slice(1, path.length));
+                    } else {
+                      result = treeItem;
+                    }
+                } else {
+                    result = treeItem;
+                }
+            }
+            return result;
+        });
+        return result;
+    },
+    
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object to be used by this tree.  Used primarily
+     * by <Jx.TreeFolder> to propogate a single selection object throughout a
+     * tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining
+     */
+    setSelection: function(selection) {
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents(this.bound);
+            this.selection.destroy();
+            this.ownsSelection = false;
+        }
+        this.selection = selection;
+        this.list.setSelection(selection);
+        this.list.each(function(item) {
+            item.retrieve('jxTreeItem').setSelection(selection);
+        });
+        return this;
+    }
+});
+
+/*
+---
+
+name: Jx.TreeItem
+
+description: An item in a tree.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+optional:
+ - More/Drag
+
+provides: [Jx.TreeItem]
+
+images:
+ - tree_hover.png
+
+...
+ */
+// $Id: treeitem.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TreeItem
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * is responsible for changing the style of the selected item in the tree
+ * and for tracking selection if that is important.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - triggered when the tree item is clicked
+ *
+ * Implements:
+ * Events - MooTools Class.Extras
+ * Options - MooTools Class.Extras
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeItem = new Class ({
+    Family: 'Jx.TreeItem',
+    Extends: Jx.Widget,
+    selection: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} a reference to the HTML element that is the TreeItem
+     * in the DOM
+     */
+    domObj : null,
+    /**
+     * Property: owner
+     * {Object} the folder or tree that this item belongs to
+     */
+    owner: null,
+    options: {
+        /* Option: label
+         * {String} the label to display for the TreeItem
+         */
+        label: '',
+        /* Option: contextMenu
+         * {<Jx.ContextMenu>} the context menu to trigger if there
+         * is a right click on the node
+         */
+        contextMenu: null,
+        /* Option: enabled
+         * {Boolean} the initial state of the TreeItem.  If the
+         * TreeItem is not enabled, it cannot be clicked.
+         */
+        enabled: true,
+        selectable: true,
+        /* Option: image
+         * {String} URL to an image to use as the icon next to the
+         * label of this TreeItem
+         */
+        image: null,
+        /* Option: imageClass
+         * {String} CSS class to apply to the image, useful for using CSS
+         * sprites
+         */
+        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>',
+        busyMask: {
+          message: null
+        }
+    },
+    classes: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel'
+    }),
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeItem with the associated options
+     */
+    render : function() {
+        this.parent();
+
+        this.domObj = this.elements.get('jxTreeContainer');
+        this.domObj.store('jxTreeItem', this);
+        this.domA.store('jxTreeItem', this);
+        if (this.options.contextMenu) {
+          this.domA.store('jxContextMenu', this.options.contextMenu);
+        }
+        /* the target for jxPressed, jxSelected, jxHover classes */
+        this.domObj.store('jxListTarget', this.domA);
+
+        if (!this.options.selectable) {
+            this.domObj.addClass('jxUnselectable');
+        }
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        if (!this.options.enabled) {
+            this.domObj.addClass('jxDisabled');
+        }
+
+        if (this.options.image && this.domIcon) {
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            if (this.options.imageClass) {
+                this.domIcon.addClass(this.options.imageClass);
+            }
+
+        }
+
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        if (this.domA) {
+            this.domA.addEvents({
+                click: this.click.bind(this),
+                dblclick: this.dblclick.bind(this),
+                drag: function(e) { e.stop(); }
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if ($defined(this.options.enabled)) {
+            this.enable(this.options.enabled, true);
+        }
+    },
+    
+    setDirty: function(state) {
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+    
+    /**
+     * Method: finalize
+     * Clean up the TreeItem and remove all DOM references
+     */
+    finalize: function() { this.destroy(); },
+    /**
+     * Method: finalizeItem
+     * Clean up the TreeItem and remove all DOM references
+     */
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeItem');
+      this.domA.eliminate('jxTreeItem');
+      this.domA.eliminate('jxContextMenu');
+      this.domObj.eliminate('jxListTarget');
+      this.domObj.eliminate('jxListTargetItem');
+      this.domA.removeEvents();
+      this.owner = null;
+      this.selection = null;
+      this.parent();
+    },
+    /**
+     * Method: update
+     * Update the CSS of the TreeItem's DOM element in case it has changed
+     * position
+     *
+     * Parameters:
+     * isLast - {Boolean} is the item the last one or not?
+     */
+    update : function(isLast) {
+        if (isLast) {
+            this.domObj.addClass(this.options.lastLeafClass);
+        } else {
+            this.domObj.removeClass(this.options.lastLeafClass);
+        }
+    },
+    click: function() {
+        if (this.options.enabled) {
+            this.fireEvent('click', this);
+        }
+    },
+    dblclick: function() {
+        if (this.options.enabled) {
+            this.fireEvent('dblclick', this);
+        }
+    },
+    /**
+     * Method: select
+     * Select a tree node.
+     */
+    select: function() {
+        if (this.selection && this.options.enabled) {
+            this.selection.select(this.domA);
+        }
+    },
+
+    /**
+     * Method: getLabel
+     * Get the label associated with a TreeItem
+     *
+     * Returns:
+     * {String} the name
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+
+    /**
+     * Method: setLabel
+     * set the label of a tree item
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html',this.getText(label));
+            this.setDirty(true);
+        }
+    },
+
+    setImage: function(url, imageClass) {
+        if (this.domIcon && $defined(url)) {
+            this.options.image = url;
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            this.setDirty(true);
+        }
+        if (this.domIcon && $defined(imageClass)) {
+            this.domIcon.removeClass(this.options.imageClass);
+            this.options.imageClass = imageClass;
+            this.domIcon.addClass(imageClass);
+            this.setDirty(true);
+        }
+    },
+    enable: function(state, force) {
+        if (this.options.enabled != state || force) {
+            this.options.enabled = state;
+            if (this.options.enabled) {
+                this.domObj.removeClass('jxDisabled');
+                this.fireEvent('enabled', this);
+            } else {
+                this.domObj.addClass('jxDisabled');
+                this.fireEvent('disabled', this);
+                if (this.selection) {
+                    this.selection.unselect(document.id(this));
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: propertyChanged
+     * A property of an object has changed, synchronize the state of the
+     * TreeItem with the state of the object
+     *
+     * Parameters:
+     * obj - {Object} the object whose state has changed
+     */
+    propertyChanged : function(obj) {
+        this.options.enabled = obj.isEnabled();
+        if (this.options.enabled) {
+            this.domObj.removeClass('jxDisabled');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    setSelection: function(selection){
+        this.selection = selection;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        this.domImg.addClass(this.options.busyClass)
+      } else {
+        if (this.options.busyClass) {
+          this.domImg.removeClass(this.options.busyClass);
+        }
+      }
+    },
+    changeText : function(lang) {
+      this.parent();
+      this.setLabel(this.options.label);
+    }
+});
+/*
+---
+
+name: Jx.TreeFolder
+
+description: A Jx.TreeFolder is an item in a tree that can contain other items. It is expandable and collapsible.
+
+license: MIT-style license.
+
+requires:
+ - Jx.TreeItem
+ - Jx.Tree
+
+provides: [Jx.TreeFolder]
+
+...
+ */
+// $Id: treefolder.js 1011 2011-01-24 18:18:42Z pagameba $
+/**
+ * Class: Jx.TreeFolder
+ *
+ * A Jx.TreeFolder is an item in a tree that can contain other items.  It is
+ * expandable and collapsible.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Extends:
+ * <Jx.TreeItem>
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeFolder = new Class({
+    Family: 'Jx.TreeFolder',
+    Extends: Jx.TreeItem,
+    /**
+     * Property: tree
+     * {<Jx.Tree>} a Jx.Tree instance for managing the folder contents
+     */
+    tree : null,
+    
+    options: {
+        /* Option: open
+         * is the folder open?  false by default.
+         */
+        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: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel',
+        domTree: 'jxTree'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeFolder
+     */
+    render : function() {
+        this.parent();
+        this.domObj.store('jxTreeFolder', this);
+
+        this.bound.toggle = this.toggle.bind(this);
+
+        this.addEvents({
+            click: this.bound.toggle,
+            dblclick: this.bound.toggle
+        });
+
+        if (this.domImg) {
+            this.domImg.addEvent('click', this.bound.toggle);
+        }
+
+        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.domTree);
+        if (this.options.open) {
+            this.expand();
+        } else {
+            this.collapse();
+        }
+
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeFolder');
+      this.removeEvents({
+        click: this.bound.toggle,
+        dblclick: this.bound.toggle
+      });
+      if (this.domImg) {
+        this.domImg.removeEvent('click', this.bound.toggle);
+      }
+      this.bound.toggle = null;
+      this.tree.destroy();
+      this.tree = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * add one or more items to the folder at a particular position in the
+     * folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        this.tree.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the folder item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        this.tree.remove(item);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        this.tree.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.tree.items();
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this folder and any folders in it
+     */
+    empty: function() {
+        this.tree.empty();
+    },
+    /**
+     * 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?
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    update: function(shouldDescend,isLast) {
+        /* avoid update if not attached to tree yet */
+        if (!this.domObj.parentNode) return;
+        
+        if (this.tree.dirty || (this.owner && this.owner.dirty)) {
+          if (!$defined(isLast)) {
+              isLast = this.domObj.hasClass('jxTreeBranchLastOpen') ||
+                       this.domObj.hasClass('jxTreeBranchLastClosed');
+          }
+
+          ['jxTreeBranchOpen','jxTreeBranchLastOpen','jxTreeBranchClosed',
+          'jxTreeBranchLastClosed'].each(function(c){
+              this.removeClass(c);
+          }, this.domObj);
+
+          var c = 'jxTreeBranch';
+          c += isLast ? 'Last' : '';
+          c += this.options.open ? 'Open' : 'Closed';
+          this.domObj.addClass(c);
+        }
+
+        this.tree.update(shouldDescend, isLast);
+    },
+    /**
+     * APIMethod: toggle
+     * toggle the state of the folder between open and closed
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    toggle: function() {
+        if (this.options.enabled) {
+            if (this.options.open) {
+                this.collapse();
+            } else {
+                this.expand();
+            }
+        }
+        return this;
+    },
+    /**
+     * APIMethod: expand
+     * Expands the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    expand : function() {
+        this.options.open = true;
+        document.id(this.tree).setStyle('display', 'block');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: collapse
+     * Collapses the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    collapse : function() {
+        this.options.open = false;
+        document.id(this.tree).setStyle('display', 'none');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return this;
+        } else {
+            return this.tree.findChild(path);
+        }
+    },
+    /**
+     * Method: setSelection
+     * sets the <Jx.Selection> object to be used by this folder.  Used
+     * to propogate a single selection object throughout a tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    setSelection: function(selection) {
+        this.tree.setSelection(selection);
+        return this;
+    },
+    
+    setDirty: function(state) {
+      this.parent(state);
+      if (this.tree) {
+        this.tree.setDirty(true);
+      }
+    }
+    
+});/*
+---
+
+name: Jx.Slider
+
+description: A wrapper for mootools' slider class to make it more Jx Friendly.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - More/Slider
+
+provides: [Jx.Slider]
+
+css:
+ - slider
+
+...
+ */
+// $Id: slider.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Slider
+ * This class wraps the mootools-more slider class to make it more Jx friendly
+ *
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slider = new Class({
+    Family: 'Jx.Slider',
+    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
+    },
+    classes: new Hash({
+        domObj: 'jxSliderContainer',
+        knob: 'jxSliderKnob'
+    }),
+    slider: null,
+    knob: null,
+    sliderOpts: null,
+    /**
+     * 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.
+     */
+    render: function () {
+        this.parent();
+        
+        /** 
+         * Not sure why this is here...
+         */
+        /**
+        if (this.domObj) {
+            return;
+        }
+        **/
+
+        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 moving and 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);
+    },
+    /**
+     * APIMethod: set
+     * set the value of the slider
+     */
+    set: function(value) {
+      this.slider.set(value);
+    }
+});/*
+---
+
+name: Jx.Notice
+
+description: Represents a single item used in a notifier.
+
+license: MIT-style license.
+
+requires:
+ - Jx.ListItem
+
+provides: [Jx.Notice]
+
+images:
+ - notice.png
+ - notice_error.png
+ - notice_warning.png
+ - notice_success.png
+ - icons.png
+
+
+...
+ */
+// $Id: notice.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notice
+ *
+ * Extends: <Jx.ListItem>
+ *
+ * Events:
+ * 
+ * MooTools.lang Keys:
+ * - notice.closeTip
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notice = new Class({
+
+    Family: 'Jx.Notice',
+    Extends: Jx.ListItem,
+
+    options: {
+        /**
+         * Option: fx
+         * the effect to use on the notice when it is shown and hidden,
+         * 'fade' by default
+         */
+        fx: 'fade',
+        /**
+         * Option: chrome
+         * {Boolean} should the notice be displayed with chrome or not,
+         * default is false
+         */
+        chrome: false,
+        /**
+         * Option: enabled
+         * {Boolean} default is false
+         */
+        enabled: true,
+        /**
+         * Option: template
+         * {String} the HTML template of a notice
+         */
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        /**
+         * Option: klass
+         * {String} css class to add to the notice
+         */
+        klass: ''
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeItemContainer',
+        domItem: 'jxNoticeItem',
+        domContent: 'jxNotice',
+        domClose: 'jxNoticeClose'
+    }),
+
+    /**
+     * Method: render
+     */
+    render: function () {
+        this.parent();
+        
+        if (this.options.klass) {
+            this.domObj.addClass(this.options.klass);
+        }
+        if (this.domClose) {
+            this.domClose.addEvent('click', this.close.bind(this));
+        }
+    },
+    /**
+     * APIMethod: close
+     * close the notice
+     */
+    close: function() {
+        this.fireEvent('close', this);
+    },
+    /**
+     * APIMethod: show
+     * show the notice
+     */
+    show: function(el, onComplete) {
+        if (this.options.chrome) {
+            this.showChrome();
+        }
+        if (this.options.fx) {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        } else {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        }
+    },
+    /**
+     * APIMethod: hide
+     * hide the notice
+     */
+    hide: function(onComplete) {
+        if (this.options.chrome) {
+            this.hideChrome();
+        }
+        if (this.options.fx) {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        } else {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        }
+    },
+
+    changeText : function(lang) {
+        this.parent();
+        //this.render();
+        //this.processElements(this.options.template, this.classes);
+    }
+});
+/**
+ * Class: Jx.Notice.Information
+ * A <Jx.Notice> subclass useful for displaying informational messages
+ */
+Jx.Notice.Information = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeInformation'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying success messages
+ */
+Jx.Notice.Success = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeSuccess'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying warning messages
+ */
+Jx.Notice.Warning = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeWarning'
+    }
+});
+/**
+ * Class: Jx.Notice.Error
+ * A <Jx.Notice> subclass useful for displaying error messages
+ */
+Jx.Notice.Error = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeError'
+    }
+});
+/*
+---
+
+name: Jx.Notifier
+
+description: Base class for notification areas that can hold temporary notices.
+
+license: MIT-style license.
+
+requires:
+ - Jx.ListView
+ - Jx.Notice
+ - Core/Fx.Tween
+
+provides: [Jx.Notifier]
+
+css:
+ - notification
+
+
+...
+ */
+// $Id: notifier.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notifier
+ *
+ * Extends: <Jx.ListView>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier = new Class({
+    
+    Family: 'Jx.Notifier',
+    Extends: Jx.ListView,
+    
+    options: {
+        /**
+         * Option: parent
+         * The parent this notifier is to be placed in. If not specified, it
+         * will be placed in the body of the document.
+         */
+        parent: null,
+        /**
+         * Option: template
+         * This is the template for the notification container itself, not the
+         * actual notice. The actual notice is below in the class property 
+         * noticeTemplate.
+         */
+        template: '<div class="jxNoticeListContainer"><ul class="jxNoticeList"></ul></div>',
+        /**
+         * Option: listOptions
+         * An object holding custom options for the internal Jx.List instance.
+         */
+        listOptions: { }
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeListContainer',
+        listObj: 'jxNoticeList'
+    }),
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        
+        if (!$defined(this.options.parent)) {
+            this.options.parent = document.body;
+        }
+        document.id(this.options.parent).adopt(this.domObj);
+        
+        this.addEvent('postRender', function() {
+            if (Jx.type(this.options.items) == 'array') {
+                this.options.items.each(function(item){
+                    this.add(item);
+                },this);
+            }
+        }.bind(this));
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function (notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.addEvent('close', this.remove.bind(this));
+        notice.show(this.listObj);
+    },
+    
+    /**
+     * APIMethod: remove
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to remove
+     */
+    remove: function (notice) {
+        if (this.domObj.hasChild(notice)) {
+            notice.removeEvents('close');
+            notice.hide();
+        }
+    }
+});/*
+---
+
+name: Jx.Notifier.Float
+
+description: A notification area that floats in a container above other content.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Notifier
+
+provides: [Jx.Notifier.Float]
+
+...
+ */
+// $Id: notifier.float.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notifier.Float
+ * A floating notice area for displaying notices, notices get chrome if
+ * the notifier has chrome
+ *
+ * Extends: <Jx.Notifier>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier.Float = new Class({
+    
+    Family: 'Jx.Notifier.Float',
+    Extends: Jx.Notifier,
+    
+    options: {
+        /**
+         * Option: chrome
+         * {Boolean} should the notifier have chrome - default true
+         */
+        chrome: true,
+        /**
+         * Option: fx
+         * {String} the effect to use when showing and hiding the notifier,
+         * default is null
+         */
+        fx: null,
+        /**
+         * Option: width
+         * {Integer} the width in pixels of the notifier, default is 250
+         */
+        width: 250,
+        /**
+         * Option: position
+         * {Object} position options to use with <Jx.Widget::position>
+         * for positioning the Notifier
+         */
+        position: {
+            horizontal: 'center center',
+            vertical: 'top top'
+        }
+    },
+
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.domObj.setStyle('position','absolute');
+        if ($defined(this.options.width)) {
+            this.domObj.setStyle('width',this.options.width);
+        }
+        this.position(this.domObj, 
+                      this.options.parent,
+                      this.options.position);
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function(notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.options.chrome = this.options.chrome;
+        this.parent(notice);
+    }
+});/*
+---
+
+name: Jx.Scrollbar
+
+description: An implementation of a custom CSS-styled scrollbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Slider
+
+provides: [Jx.Scrollbar]
+
+css:
+ - scrollbar
+
+...
+ */
+// $Id: scrollbar.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Scrollbar
+ * Creates a custom scrollbar either vertically or horizontally (determined by
+ * options). These scrollbars are designed to be styled entirely through CSS.
+ * 
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ * 
+ * Based in part on 'Mootools CSS Styled Scrollbar' on
+ * http://solutoire.com/2008/03/10/mootools-css-styled-scrollbar/
+ */
+Jx.Scrollbar = new Class({
+    
+    Family: 'Jx.Scrollbar',
+    
+    Extends: Jx.Widget,
+    
+    Binds: ['scrollIt'],
+    
+    options: {
+        /**
+         * Option: direction
+         * Determines which bars are visible. Valid options are 'horizontal'
+         * or 'vertical'
+         */
+        direction: 'vertical',
+        /**
+         * Option: useMouseWheel
+         * Whether to allow the mouse wheel to move the content. Defaults 
+         * to true.
+         */
+        useMouseWheel: true,
+        /**
+         * Option: useScrollers
+         * Whether to show the scrollers. Defaults to true.
+         */
+        useScrollers: true,
+        /**
+         * Option: scrollerInterval
+         * The amount to scroll the content when using the scrollers. 
+         * useScrollers option must be true. Default is 50 (px).
+         */
+        scrollerInterval: 50,
+        /**
+         * Option: template
+         * the HTML template for a scrollbar
+         */
+        template: '<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'
+    },
+    
+    classes: new Hash({
+        domObj: 'jxScrollbarContainer',
+        scrollLeft: 'jxScrollLeft',
+        scrollRight: 'jxScrollRight',
+        sliderHolder: 'jxSlider'
+    }),
+    
+    el: null,
+    //element is the element we want to scroll. 
+    parameters: ['element', 'options'],
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.el = document.id(this.options.element);
+        if (this.el) {
+            this.el.addClass('jxHas'+this.options.direction.capitalize()+'Scrollbar');
+            
+            //wrap content to make scroll work correctly
+            var children = this.el.getChildren();
+            this.wrapper = new Element('div',{
+                'class': 'jxScrollbarChildWrapper'
+            });
+            
+            /**
+             * the wrapper needs the same settings as the original container
+             * specifically, the width and height
+             */ 
+            this.wrapper.setStyles({
+                width: this.el.getStyle('width'),
+                height: this.el.getStyle('height')
+            });
+            
+            children.inject(this.wrapper);
+            this.wrapper.inject(this.el);
+            
+            this.domObj.inject(this.el);
+            
+            var scrollSize = this.wrapper.getScrollSize();
+            var size = this.wrapper.getContentBoxSize();
+            this.steps = this.options.direction==='horizontal'?scrollSize.x-size.width:scrollSize.y-size.height;
+            this.slider = new Jx.Slider({
+                snap: false,
+                min: 0,
+                max: this.steps,
+                step: 1,
+                mode: this.options.direction,
+                onChange: this.scrollIt
+                
+            });
+            
+            if (!this.options.useScrollers) {
+                this.scrollLeft.dispose();
+                this.scrollRight.dispose();
+                //set size of the sliderHolder
+                if (this.options.direction === 'horizontal') {
+                    this.sliderHolder.setStyle('width','100%');
+                } else {
+                    this.sliderHolder.setStyle('height', '100%');
+                }
+                
+            } else {
+                this.scrollLeft.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                this.scrollRight.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                //set size of the sliderHolder
+                var holderSize, scrollerRightSize, scrollerLeftSize;
+                if (this.options.direction === 'horizontal') {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().width;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().width;
+                    holderSize = size.width - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('width', holderSize + 'px');
+                } else {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().height;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().height;
+                    holderSize = size.height - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('height', holderSize + 'px');
+                }
+            }
+            document.id(this.slider).inject(this.sliderHolder);
+            
+            //allows mouse wheel to function
+            if (this.options.useMouseWheel) {
+                $$(this.el, this.domObj).addEvent('mousewheel', function(e){
+                    e = new Event(e).stop();
+                    var step = this.slider.slider.step - e.wheel * 30;
+                    this.slider.slider.set(step);
+                }.bind(this));
+            }
+            
+            //stop slider if we leave the window
+            document.id(document.body).addEvent('mouseleave', function(){ 
+                this.slider.slider.drag.stop();
+            }.bind(this));
+
+            this.slider.start();
+        }
+    },
+    
+    /**
+     * Method: scrollIt
+     * scroll the content in response to the slider being moved.
+     */
+    scrollIt: function (step) {
+        var x = this.options.direction==='horizontal'?step:0;
+        var y = this.options.direction==='horizontal'?0:step;
+        this.wrapper.scrollTo(x,y);
+    }
+});/*
+---
+
+name: Jx.Formatter
+
+description: Base formatter object
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Formatter]
+
+...
+ */
+ // $Id: formatter.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Formatter',
+    Extends: Jx.Object,
+
+    /**
+     * APIMethod: format
+     * Empty method that must be overridden by subclasses to provide
+     * the needed formatting functionality.
+     */
+    format: $empty
+});/*
+---
+
+name: Jx.Formatter.Number
+
+description: Formats numbers including negative and floats
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Number]
+
+...
+ */
+// $Id: number.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.number'.decimalSeparator
+ * - 'formatter.number'.thousandsSeparator
+ *
+ * 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: 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);
+
+        //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.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                } else if (i >= left) {
+                    j++;
+                    if (j === 3 && i !== l - 1) {
+                        ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                        j = 0;
+                    }
+                }
+
+            }
+        } else {
+            ret = parts[0];
+        }
+
+        if (dec) {
+            ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'decimalSeparator'}) + parts[1];
+        }
+        if (neg && this.options.useParens) {
+            ret = "(" + ret + ")";
+        } else if (neg && !this.options.useParens) {
+            ret = "-" + ret;
+        }
+
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});/*
+---
+
+name: Jx.Formatter.Currency
+
+description: Formats input as currency. Currently only US currency is supported
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter.Number
+
+provides: [Jx.Formatter.Currency]
+
+...
+ */
+// $Id: currency.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.currency'.sign
+ *
+ * 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: {},
+    /**
+     * APIMethod: format
+     * Takes a number and formats it as currency.
+     *
+     * Parameters:
+     * value - the number to format
+     */
+    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.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value.substring(1, value.length);
+        } else {
+            ret = this.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value;
+        }
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});/*
+---
+
+name: Jx.Formatter.Date
+
+description: Formats dates using the mootools-more Date extensions
+
+license: MIT-style license.
+
+requires:
+ - More/Date.Extras
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Date]
+...
+ */
+// $Id: date.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+        var d = Date.parse(value);
+        return d.format(this.options.format);
+    }
+});/*
+---
+
+name: Jx.Formatter.URI
+
+description: Formats uris using the mootools-more URI extensions
+
+license: MIT-style license.
+
+requires:
+ - More/String.Extras
+ - Jx.Formatter
+ - More/URI
+
+provides: [Jx.Formatter.URI]
+
+...
+ */
+// $Id: uri.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Formatter.URI
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats URIs using the mootools-more's
+ * URI extensions. See the -more docs for details of
+ * supported formats for parsing and formatting.
+ * 
+ * @url http://mootools.net/docs/more/Native/URI
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Uri = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {
+        /**
+         * Option: format
+         * The format to use. See the mootools-more URI options
+         * to use within a {pattern}
+         *   {string} will call the URI.toString() method
+         */
+        format: '<a href="{string}" target="_blank">{host}</a>'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+      var uri        = new URI(value),
+          uriContent = {},
+          pattern    = new Array(),
+          patternTmp = this.options.format.match(/\\?\{([^{}]+)\}/g);
+
+      // remove bracktes
+      patternTmp.each(function(e) {
+        pattern.push(e.slice(1, e.length-1));
+      });
+
+      // build object that contains replacements
+      for(var i = 0, j = pattern.length; i < j; i++) {
+        switch(pattern[i]) {
+          case 'string':
+            uriContent[pattern[i]] = uri.toString();
+            break;
+          default:
+            uriContent[pattern[i]] = uri.get(pattern[i]);
+            break;
+        }
+      }
+      return this.options.format.substitute(uriContent);
+    }
+});/*
+---
+
+name: Jx.Formatter.Boolean
+
+description: Formats boolean input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Boolean]
+...
+ */
+// $Id: boolean.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Formatter.Boolean
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats boolean values. You supply the
+ * text values for true and false in the options.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * MooTools.lang Keys:
+ * - 'formatter.boolean'.true
+ * - 'formatter.boolean'.false
+ * 
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Boolean = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {},
+    /**
+     * APIMethod: format
+     * Takes a value, determines boolean equivalent and
+     * displays the appropriate text value.
+     *
+     * 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.getText({set:'Jx',key:'formatter.boolean',value:'true'}) : this.getText({set:'Jx',key:'formatter.boolean',value:'false'});
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+
+});/*
+---
+
+name: Jx.Formatter.Phone
+
+description: Formats phone numbers in US format including area code
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+
+provides: [Jx.Formatter.Phone]
+
+...
+ */
+// $Id: phone.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 {
+                ret = ret + sep + v.substring(0, 3) + sep;
+            }
+            v = v.substring(3);
+        }
+        //do the rest of the number
+        ret = ret + v.substring(0, 3) + sep + v.substring(3);
+        return ret;
+    }
+});/*
+---
+
+name: Jx.Formatter.Text
+
+description: Formats strings by limiting to a max length
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Text]
+
+...
+ */
+// $Id: $
+/**
+ * Class: Jx.Formatter.Text
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats strings by limiting them to a maximum length
+ * and replacing the remainder with an ellipsis.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010, Hughes Gauthier.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Text = new Class({
+
+  Extends: Jx.Formatter,
+
+  options: {
+    /**
+     * Option: length
+     * {Integer} default null, if set to an integer value greater than
+     * 0 then the value will be truncated to length characters and
+     * the remaining characters will be replaced by an ellipsis (...)
+     */
+    length: null,
+    /**
+     * Option: ellipsis
+     * {String} the text to use as the ellipsis when truncating a string
+     * default is three periods (...)
+     */
+    ellipsis: '...'
+  },
+
+  format : function (value) {
+    var text = '' + value,
+        max = this.options.length,
+        ellipsis = this.options.ellipsis;
+
+    if (max && text.length > max) {
+      text = text.substr(0,max-ellipsis.length) + ellipsis;
+    }
+
+    return text;
+  }
+});/*
+---
+
+name: Jx.Field.Check
+
+description: Represents a checkbox input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Checkbox]
+
+...
+ */
+// $Id: checkbox.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 : '<span class="jxInputContainer"><input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * Whether this field is checked or not
+         */
+        checked : false,
+
+        labelSeparator: ''
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    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.id(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");
+            }
+        }
+
+        // add click event to the label to toggle the checkbox
+        if(this.label) {
+          this.label.addEvent('click', function(ev) {
+            this.setValue(this.getValue() != null ? false : true)
+          }.bind(this));
+        }
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - Whether the box shouldbe checked or not. "checked" or "true" if it should be checked.
+     */
+    setValue : function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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.
+     */
+    reset : function () {
+        if (this.options.checked) {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
+        }
+    },
+
+    getChecked: function () {
+        return this.field.get("checked");
+    }
+
+});
+/*
+---
+
+name: Jx.Field.Radio
+
+description: Represents a radio button input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Radio]
+
+...
+ */
+// $Id: radio.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * whether this radio button is checked or not
+         */
+        checked: false,
+
+        labelSeparator: ''
+    },
+    /**
+     * 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.id(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");
+            }
+        }
+
+        // add click event to toggle the radio buttons
+        this.label.addEvent('click', function(ev) {
+          this.field.checked ? this.setValue(false) : this.setValue(true);
+        }.bind(this));
+
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to, "checked" it should be checked.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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');
+        }
+    }
+
+});
+
+
+
+
+/*
+---
+
+name: Jx.Field.Select
+
+description: Represents a select, or drop down, input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Select]
+
+...
+ */
+// $Id: select.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ *
+ */
+
+Jx.Field.Select = new Class({
+
+    Extends: Jx.Field,
+
+    options: {
+        /**
+         * Option: multiple
+         * {Boolean} optional, defaults to false.  If true, then the select
+         * will support multi-select
+         */
+        mulitple: false,
+        /**
+         * Option: size
+         * {Integer} optional, defaults to 1.  If set, then this specifies
+         * the number of rows of the select that are visible
+         */
+        size: 1,
+        /**
+         * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * Indictes this type of field.
+     */
+    type: 'Select',
+
+    /**
+     * APIMethod: render
+     * Creates a select field.
+     */
+    render: function () {
+        this.parent();
+        this.field.addEvent('change', function() {this.fireEvent('change', this);}.bind(this));
+        if ($defined(this.options.multiple)) {
+          this.field.set('multiple', this.options.multiple);
+        }
+        if ($defined(this.options.size)) {
+          this.field.set('size', this.options.size);
+        }
+        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': this.getText(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) {
+                this.addOption(item);
+            }, this);
+        }
+    },
+
+    /**
+     * Method: addOption
+     * add an option to the select list
+     *
+     * Parameters:
+     * item - The option to add.
+     * position (optional) - an integer index or the string 'top'.
+     *                     - default is to add at the bottom.
+     */
+    addOption: function (item, position) {
+        var opt = new Element('option', {
+            'value': item.value,
+            'html': this.getText(item.text)
+        });
+        if ($defined(item.selected) && item.selected) {
+            opt.set("selected", "selected");
+        }
+        var where = 'bottom';
+        var field = this.field;
+        if ($defined(position)) {
+            if (Jx.type(position) == 'integer' &&
+                (position >= 0  && position < field.options.length)) {
+                field = this.field.options[position];
+                where = 'before';
+            } else if (position == 'top') {
+                where = 'top';
+            }
+
+        }
+        opt.inject(field, where);
+    },
+
+    /**
+     * Method: removeOption
+     * removes an option from the select list
+     *
+     * Parameters:
+     *  item - The option to remove.
+     */
+    removeOption: function (item) {
+        //TBD
+    },
+    /**
+     * Method: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            //loop through the options and set the one that matches v
+            $$(this.field.options).each(function (opt) {
+                if (opt.get('value') === v) {
+                    document.id(opt).set("selected", true);
+                }
+            }, this);
+        }
+    },
+
+    /**
+     * Method: getValue
+     * Returns the current value of the field.
+     */
+    getValue: function () {
+        var index = this.field.selectedIndex;
+        //check for a set "value" attribute. If not there return the text
+        if (index > -1) {
+            var ret = this.field.options[index].get("value");
+            if (!$defined(ret)) {
+                ret = this.field.options[index].get("text");
+            }
+            return ret;
+        }
+    },
+    
+    /**
+     * APIMethod: empty
+     * Empties all options from this select
+     */
+    empty: function () {
+        if ($defined(this.field.options)) {
+            $A(this.field.options).each(function (option) {
+                this.field.remove(option);
+            }, this);
+        }
+    }
+});/*
+---
+
+name: Jx.Field.Textarea
+
+description: Represents a textarea input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Textarea]
+
+...
+ */
+// $Id: textarea.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * The type of field this is.
+     */
+    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 ($defined(this.options.columns)) {
+            this.field.set('cols', this.options.columns);
+        }
+
+        //TODO: Do we need to use OverText here as well??
+
+    }
+});/*
+---
+
+name: Jx.Field.Button
+
+description: Represents a button input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+ - Jx.Button
+
+provides: [Jx.Field.Button]
+
+...
+ */
+/**
+ * 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: buttonClass
+         * choose the actual Jx.Button subclass to create for this form
+         * field.  The default is to create a basic Jx.Button.  To create
+         * a different kind of button, pass the class to this option, for
+         * instance:
+         * (code)
+         * buttonClass: Jx.Button.Color
+         * (end)
+         */
+        buttonClass: Jx.Button,
+        
+        /**
+         * Option: buttonOptions
+         */
+        buttonOptions: {},
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span></span>'
+    },
+    
+    button: null,
+    
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Button',
+
+    processTemplate: function(template, classes, container) {
+        var h = this.parent(template, classes, container);
+        this.button = new this.options.buttonClass(this.options.buttonOptions);
+        this.button.addEvent('click', function(){
+          this.fireEvent('click');
+        }.bind(this));
+        var c = h.get('jxInputButton');
+        if (c) {
+            this.button.domObj.replaces(c);
+        }
+        this.button.setEnabled(!this.options.disabled);
+        return h;
+    },
+    
+    click: function() {
+        this.button.clicked();
+    },
+    
+    enable: function() {
+      this.parent();
+      this.button.setEnabled(true);
+    },
+    
+    disable: function() {
+      this.parent();
+      this.button.setEnabled(false);
+    }
+});/*
+---
+
+name: Jx.Field.Combo
+
+description: Represents an editable combo
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+ - Jx.Button
+ - Jx.Menu
+ - Jx.Menu.Item
+ - Jx.ButtonSet
+
+provides: [Jx.Field.Combo]
+
+...
+ */
+// $Id: jxcombo.js 993 2010-10-07 19:29:08Z pagameba $
+/**
+ * Class: Jx.Field.Combo
+ *
+ * Extends: <Jx.Field>
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - 
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Combo = new Class({
+    Family: 'Jx.Field.Combo',
+    Extends: Jx.Field,
+    pluginNamespace: 'Combo',
+
+    options: {
+        buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+        /* Option: template
+         */
+         template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'
+     },
+     
+     type: 'Combo',
+     
+    /**
+     * APIMethod: render
+     * create a new instance of Jx.Field.Combo
+     */
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+        
+        var button = new Jx.Button({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon'
+        }).addTo(this.revealer);
+
+        this.menu = new Jx.Menu();
+        this.menu.button = button;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.buttonSet = new Jx.ButtonSet({
+            onChange: (function(set) {
+                var button = set.activeButton;
+                var l = button.options.label;
+                if (l == '&nbsp;') {
+                    l = '';
+                }
+                this.setLabel(l);
+                var img = button.options.image;
+                if (img.indexOf('a_pixel') != -1) {
+                    img = '';
+                }
+                this.setImage(img, button.options.imageClass);
+
+                this.fireEvent('change', this);
+            }).bind(this)
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+        var that = this;
+        button.addEvent('click', function(e) {
+            if (this.list.count() === 0) {
+                return;
+            }
+            if (!button.options.enabled) {
+                return;
+            }
+            this.contentContainer.setStyle('visibility','hidden');
+            this.contentContainer.setStyle('display','block');
+            document.id(document.body).adopt(this.contentContainer);
+            /* we have to size the container for IE to render the chrome correctly
+             * but just in the menu/sub menu case - there is some horrible peekaboo
+             * bug in IE related to ULs that we just couldn't figure out
+             */
+            this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+            this.showChrome(this.contentContainer);
+
+            this.position(this.contentContainer, that.field, {
+                horizontal: ['left left', 'right right'],
+                vertical: ['bottom top', 'top bottom'],
+                offsets: this.chromeOffsets
+            });
+
+            this.contentContainer.setStyle('visibility','');
+
+            document.addEvent('mousedown', this.bound.hide);
+            document.addEvent('keyup', this.bound.keypress);
+
+            this.fireEvent('show', this);
+        }.bindWithEvent(this.menu));
+
+        this.menu.addEvents({
+            'show': (function() {
+                //this.setActive(true);
+            }).bind(this),
+            'hide': (function() {
+                //this.setActive(false);
+            }).bind(this)
+        });
+    },
+    
+    setLabel: function(label) {
+      if ($defined(this.field)) {
+        this.field.value = this.getText(label);
+      }
+    },
+    
+    setImage: function(url, imageClass) {
+      if ($defined(this.icon)) {
+        this.icon.setStyle('background-image', 'url('+url+')');
+        this.icon.setStyle('background-repeat', 'no-repeat');
+
+        if (this.options.imageClass) {
+            this.icon.removeClass(this.options.imageClass);
+        }
+        if (imageClass) {
+            this.options.imageClass = imageClass;
+            this.icon.addClass(imageClass);
+            this.icon.setStyle('background-position','');
+        } else {
+            this.options.imageClass = null;
+            this.icon.setStyle('background-position','center center');
+        }
+      }
+      if (!url) {
+        this.wrapper.addClass('jxInputIconHidden');
+      } else {
+        this.wrapper.removeClass('jxInputIconHidden');
+      }
+    },
+
+    /**
+     * Method: valueChanged
+     * invoked when the current value is changed
+     */
+    valueChanged: function() {
+        this.fireEvent('change', this);
+    },
+
+    setValue: function(value) {
+        this.field.set('value', value);
+        this.buttonSet.buttons.each(function(button){
+          button.setActive(button.options.label === value);
+        },this);
+    },
+
+    /**
+     * Method: onKeyPress
+     * Handle the user pressing a key by looking for an ENTER key to set the
+     * value.
+     *
+     * Parameters:
+     * e - {Event} the keypress event
+     */
+    onKeyPress: function(e) {
+        if (e.key == 'enter') {
+            this.valueChanged();
+        }
+    },
+
+    /**
+     * Method: add
+     * add a new item to the pick list
+     *
+     * Parameters:
+     * options - {Object} object with properties suitable to be passed to
+     * a <Jx.Menu.Item.Options> object.  More than one options object can be
+     * passed, comma separated or in an array.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(opt) {
+            var button = new Jx.Menu.Item($merge(opt,{
+                toggle: true
+            }));
+            this.menu.add(button);
+            this.buttonSet.add(button);
+            if (opt.selected) {
+              this.buttonSet.setActiveButton(button);
+            }
+        }, this);
+    },
+
+    /**
+     * Method: remove
+     * Remove the item at the given index.  Not implemented.
+     *
+     * Parameters:
+     * idx - {Mixed} the item to remove by reference or by index.
+     */
+    remove: function(idx) {
+      var item;
+      if ($type(idx) == 'number' && idx < this.buttonSet.buttons.length) {
+        item = this.buttonSet.buttons[idx];
+      } else if ($type(idx) == 'string'){
+        this.buttonSet.buttons.some(function(button){
+            if (button.options.label === idx) {
+                item = button;
+                return true;
+            }
+            return false;
+        },this);
+      }
+      if (item) {
+        this.buttonSet.remove(item);
+        this.menu.remove(item);
+      }
+    },
+    /**
+     * APIMethod: empty
+     * remove all values from the combo
+     */
+    empty: function() {
+      this.menu.empty();
+      this.buttonSet.empty();
+      this.setLabel('');
+      this.setImage(Jx.aPixel.src);
+    },
+    
+    enable: function() {
+      this.parent();
+      this.menu.setEnabled(true);
+    },
+    
+    disable: function() {
+      this.parent();
+      this.menu.setEnabled(false);
+    }
+    
+});/*
+---
+
+name: Jx.Field.Password
+
+description: Represents a password input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field.Text
+
+provides: [Jx.Field.Password]
+
+...
+ */
+// $Id: password.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel" ></label><input class="jxInputPassword" type="password" name="{name}"/><span class="jxInputTag"></span></span>'
+    },
+
+    type: 'Password'
+});/*
+---
+
+name: Jx.Field.Color
+
+description: Represents an input field with a jx.button.color
+
+license: MIT-style license.
+
+requires:
+ - Jx.Text
+ - Jx.Button.Color
+ - Jx.Form
+ - Jx.Plugin.Field.Validator
+
+provides: [Jx.Field.Color]
+
+...
+ */
+/**
+ * Class: Jx.Field.Color
+ *
+ * Extends: <Jx.Field>
+ *
+ * This class provides a Jx.Field.Text in combination with a Jx.Button.Color
+ * to have a Colorpicker with an input field.
+ *
+ * License:
+ * Copyright (c) 2010, Paul Spener, Fred Warnock, Conrad Barthelmes
+ *
+ * This file is licensed under an MIT style license
+ */
+  Jx.Field.Color = new Class({
+    Extends: Jx.Field,
+    Binds: ['changed','hide','keyup','changeText'],
+    type: 'Color',
+    options: {
+      buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+      /**
+       * Option: template
+       * The template used to render this field
+       */
+      template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',
+      /**
+       * Option: showOnHover
+       * {Boolean} show the color palette when hovering over the input, default 
+       * is false
+       */
+      showOnHover: false,
+      /**
+       *  Option: showDelay
+       *  set time in milliseconds when to show the color field on mouseenter
+       */
+      showDelay: 250,
+      /**
+       * Option: errorMsg
+       * error message for the validator.
+       */
+      errorMsg: 'Invalid Web-Color',
+      /**
+       * Option: color
+       * a color to initialize the field with, defaults to #000000
+       * (black) if not specified.
+       */
+      color: '#000000'
+
+    },
+    button: null,
+    validator: null,
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+
+      var self = this;
+      if (!Jx.Field.Color.ColorPalette) {
+          Jx.Field.Color.ColorPalette = new Jx.ColorPalette(this.options);
+      }
+      this.button = new Jx.Button.Flyout({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon',
+          positionElement: this.field,
+          onBeforeOpen: function() {
+            if (Jx.Field.Color.ColorPalette.currentButton) {
+                Jx.Field.Color.ColorPalette.currentButton.hide();
+            }
+            Jx.Field.Color.ColorPalette.currentButton = this;
+            Jx.Field.Color.ColorPalette.addEvent('change', self.changed);
+            Jx.Field.Color.ColorPalette.addEvent('click', self.hide);
+            this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);
+            Jx.Field.Color.ColorPalette.domObj.setStyle('display', 'block');
+          },
+          onOpen: function() {
+            /* setting these before causes an update problem when clicking on
+             * a second color button when another one is open - the color
+             * wasn't updating properly
+             */
+            Jx.Field.Color.ColorPalette.options.color = self.options.color;
+            Jx.Field.Color.ColorPalette.updateSelected();
+          }
+        }).addTo(this.revealer);
+
+      this.validator = new Jx.Plugin.Field.Validator({
+        validators: [{
+            validatorClass: 'colorHex',
+            validator: {
+              name: 'colorValidator',
+              options: {
+                validateOnChange: false,
+                errorMsg: self.options.errorMsg,
+                test: function(field,props) {
+                  try {
+                    var c = field.get('value').hexToRgb(true);
+                    if(c == null) return false;
+                    for(var i = 0; i < 3; i++) {
+                      if(c[i].toString() == 'NaN') {
+                        return false;
+                      }
+                    }
+                  }catch(e) {
+                    return false;
+                  }
+                  c = c.rgbToHex().toUpperCase();
+                  self.setColor(c);
+                  return true;
+                }
+              }
+            }
+        }],
+        validateOnBlur: true,
+        validateOnChange: true
+      });
+      this.validator.attach(this);
+      this.field.addEvent('keyup', this.onKeyUp.bind(this));
+      if (this.options.showOnHover) {
+        this.field.addEvent('mouseenter', function(ev) {
+          self.button.clicked.delay(self.options.showDelay, self.button);
+        });
+      }
+      this.setValue(this.options.color);
+      this.icon.setStyle('background-color', this.options.color);
+      //this.addEvent('change', self.changed);
+    },
+    /*
+     * Method: onKeyUp
+     *
+     * listens to the keyup event and validates the input for a hex color
+     *
+     */
+    onKeyUp : function(ev) {
+      var color = this.getValue();
+      if (color.substring(0,1) == '#') {
+          color = color.substring(1);
+      }
+      if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+          this.options.color = '#' +color.toUpperCase();
+          this.setColor(this.options.color);
+      }
+    },
+    setColor: function(c) {
+        this.options.color = c;
+        this.setValue(c);
+        this.icon.setStyle('background-color', c);
+    },
+    changed: function() {
+        var c = Jx.Field.Color.ColorPalette.options.color;
+        this.setColor(c);
+    },
+    hide: function() {
+        this.button.setActive(false);
+        Jx.Field.Color.ColorPalette.removeEvent('change', this.changed);
+        Jx.Field.Color.ColorPalette.removeEvent('click', this.hide);
+
+        this.button.hide();
+        Jx.Field.Color.ColorPalette.currentButton = null;
+    },
+    changeText: function(lang) {
+      this.parent();
+    }
+  });

Added: trunk/lib/jxLib/jxlib.uncompressed.24Jan.js
===================================================================
--- trunk/lib/jxLib/jxlib.uncompressed.24Jan.js	                        (rev 0)
+++ trunk/lib/jxLib/jxlib.uncompressed.24Jan.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,39158 @@
+/******************************************************************************
+ * 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, 3.0alpha
+ * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+/*
+---
+
+script: Core.js
+
+description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [MooTools, Native, Hash.base, Array.each, $util]
+
+...
+*/
+
+var MooTools = {
+	'version': '1.2.5dev',
+	'build': '%build%'
+};
+
+var Native = function(options){
+	options = options || {};
+	var name = options.name;
+	var legacy = options.legacy;
+	var protect = options.protect;
+	var methods = options.implement;
+	var generics = options.generics;
+	var initialize = options.initialize;
+	var afterImplement = options.afterImplement || function(){};
+	var object = initialize || legacy;
+	generics = generics !== false;
+
+	object.constructor = Native;
+	object.$family = {name: 'native'};
+	if (legacy && initialize) object.prototype = legacy.prototype;
+	object.prototype.constructor = object;
+
+	if (name){
+		var family = name.toLowerCase();
+		object.prototype.$family = {name: family};
+		Native.typize(object, family);
+	}
+
+	var add = function(obj, name, method, force){
+		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
+		if (generics) Native.genericize(obj, name, protect);
+		afterImplement.call(obj, name, method);
+		return obj;
+	};
+
+	object.alias = function(a1, a2, a3){
+		if (typeof a1 == 'string'){
+			var pa1 = this.prototype[a1];
+			if ((a1 = pa1)) return add(this, a2, a1, a3);
+		}
+		for (var a in a1) this.alias(a, a1[a], a2);
+		return this;
+	};
+
+	object.implement = function(a1, a2, a3){
+		if (typeof a1 == 'string') return add(this, a1, a2, a3);
+		for (var p in a1) add(this, p, a1[p], a2);
+		return this;
+	};
+
+	if (methods) object.implement(methods);
+
+	return object;
+};
+
+Native.genericize = function(object, property, check){
+	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
+		var args = Array.prototype.slice.call(arguments);
+		return object.prototype[property].apply(args.shift(), args);
+	};
+};
+
+Native.implement = function(objects, properties){
+	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
+};
+
+Native.typize = function(object, family){
+	if (!object.type) object.type = function(item){
+		return ($type(item) === family);
+	};
+};
+
+(function(){
+	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
+	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
+
+	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
+	for (var t in types) Native.typize(types[t], t);
+
+	var generics = {
+		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
+		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
+	};
+	for (var g in generics){
+		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
+	}
+})();
+
+var Hash = new Native({
+
+	name: 'Hash',
+
+	initialize: function(object){
+		if ($type(object) == 'hash') object = $unlink(object.getClean());
+		for (var key in object) this[key] = object[key];
+		return this;
+	}
+
+});
+
+Hash.implement({
+
+	forEach: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
+		}
+	},
+
+	getClean: function(){
+		var clean = {};
+		for (var key in this){
+			if (this.hasOwnProperty(key)) clean[key] = this[key];
+		}
+		return clean;
+	},
+
+	getLength: function(){
+		var length = 0;
+		for (var key in this){
+			if (this.hasOwnProperty(key)) length++;
+		}
+		return length;
+	}
+
+});
+
+Hash.alias('forEach', 'each');
+
+Array.implement({
+
+	forEach: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
+	}
+
+});
+
+Array.alias('forEach', 'each');
+
+function $A(iterable){
+	if (iterable.item){
+		var l = iterable.length, array = new Array(l);
+		while (l--) array[l] = iterable[l];
+		return array;
+	}
+	return Array.prototype.slice.call(iterable);
+};
+
+function $arguments(i){
+	return function(){
+		return arguments[i];
+	};
+};
+
+function $chk(obj){
+	return !!(obj || obj === 0);
+};
+
+function $clear(timer){
+	clearTimeout(timer);
+	clearInterval(timer);
+	return null;
+};
+
+function $defined(obj){
+	return (obj != undefined);
+};
+
+function $each(iterable, fn, bind){
+	var type = $type(iterable);
+	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
+};
+
+function $empty(){};
+
+function $extend(original, extended){
+	for (var key in (extended || {})) original[key] = extended[key];
+	return original;
+};
+
+function $H(object){
+	return new Hash(object);
+};
+
+function $lambda(value){
+	return ($type(value) == 'function') ? value : function(){
+		return value;
+	};
+};
+
+function $merge(){
+	var args = Array.slice(arguments);
+	args.unshift({});
+	return $mixin.apply(null, args);
+};
+
+function $mixin(mix){
+	for (var i = 1, l = arguments.length; i < l; i++){
+		var object = arguments[i];
+		if ($type(object) != 'object') continue;
+		for (var key in object){
+			var op = object[key], mp = mix[key];
+			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
+		}
+	}
+	return mix;
+};
+
+function $pick(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		if (arguments[i] != undefined) return arguments[i];
+	}
+	return null;
+};
+
+function $random(min, max){
+	return Math.floor(Math.random() * (max - min + 1) + min);
+};
+
+function $splat(obj){
+	var type = $type(obj);
+	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
+};
+
+var $time = Date.now || function(){
+	return +new Date;
+};
+
+function $try(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		try {
+			return arguments[i]();
+		} catch(e){}
+	}
+	return null;
+};
+
+function $type(obj){
+	if (obj == undefined) return false;
+	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
+	if (obj.nodeName){
+		switch (obj.nodeType){
+			case 1: return 'element';
+			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
+		}
+	} else if (typeof obj.length == 'number'){
+		if (obj.callee) return 'arguments';
+		else if (obj.item) return 'collection';
+	}
+	return typeof obj;
+};
+
+function $unlink(object){
+	var unlinked;
+	switch ($type(object)){
+		case 'object':
+			unlinked = {};
+			for (var p in object) unlinked[p] = $unlink(object[p]);
+		break;
+		case 'hash':
+			unlinked = new Hash(object);
+		break;
+		case 'array':
+			unlinked = [];
+			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+		break;
+		default: return object;
+	}
+	return unlinked;
+};
+/*
+---
+
+script: Browser.js
+
+description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: 
+- /Native
+- /$util
+
+provides: [Browser, Window, Document, $exec]
+
+...
+*/
+
+var Browser = $merge({
+
+	Engine: {name: 'unknown', version: 0},
+
+	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
+
+	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
+
+	Plugins: {},
+
+	Engines: {
+
+		presto: function(){
+			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
+		},
+
+		trident: function(){
+			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
+		},
+
+		webkit: function(){
+			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
+		},
+
+		gecko: function(){
+			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
+		}
+
+	}
+
+}, Browser || {});
+
+Browser.Platform[Browser.Platform.name] = true;
+
+Browser.detect = function(){
+
+	for (var engine in this.Engines){
+		var version = this.Engines[engine]();
+		if (version){
+			this.Engine = {name: engine, version: version};
+			this.Engine[engine] = this.Engine[engine + version] = true;
+			break;
+		}
+	}
+
+	return {name: engine, version: version};
+
+};
+
+Browser.detect();
+
+Browser.Request = function(){
+	return $try(function(){
+		return new XMLHttpRequest();
+	}, function(){
+		return new ActiveXObject('MSXML2.XMLHTTP');
+	}, function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
+	});
+};
+
+Browser.Features.xhr = !!(Browser.Request());
+
+Browser.Plugins.Flash = (function(){
+	var version = ($try(function(){
+		return navigator.plugins['Shockwave Flash'].description;
+	}, function(){
+		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+	}) || '0 r0').match(/\d+/g);
+	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+})();
+
+function $exec(text){
+	if (!text) return text;
+	if (window.execScript){
+		window.execScript(text);
+	} else {
+		var script = document.createElement('script');
+		script.setAttribute('type', 'text/javascript');
+		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
+		document.head.appendChild(script);
+		document.head.removeChild(script);
+	}
+	return text;
+};
+
+Native.UID = 1;
+
+var $uid = (Browser.Engine.trident) ? function(item){
+	return (item.uid || (item.uid = [Native.UID++]))[0];
+} : function(item){
+	return item.uid || (item.uid = Native.UID++);
+};
+
+var Window = new Native({
+
+	name: 'Window',
+
+	legacy: (Browser.Engine.trident) ? null: window.Window,
+
+	initialize: function(win){
+		$uid(win);
+		if (!win.Element){
+			win.Element = $empty;
+			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
+			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
+		}
+		win.document.window = win;
+		return $extend(win, Window.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		window[property] = Window.Prototype[property] = value;
+	}
+
+});
+
+Window.Prototype = {$family: {name: 'window'}};
+
+new Window(window);
+
+var Document = new Native({
+
+	name: 'Document',
+
+	legacy: (Browser.Engine.trident) ? null: window.Document,
+
+	initialize: function(doc){
+		$uid(doc);
+		doc.head = doc.getElementsByTagName('head')[0];
+		doc.html = doc.getElementsByTagName('html')[0];
+		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
+			doc.execCommand("BackgroundImageCache", false, true);
+		});
+		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
+			doc.window.detachEvent('onunload', arguments.callee);
+			doc.head = doc.html = doc.window = null;
+		});
+		return $extend(doc, Document.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		document[property] = Document.Prototype[property] = value;
+	}
+
+});
+
+Document.Prototype = {$family: {name: 'document'}};
+
+new Document(document);
+/*
+---
+
+script: Array.js
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Array.each
+
+provides: [Array]
+
+...
+*/
+
+Array.implement({
+
+	every: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (!fn.call(bind, this[i], i, this)) return false;
+		}
+		return true;
+	},
+
+	filter: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
+		}
+		return results;
+	},
+
+	clean: function(){
+		return this.filter($defined);
+	},
+
+	indexOf: function(item, from){
+		var len = this.length;
+		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+			if (this[i] === item) return i;
+		}
+		return -1;
+	},
+
+	map: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
+		return results;
+	},
+
+	some: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) return true;
+		}
+		return false;
+	},
+
+	associate: function(keys){
+		var obj = {}, length = Math.min(this.length, keys.length);
+		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+		return obj;
+	},
+
+	link: function(object){
+		var result = {};
+		for (var i = 0, l = this.length; i < l; i++){
+			for (var key in object){
+				if (object[key](this[i])){
+					result[key] = this[i];
+					delete object[key];
+					break;
+				}
+			}
+		}
+		return result;
+	},
+
+	contains: function(item, from){
+		return this.indexOf(item, from) != -1;
+	},
+
+	extend: function(array){
+		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
+		return this;
+	},
+	
+	getLast: function(){
+		return (this.length) ? this[this.length - 1] : null;
+	},
+
+	getRandom: function(){
+		return (this.length) ? this[$random(0, this.length - 1)] : null;
+	},
+
+	include: function(item){
+		if (!this.contains(item)) this.push(item);
+		return this;
+	},
+
+	combine: function(array){
+		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+		return this;
+	},
+
+	erase: function(item){
+		for (var i = this.length; i--; i){
+			if (this[i] === item) this.splice(i, 1);
+		}
+		return this;
+	},
+
+	empty: function(){
+		this.length = 0;
+		return this;
+	},
+
+	flatten: function(){
+		var array = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			var type = $type(this[i]);
+			if (!type) continue;
+			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
+		}
+		return array;
+	},
+
+	hexToRgb: function(array){
+		if (this.length != 3) return null;
+		var rgb = this.map(function(value){
+			if (value.length == 1) value += value;
+			return value.toInt(16);
+		});
+		return (array) ? rgb : 'rgb(' + rgb + ')';
+	},
+
+	rgbToHex: function(array){
+		if (this.length < 3) return null;
+		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+		var hex = [];
+		for (var i = 0; i < 3; i++){
+			var bit = (this[i] - 0).toString(16);
+			hex.push((bit.length == 1) ? '0' + bit : bit);
+		}
+		return (array) ? hex : '#' + hex.join('');
+	}
+
+});
+/*
+---
+
+script: Function.js
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Function]
+
+...
+*/
+
+Function.implement({
+
+	extend: function(properties){
+		for (var property in properties) this[property] = properties[property];
+		return this;
+	},
+
+	create: function(options){
+		var self = this;
+		options = options || {};
+		return function(event){
+			var args = options.arguments;
+			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+			if (options.event) args = [event || window.event].extend(args);
+			var returns = function(){
+				return self.apply(options.bind || null, args);
+			};
+			if (options.delay) return setTimeout(returns, options.delay);
+			if (options.periodical) return setInterval(returns, options.periodical);
+			if (options.attempt) return $try(returns);
+			return returns();
+		};
+	},
+
+	run: function(args, bind){
+		return this.apply(bind, $splat(args));
+	},
+
+	pass: function(args, bind){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bind: function(bind, args){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bindWithEvent: function(bind, args){
+		return this.create({bind: bind, arguments: args, event: true});
+	},
+
+	attempt: function(args, bind){
+		return this.create({bind: bind, arguments: args, attempt: true})();
+	},
+
+	delay: function(delay, bind, args){
+		return this.create({bind: bind, arguments: args, delay: delay})();
+	},
+
+	periodical: function(periodical, bind, args){
+		return this.create({bind: bind, arguments: args, periodical: periodical})();
+	}
+
+});
+/*
+---
+
+script: Number.js
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Number]
+
+...
+*/
+
+Number.implement({
+
+	limit: function(min, max){
+		return Math.min(max, Math.max(min, this));
+	},
+
+	round: function(precision){
+		precision = Math.pow(10, precision || 0);
+		return Math.round(this * precision) / precision;
+	},
+
+	times: function(fn, bind){
+		for (var i = 0; i < this; i++) fn.call(bind, i, this);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	}
+
+});
+
+Number.alias('times', 'each');
+
+(function(math){
+	var methods = {};
+	math.each(function(name){
+		if (!Number[name]) methods[name] = function(){
+			return Math[name].apply(null, [this].concat($A(arguments)));
+		};
+	});
+	Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+/*
+---
+
+script: String.js
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires:
+- /Native
+
+provides: [String]
+
+...
+*/
+
+String.implement({
+
+	test: function(regex, params){
+		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
+	},
+
+	contains: function(string, separator){
+		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+	},
+
+	trim: function(){
+		return this.replace(/^\s+|\s+$/g, '');
+	},
+
+	clean: function(){
+		return this.replace(/\s+/g, ' ').trim();
+	},
+
+	camelCase: function(){
+		return this.replace(/-\D/g, function(match){
+			return match.charAt(1).toUpperCase();
+		});
+	},
+
+	hyphenate: function(){
+		return this.replace(/[A-Z]/g, function(match){
+			return ('-' + match.charAt(0).toLowerCase());
+		});
+	},
+
+	capitalize: function(){
+		return this.replace(/\b[a-z]/g, function(match){
+			return match.toUpperCase();
+		});
+	},
+
+	escapeRegExp: function(){
+		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	hexToRgb: function(array){
+		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+		return (hex) ? hex.slice(1).hexToRgb(array) : null;
+	},
+
+	rgbToHex: function(array){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHex(array) : null;
+	},
+
+	stripScripts: function(option){
+		var scripts = '';
+		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
+			scripts += arguments[1] + '\n';
+			return '';
+		});
+		if (option === true) $exec(scripts);
+		else if ($type(option) == 'function') option(scripts, text);
+		return text;
+	},
+
+	substitute: function(object, regexp){
+		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+			if (match.charAt(0) == '\\') return match.slice(1);
+			return (object[name] != undefined) ? object[name] : '';
+		});
+	}
+
+});
+/*
+---
+
+script: Hash.js
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+- /Hash.base
+
+provides: [Hash]
+
+...
+*/
+
+Hash.implement({
+
+	has: Object.prototype.hasOwnProperty,
+
+	keyOf: function(value){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && this[key] === value) return key;
+		}
+		return null;
+	},
+
+	hasValue: function(value){
+		return (Hash.keyOf(this, value) !== null);
+	},
+
+	extend: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.set(this, key, value);
+		}, this);
+		return this;
+	},
+
+	combine: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.include(this, key, value);
+		}, this);
+		return this;
+	},
+
+	erase: function(key){
+		if (this.hasOwnProperty(key)) delete this[key];
+		return this;
+	},
+
+	get: function(key){
+		return (this.hasOwnProperty(key)) ? this[key] : null;
+	},
+
+	set: function(key, value){
+		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+		return this;
+	},
+
+	empty: function(){
+		Hash.each(this, function(value, key){
+			delete this[key];
+		}, this);
+		return this;
+	},
+
+	include: function(key, value){
+		if (this[key] == undefined) this[key] = value;
+		return this;
+	},
+
+	map: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			results.set(key, fn.call(bind, value, key, this));
+		}, this);
+		return results;
+	},
+
+	filter: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			if (fn.call(bind, value, key, this)) results.set(key, value);
+		}, this);
+		return results;
+	},
+
+	every: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
+		}
+		return true;
+	},
+
+	some: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
+		}
+		return false;
+	},
+
+	getKeys: function(){
+		var keys = [];
+		Hash.each(this, function(value, key){
+			keys.push(key);
+		});
+		return keys;
+	},
+
+	getValues: function(){
+		var values = [];
+		Hash.each(this, function(value){
+			values.push(value);
+		});
+		return values;
+	},
+
+	toQueryString: function(base){
+		var queryString = [];
+		Hash.each(this, function(value, key){
+			if (base) key = base + '[' + key + ']';
+			var result;
+			switch ($type(value)){
+				case 'object': result = Hash.toQueryString(value, key); break;
+				case 'array':
+					var qs = {};
+					value.each(function(val, i){
+						qs[i] = val;
+					});
+					result = Hash.toQueryString(qs, key);
+				break;
+				default: result = key + '=' + encodeURIComponent(value);
+			}
+			if (value != undefined) queryString.push(result);
+		});
+
+		return queryString.join('&');
+	}
+
+});
+
+Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
+/*
+---
+
+script: Event.js
+
+description: Contains the Event Class, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Hash
+- /Array
+- /Function
+- /String
+
+provides: [Event]
+
+...
+*/
+
+var Event = new Native({
+
+	name: 'Event',
+
+	initialize: function(event, win){
+		win = win || window;
+		var doc = win.document;
+		event = event || win.event;
+		if (event.$extended) return event;
+		this.$extended = true;
+		var type = event.type;
+		var target = event.target || event.srcElement;
+		while (target && target.nodeType == 3) target = target.parentNode;
+
+		if (type.test(/key/)){
+			var code = event.which || event.keyCode;
+			var key = Event.Keys.keyOf(code);
+			if (type == 'keydown'){
+				var fKey = code - 111;
+				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
+			}
+			key = key || String.fromCharCode(code).toLowerCase();
+		} else if (type.match(/(click|mouse|menu)/i)){
+			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+			var page = {
+				x: event.pageX || event.clientX + doc.scrollLeft,
+				y: event.pageY || event.clientY + doc.scrollTop
+			};
+			var client = {
+				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
+				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
+			};
+			if (type.match(/DOMMouseScroll|mousewheel/)){
+				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+			}
+			var rightClick = (event.which == 3) || (event.button == 2);
+			var related = null;
+			if (type.match(/over|out/)){
+				switch (type){
+					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
+					case 'mouseout': related = event.relatedTarget || event.toElement;
+				}
+				if (!(function(){
+					while (related && related.nodeType == 3) related = related.parentNode;
+					return true;
+				}).create({attempt: Browser.Engine.gecko})()) related = false;
+			}
+		}
+
+		return $extend(this, {
+			event: event,
+			type: type,
+
+			page: page,
+			client: client,
+			rightClick: rightClick,
+
+			wheel: wheel,
+
+			relatedTarget: related,
+			target: target,
+
+			code: code,
+			key: key,
+
+			shift: event.shiftKey,
+			control: event.ctrlKey,
+			alt: event.altKey,
+			meta: event.metaKey
+		});
+	}
+
+});
+
+Event.Keys = new Hash({
+	'enter': 13,
+	'up': 38,
+	'down': 40,
+	'left': 37,
+	'right': 39,
+	'esc': 27,
+	'space': 32,
+	'backspace': 8,
+	'tab': 9,
+	'delete': 46
+});
+
+Event.implement({
+
+	stop: function(){
+		return this.stopPropagation().preventDefault();
+	},
+
+	stopPropagation: function(){
+		if (this.event.stopPropagation) this.event.stopPropagation();
+		else this.event.cancelBubble = true;
+		return this;
+	},
+
+	preventDefault: function(){
+		if (this.event.preventDefault) this.event.preventDefault();
+		else this.event.returnValue = false;
+		return this;
+	}
+
+});
+/*
+---
+
+script: Class.js
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Native
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Class]
+
+...
+*/
+
+function Class(params){
+	
+	if (params instanceof Function) params = {initialize: params};
+	
+	var newClass = function(){
+		Object.reset(this);
+		if (newClass._prototyping) return this;
+		this._current = $empty;
+		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+		delete this._current; delete this.caller;
+		return value;
+	}.extend(this);
+	
+	newClass.implement(params);
+	
+	newClass.constructor = Class;
+	newClass.prototype.constructor = newClass;
+
+	return newClass;
+
+};
+
+Function.prototype.protect = function(){
+	this._protected = true;
+	return this;
+};
+
+Object.reset = function(object, key){
+		
+	if (key == null){
+		for (var p in object) Object.reset(object, p);
+		return object;
+	}
+	
+	delete object[key];
+	
+	switch ($type(object[key])){
+		case 'object':
+			var F = function(){};
+			F.prototype = object[key];
+			var i = new F;
+			object[key] = Object.reset(i);
+		break;
+		case 'array': object[key] = $unlink(object[key]); break;
+	}
+	
+	return object;
+	
+};
+
+new Native({name: 'Class', initialize: Class}).extend({
+
+	instantiate: function(F){
+		F._prototyping = true;
+		var proto = new F;
+		delete F._prototyping;
+		return proto;
+	},
+	
+	wrap: function(self, key, method){
+		if (method._origin) method = method._origin;
+		
+		return function(){
+			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
+			var caller = this.caller, current = this._current;
+			this.caller = current; this._current = arguments.callee;
+			var result = method.apply(this, arguments);
+			this._current = current; this.caller = caller;
+			return result;
+		}.extend({_owner: self, _origin: method, _name: key});
+
+	}
+	
+});
+
+Class.implement({
+	
+	implement: function(key, value){
+		
+		if ($type(key) == 'object'){
+			for (var p in key) this.implement(p, key[p]);
+			return this;
+		}
+		
+		var mutator = Class.Mutators[key];
+		
+		if (mutator){
+			value = mutator.call(this, value);
+			if (value == null) return this;
+		}
+		
+		var proto = this.prototype;
+
+		switch ($type(value)){
+			
+			case 'function':
+				if (value._hidden) return this;
+				proto[key] = Class.wrap(this, key, value);
+			break;
+			
+			case 'object':
+				var previous = proto[key];
+				if ($type(previous) == 'object') $mixin(previous, value);
+				else proto[key] = $unlink(value);
+			break;
+			
+			case 'array':
+				proto[key] = $unlink(value);
+			break;
+			
+			default: proto[key] = value;
+
+		}
+		
+		return this;
+
+	}
+	
+});
+
+Class.Mutators = {
+	
+	Extends: function(parent){
+
+		this.parent = parent;
+		this.prototype = Class.instantiate(parent);
+
+		this.implement('parent', function(){
+			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
+			if (!previous) throw new Error('The method "' + name + '" has no parent.');
+			return previous.apply(this, arguments);
+		}.protect());
+
+	},
+
+	Implements: function(items){
+		$splat(items).each(function(item){
+			if (item instanceof Function) item = Class.instantiate(item);
+			this.implement(item);
+		}, this);
+
+	}
+	
+};
+/*
+---
+
+script: Class.Extras.js
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires:
+- /Class
+
+provides: [Chain, Events, Options]
+
+...
+*/
+
+var Chain = new Class({
+
+	$chain: [],
+
+	chain: function(){
+		this.$chain.extend(Array.flatten(arguments));
+		return this;
+	},
+
+	callChain: function(){
+		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+	},
+
+	clearChain: function(){
+		this.$chain.empty();
+		return this;
+	}
+
+});
+
+var Events = new Class({
+
+	$events: {},
+
+	addEvent: function(type, fn, internal){
+		type = Events.removeOn(type);
+		if (fn != $empty){
+			this.$events[type] = this.$events[type] || [];
+			this.$events[type].include(fn);
+			if (internal) fn.internal = true;
+		}
+		return this;
+	},
+
+	addEvents: function(events){
+		for (var type in events) this.addEvent(type, events[type]);
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		type = Events.removeOn(type);
+		if (!this.$events || !this.$events[type]) return this;
+		this.$events[type].each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		type = Events.removeOn(type);
+		if (!this.$events[type]) return this;
+		if (!fn.internal) this.$events[type].erase(fn);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		if (events) events = Events.removeOn(events);
+		for (type in this.$events){
+			if (events && events != type) continue;
+			var fns = this.$events[type];
+			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
+		}
+		return this;
+	}
+
+});
+
+Events.removeOn = function(string){
+	return string.replace(/^on([A-Z])/, function(full, first){
+		return first.toLowerCase();
+	});
+};
+
+var Options = new Class({
+
+	setOptions: function(){
+		this.options = $merge.run([this.options].extend(arguments));
+		if (!this.addEvent) return this;
+		for (var option in this.options){
+			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+			this.addEvent(option, this.options[option]);
+			delete this.options[option];
+		}
+		return this;
+	}
+
+});
+/*
+---
+
+script: Element.js
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Element, Elements, $, $$, Iframe]
+
+...
+*/
+
+var Element = new Native({
+
+	name: 'Element',
+
+	legacy: window.Element,
+
+	initialize: function(tag, props){
+		var konstructor = Element.Constructors.get(tag);
+		if (konstructor) return konstructor(props);
+		if (typeof tag == 'string') return document.newElement(tag, props);
+		return document.id(tag).set(props);
+	},
+
+	afterImplement: function(key, value){
+		Element.Prototype[key] = value;
+		if (Array[key]) return;
+		Elements.implement(key, function(){
+			var items = [], elements = true;
+			for (var i = 0, j = this.length; i < j; i++){
+				var returns = this[i][key].apply(this[i], arguments);
+				items.push(returns);
+				if (elements) elements = ($type(returns) == 'element');
+			}
+			return (elements) ? new Elements(items) : items;
+		});
+	}
+
+});
+
+Element.Prototype = {$family: {name: 'element'}};
+
+Element.Constructors = new Hash;
+
+var IFrame = new Native({
+
+	name: 'IFrame',
+
+	generics: false,
+
+	initialize: function(){
+		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
+		var props = params.properties || {};
+		var iframe = document.id(params.iframe);
+		var onload = props.onload || $empty;
+		delete props.onload;
+		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
+		iframe = new Element(iframe || 'iframe', props);
+		var onFrameLoad = function(){
+			var host = $try(function(){
+				return iframe.contentWindow.location.host;
+			});
+			if (!host || host == window.location.host){
+				var win = new Window(iframe.contentWindow);
+				new Document(iframe.contentWindow.document);
+				$extend(win.Element.prototype, Element.Prototype);
+			}
+			onload.call(iframe.contentWindow, iframe.contentWindow.document);
+		};
+		var contentWindow = $try(function(){
+			return iframe.contentWindow;
+		});
+		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
+		return iframe;
+	}
+
+});
+
+var Elements = new Native({
+
+	initialize: function(elements, options){
+		options = $extend({ddup: true, cash: true}, options);
+		elements = elements || [];
+		if (options.ddup || options.cash){
+			var uniques = {}, returned = [];
+			for (var i = 0, l = elements.length; i < l; i++){
+				var el = document.id(elements[i], !options.cash);
+				if (options.ddup){
+					if (uniques[el.uid]) continue;
+					uniques[el.uid] = true;
+				}
+				if (el) returned.push(el);
+			}
+			elements = returned;
+		}
+		return (options.cash) ? $extend(elements, this) : elements;
+	}
+
+});
+
+Elements.implement({
+
+	filter: function(filter, bind){
+		if (!filter) return this;
+		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
+			return item.match(filter);
+		} : filter, bind));
+	}
+
+});
+
+Document.implement({
+
+	newElement: function(tag, props){
+		if (Browser.Engine.trident && props){
+			['name', 'type', 'checked'].each(function(attribute){
+				if (!props[attribute]) return;
+				tag += ' ' + attribute + '="' + props[attribute] + '"';
+				if (attribute != 'checked') delete props[attribute];
+			});
+			tag = '<' + tag + '>';
+		}
+		return document.id(this.createElement(tag)).set(props);
+	},
+
+	newTextNode: function(text){
+		return this.createTextNode(text);
+	},
+
+	getDocument: function(){
+		return this;
+	},
+
+	getWindow: function(){
+		return this.window;
+	},
+	
+	id: (function(){
+		
+		var types = {
+
+			string: function(id, nocash, doc){
+				id = doc.getElementById(id);
+				return (id) ? types.element(id, nocash) : null;
+			},
+			
+			element: function(el, nocash){
+				$uid(el);
+				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
+					var proto = Element.Prototype;
+					for (var p in proto) el[p] = proto[p];
+				};
+				return el;
+			},
+			
+			object: function(obj, nocash, doc){
+				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+				return null;
+			}
+			
+		};
+
+		types.textnode = types.whitespace = types.window = types.document = $arguments(0);
+		
+		return function(el, nocash, doc){
+			if (el && el.$family && el.uid) return el;
+			var type = $type(el);
+			return (types[type]) ? types[type](el, nocash, doc || document) : null;
+		};
+
+	})()
+
+});
+
+if (window.$ == null) Window.implement({
+	$: function(el, nc){
+		return document.id(el, nc, this.document);
+	}
+});
+
+Window.implement({
+
+	$$: function(selector){
+		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
+		var elements = [];
+		var args = Array.flatten(arguments);
+		for (var i = 0, l = args.length; i < l; i++){
+			var item = args[i];
+			switch ($type(item)){
+				case 'element': elements.push(item); break;
+				case 'string': elements.extend(this.document.getElements(item, true));
+			}
+		}
+		return new Elements(elements);
+	},
+
+	getDocument: function(){
+		return this.document;
+	},
+
+	getWindow: function(){
+		return this;
+	}
+
+});
+
+Native.implement([Element, Document], {
+
+	getElement: function(selector, nocash){
+		return document.id(this.getElements(selector, true)[0] || null, nocash);
+	},
+
+	getElements: function(tags, nocash){
+		tags = tags.split(',');
+		var elements = [];
+		var ddup = (tags.length > 1);
+		tags.each(function(tag){
+			var partial = this.getElementsByTagName(tag.trim());
+			(ddup) ? elements.extend(partial) : elements = partial;
+		}, this);
+		return new Elements(elements, {ddup: ddup, cash: !nocash});
+	}
+
+});
+
+(function(){
+
+var collected = {}, storage = {};
+var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
+
+var get = function(uid){
+	return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item, retain){
+	if (!item) return;
+	var uid = item.uid;
+	if (retain !== true) retain = false;
+	if (Browser.Engine.trident){
+		if (item.clearAttributes){
+			var clone = retain && item.cloneNode(false);
+			item.clearAttributes();
+			if (clone) item.mergeAttributes(clone);
+		} else if (item.removeEvents){
+			item.removeEvents();
+		}
+		if ((/object/i).test(item.tagName)){
+			for (var p in item){
+				if (typeof item[p] == 'function') item[p] = $empty;
+			}
+			Element.dispose(item);
+		}
+	}	
+	if (!uid) return;
+	collected[uid] = storage[uid] = null;
+};
+
+var purge = function(){
+	Hash.each(collected, clean);
+	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
+	if (window.CollectGarbage) CollectGarbage();
+	collected = storage = null;
+};
+
+var walk = function(element, walk, start, match, all, nocash){
+	var el = element[start || walk];
+	var elements = [];
+	while (el){
+		if (el.nodeType == 1 && (!match || Element.match(el, match))){
+			if (!all) return document.id(el, nocash);
+			elements.push(el);
+		}
+		el = el[walk];
+	}
+	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
+};
+
+var attributes = {
+	'html': 'innerHTML',
+	'class': 'className',
+	'for': 'htmlFor',
+	'defaultValue': 'defaultValue',
+	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
+};
+var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
+var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
+
+bools = bools.associate(bools);
+
+Hash.extend(attributes, bools);
+Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
+
+var inserters = {
+
+	before: function(context, element){
+		if (element.parentNode) element.parentNode.insertBefore(context, element);
+	},
+
+	after: function(context, element){
+		if (!element.parentNode) return;
+		var next = element.nextSibling;
+		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
+	},
+
+	bottom: function(context, element){
+		element.appendChild(context);
+	},
+
+	top: function(context, element){
+		var first = element.firstChild;
+		(first) ? element.insertBefore(context, first) : element.appendChild(context);
+	}
+
+};
+
+inserters.inside = inserters.bottom;
+
+Hash.each(inserters, function(inserter, where){
+
+	where = where.capitalize();
+
+	Element.implement('inject' + where, function(el){
+		inserter(this, document.id(el, true));
+		return this;
+	});
+
+	Element.implement('grab' + where, function(el){
+		inserter(document.id(el, true), this);
+		return this;
+	});
+
+});
+
+Element.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				var property = Element.Properties.get(prop);
+				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		var property = Element.Properties.get(prop);
+		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
+	},
+
+	erase: function(prop){
+		var property = Element.Properties.get(prop);
+		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+		return this;
+	},
+
+	setProperty: function(attribute, value){
+		var key = attributes[attribute];
+		if (value == undefined) return this.removeProperty(attribute);
+		if (key && bools[attribute]) value = !!value;
+		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
+		return this;
+	},
+
+	setProperties: function(attributes){
+		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+		return this;
+	},
+
+	getProperty: function(attribute){
+		var key = attributes[attribute];
+		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
+		return (bools[attribute]) ? !!value : (key) ? value : value || null;
+	},
+
+	getProperties: function(){
+		var args = $A(arguments);
+		return args.map(this.getProperty, this).associate(args);
+	},
+
+	removeProperty: function(attribute){
+		var key = attributes[attribute];
+		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
+		return this;
+	},
+
+	removeProperties: function(){
+		Array.each(arguments, this.removeProperty, this);
+		return this;
+	},
+
+	hasClass: function(className){
+		return this.className.contains(className, ' ');
+	},
+
+	addClass: function(className){
+		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+		return this;
+	},
+
+	removeClass: function(className){
+		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+		return this;
+	},
+
+	toggleClass: function(className){
+		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+	},
+
+	adopt: function(){
+		Array.flatten(arguments).each(function(element){
+			element = document.id(element, true);
+			if (element) this.appendChild(element);
+		}, this);
+		return this;
+	},
+
+	appendText: function(text, where){
+		return this.grab(this.getDocument().newTextNode(text), where);
+	},
+
+	grab: function(el, where){
+		inserters[where || 'bottom'](document.id(el, true), this);
+		return this;
+	},
+
+	inject: function(el, where){
+		inserters[where || 'bottom'](this, document.id(el, true));
+		return this;
+	},
+
+	replaces: function(el){
+		el = document.id(el, true);
+		el.parentNode.replaceChild(this, el);
+		return this;
+	},
+
+	wraps: function(el, where){
+		el = document.id(el, true);
+		return this.replaces(el).grab(el, where);
+	},
+
+	getPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, false, nocash);
+	},
+
+	getAllPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, true, nocash);
+	},
+
+	getNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, false, nocash);
+	},
+
+	getAllNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, true, nocash);
+	},
+
+	getFirst: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
+	},
+
+	getLast: function(match, nocash){
+		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
+	},
+
+	getParent: function(match, nocash){
+		return walk(this, 'parentNode', null, match, false, nocash);
+	},
+
+	getParents: function(match, nocash){
+		return walk(this, 'parentNode', null, match, true, nocash);
+	},
+	
+	getSiblings: function(match, nocash){
+		return this.getParent().getChildren(match, nocash).erase(this);
+	},
+
+	getChildren: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
+	},
+
+	getWindow: function(){
+		return this.ownerDocument.window;
+	},
+
+	getDocument: function(){
+		return this.ownerDocument;
+	},
+
+	getElementById: function(id, nocash){
+		var el = this.ownerDocument.getElementById(id);
+		if (!el) return null;
+		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
+			if (!parent) return null;
+		}
+		return document.id(el, nocash);
+	},
+
+	getSelected: function(){
+		return new Elements($A(this.options).filter(function(option){
+			return option.selected;
+		}));
+	},
+
+	getComputedStyle: function(property){
+		if (this.currentStyle) return this.currentStyle[property.camelCase()];
+		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
+		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
+	},
+
+	toQueryString: function(){
+		var queryString = [];
+		this.getElements('input, select, textarea', true).each(function(el){
+			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
+			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
+				return opt.value;
+			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
+			$splat(value).each(function(val){
+				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
+			});
+		});
+		return queryString.join('&');
+	},
+
+	clone: function(contents, keepid){
+		contents = contents !== false;
+		var clone = this.cloneNode(contents);
+		var clean = function(node, element){
+			if (!keepid) node.removeAttribute('id');
+			if (Browser.Engine.trident){
+				node.clearAttributes();
+				node.mergeAttributes(element);
+				node.removeAttribute('uid');
+				if (node.options){
+					var no = node.options, eo = element.options;
+					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+				}
+			}
+			var prop = props[element.tagName.toLowerCase()];
+			if (prop && element[prop]) node[prop] = element[prop];
+		};
+
+		if (contents){
+			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
+			for (var i = ce.length; i--;) clean(ce[i], te[i]);
+		}
+
+		clean(clone, this);
+		return document.id(clone);
+	},
+
+	destroy: function(){
+		Element.empty(this);
+		Element.dispose(this);
+		clean(this, true);
+		return null;
+	},
+
+	empty: function(){
+		$A(this.childNodes).each(function(node){
+			Element.destroy(node);
+		});
+		return this;
+	},
+
+	dispose: function(){
+		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+	},
+
+	hasChild: function(el){
+		el = document.id(el, true);
+		if (!el) return false;
+		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
+		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
+	},
+
+	match: function(tag){
+		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
+	}
+
+});
+
+Native.implement([Element, Window, Document], {
+
+	addListener: function(type, fn){
+		if (type == 'unload'){
+			var old = fn, self = this;
+			fn = function(){
+				self.removeListener('unload', fn);
+				old();
+			};
+		} else {
+			collected[this.uid] = this;
+		}
+		if (this.addEventListener) this.addEventListener(type, fn, false);
+		else this.attachEvent('on' + type, fn);
+		return this;
+	},
+
+	removeListener: function(type, fn){
+		if (this.removeEventListener) this.removeEventListener(type, fn, false);
+		else this.detachEvent('on' + type, fn);
+		return this;
+	},
+
+	retrieve: function(property, dflt){
+		var storage = get(this.uid), prop = storage[property];
+		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
+		return $pick(prop);
+	},
+
+	store: function(property, value){
+		var storage = get(this.uid);
+		storage[property] = value;
+		return this;
+	},
+
+	eliminate: function(property){
+		var storage = get(this.uid);
+		delete storage[property];
+		return this;
+	}
+
+});
+
+window.addListener('unload', purge);
+
+})();
+
+Element.Properties = new Hash;
+
+Element.Properties.style = {
+
+	set: function(style){
+		this.style.cssText = style;
+	},
+
+	get: function(){
+		return this.style.cssText;
+	},
+
+	erase: function(){
+		this.style.cssText = '';
+	}
+
+};
+
+Element.Properties.tag = {
+
+	get: function(){
+		return this.tagName.toLowerCase();
+	}
+
+};
+
+Element.Properties.html = (function(){
+	var wrapper = document.createElement('div');
+
+	var translations = {
+		table: [1, '<table>', '</table>'],
+		select: [1, '<select>', '</select>'],
+		tbody: [2, '<table><tbody>', '</tbody></table>'],
+		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+	};
+	translations.thead = translations.tfoot = translations.tbody;
+
+	var html = {
+		set: function(){
+			var html = Array.flatten(arguments).join('');
+			var wrap = Browser.Engine.trident && translations[this.get('tag')];
+			if (wrap){
+				var first = wrapper;
+				first.innerHTML = wrap[1] + html + wrap[2];
+				for (var i = wrap[0]; i--;) first = first.firstChild;
+				this.empty().adopt(first.childNodes);
+			} else {
+				this.innerHTML = html;
+			}
+		}
+	};
+
+	html.erase = html.set;
+
+	return html;
+})();
+
+if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
+	get: function(){
+		if (this.innerText) return this.innerText;
+		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
+		var text = temp.innerText;
+		temp.destroy();
+		return text;
+	}
+};
+/*
+---
+
+script: Element.Event.js
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: 
+- /Element
+- /Event
+
+provides: [Element.Event]
+
+...
+*/
+
+Element.Properties.events = {set: function(events){
+	this.addEvents(events);
+}};
+
+Native.implement([Element, Window, Document], {
+
+	addEvent: function(type, fn){
+		var events = this.retrieve('events', {});
+		events[type] = events[type] || {'keys': [], 'values': []};
+		if (events[type].keys.contains(fn)) return this;
+		events[type].keys.push(fn);
+		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
+		if (custom){
+			if (custom.onAdd) custom.onAdd.call(this, fn);
+			if (custom.condition){
+				condition = function(event){
+					if (custom.condition.call(this, event)) return fn.call(this, event);
+					return true;
+				};
+			}
+			realType = custom.base || realType;
+		}
+		var defn = function(){
+			return fn.call(self);
+		};
+		var nativeEvent = Element.NativeEvents[realType];
+		if (nativeEvent){
+			if (nativeEvent == 2){
+				defn = function(event){
+					event = new Event(event, self.getWindow());
+					if (condition.call(self, event) === false) event.stop();
+				};
+			}
+			this.addListener(realType, defn);
+		}
+		events[type].values.push(defn);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		var pos = events[type].keys.indexOf(fn);
+		if (pos == -1) return this;
+		events[type].keys.splice(pos, 1);
+		var value = events[type].values.splice(pos, 1)[0];
+		var custom = Element.Events.get(type);
+		if (custom){
+			if (custom.onRemove) custom.onRemove.call(this, fn);
+			type = custom.base || type;
+		}
+		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
+	},
+
+	addEvents: function(events){
+		for (var event in events) this.addEvent(event, events[event]);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		var attached = this.retrieve('events');
+		if (!attached) return this;
+		if (!events){
+			for (type in attached) this.removeEvents(type);
+			this.eliminate('events');
+		} else if (attached[events]){
+			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
+			attached[events] = null;
+		}
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		events[type].keys.each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	cloneEvents: function(from, type){
+		from = document.id(from);
+		var fevents = from.retrieve('events');
+		if (!fevents) return this;
+		if (!type){
+			for (var evType in fevents) this.cloneEvents(from, evType);
+		} else if (fevents[type]){
+			fevents[type].keys.each(function(fn){
+				this.addEvent(type, fn);
+			}, this);
+		}
+		return this;
+	}
+
+});
+
+Element.NativeEvents = {
+	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+	keydown: 2, keypress: 2, keyup: 2, //keyboard
+	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
+	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+	error: 1, abort: 1, scroll: 1 //misc
+};
+
+(function(){
+
+var $check = function(event){
+	var related = event.relatedTarget;
+	if (related == undefined) return true;
+	if (related === false) return false;
+	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
+};
+
+Element.Events = new Hash({
+
+	mouseenter: {
+		base: 'mouseover',
+		condition: $check
+	},
+
+	mouseleave: {
+		base: 'mouseout',
+		condition: $check
+	},
+
+	mousewheel: {
+		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
+	}
+
+});
+
+})();
+/*
+---
+
+script: Element.Style.js
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Element.Style]
+
+...
+*/
+
+Element.Properties.styles = {set: function(styles){
+	this.setStyles(styles);
+}};
+
+Element.Properties.opacity = {
+
+	set: function(opacity, novisibility){
+		if (!novisibility){
+			if (opacity == 0){
+				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
+			} else {
+				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
+			}
+		}
+		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
+		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
+		this.style.opacity = opacity;
+		this.store('opacity', opacity);
+	},
+
+	get: function(){
+		return this.retrieve('opacity', 1);
+	}
+
+};
+
+Element.implement({
+
+	setOpacity: function(value){
+		return this.set('opacity', value, true);
+	},
+
+	getOpacity: function(){
+		return this.get('opacity');
+	},
+
+	setStyle: function(property, value){
+		switch (property){
+			case 'opacity': return this.set('opacity', parseFloat(value));
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		if ($type(value) != 'string'){
+			var map = (Element.Styles.get(property) || '@').split(' ');
+			value = $splat(value).map(function(val, i){
+				if (!map[i]) return '';
+				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+			}).join(' ');
+		} else if (value == String(Number(value))){
+			value = Math.round(value);
+		}
+		this.style[property] = value;
+		return this;
+	},
+
+	getStyle: function(property){
+		switch (property){
+			case 'opacity': return this.get('opacity');
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		var result = this.style[property];
+		if (!$chk(result)){
+			result = [];
+			for (var style in Element.ShortStyles){
+				if (property != style) continue;
+				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+				return result.join(' ');
+			}
+			result = this.getComputedStyle(property);
+		}
+		if (result){
+			result = String(result);
+			var color = result.match(/rgba?\([\d\s,]+\)/);
+			if (color) result = result.replace(color[0], color[0].rgbToHex());
+		}
+		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
+			if (property.test(/^(height|width)$/)){
+				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+				values.each(function(value){
+					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+				}, this);
+				return this['offset' + property.capitalize()] - size + 'px';
+			}
+			if ((Browser.Engine.presto) && String(result).test('px')) return result;
+			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
+		}
+		return result;
+	},
+
+	setStyles: function(styles){
+		for (var style in styles) this.setStyle(style, styles[style]);
+		return this;
+	},
+
+	getStyles: function(){
+		var result = {};
+		Array.flatten(arguments).each(function(key){
+			result[key] = this.getStyle(key);
+		}, this);
+		return result;
+	}
+
+});
+
+Element.Styles = new Hash({
+	left: '@px', top: '@px', bottom: '@px', right: '@px',
+	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+});
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+	var Short = Element.ShortStyles;
+	var All = Element.Styles;
+	['margin', 'padding'].each(function(style){
+		var sd = style + direction;
+		Short[style][sd] = All[sd] = '@px';
+	});
+	var bd = 'border' + direction;
+	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+	Short[bd] = {};
+	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+/*
+---
+
+script: Element.Dimensions.js
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires:
+- /Element
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+Element.implement({
+
+	scrollTo: function(x, y){
+		if (isBody(this)){
+			this.getWindow().scrollTo(x, y);
+		} else {
+			this.scrollLeft = x;
+			this.scrollTop = y;
+		}
+		return this;
+	},
+
+	getSize: function(){
+		if (isBody(this)) return this.getWindow().getSize();
+		return {x: this.offsetWidth, y: this.offsetHeight};
+	},
+
+	getScrollSize: function(){
+		if (isBody(this)) return this.getWindow().getScrollSize();
+		return {x: this.scrollWidth, y: this.scrollHeight};
+	},
+
+	getScroll: function(){
+		if (isBody(this)) return this.getWindow().getScroll();
+		return {x: this.scrollLeft, y: this.scrollTop};
+	},
+
+	getScrolls: function(){
+		var element = this, position = {x: 0, y: 0};
+		while (element && !isBody(element)){
+			position.x += element.scrollLeft;
+			position.y += element.scrollTop;
+			element = element.parentNode;
+		}
+		return position;
+	},
+
+	getOffsetParent: function(){
+		var element = this;
+		if (isBody(element)) return null;
+		if (!Browser.Engine.trident) return element.offsetParent;
+		while ((element = element.parentNode) && !isBody(element)){
+			if (styleString(element, 'position') != 'static') return element;
+		}
+		return null;
+	},
+
+	getOffsets: function(){
+		if (this.getBoundingClientRect){
+			var bound = this.getBoundingClientRect(),
+				html = document.id(this.getDocument().documentElement),
+				htmlScroll = html.getScroll(),
+				elemScrolls = this.getScrolls(),
+				elemScroll = this.getScroll(),
+				isFixed = (styleString(this, 'position') == 'fixed');
+
+			return {
+				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+			};
+		}
+
+		var element = this, position = {x: 0, y: 0};
+		if (isBody(this)) return position;
+
+		while (element && !isBody(element)){
+			position.x += element.offsetLeft;
+			position.y += element.offsetTop;
+
+			if (Browser.Engine.gecko){
+				if (!borderBox(element)){
+					position.x += leftBorder(element);
+					position.y += topBorder(element);
+				}
+				var parent = element.parentNode;
+				if (parent && styleString(parent, 'overflow') != 'visible'){
+					position.x += leftBorder(parent);
+					position.y += topBorder(parent);
+				}
+			} else if (element != this && Browser.Engine.webkit){
+				position.x += leftBorder(element);
+				position.y += topBorder(element);
+			}
+
+			element = element.offsetParent;
+		}
+		if (Browser.Engine.gecko && !borderBox(this)){
+			position.x -= leftBorder(this);
+			position.y -= topBorder(this);
+		}
+		return position;
+	},
+
+	getPosition: function(relative){
+		if (isBody(this)) return {x: 0, y: 0};
+		var offset = this.getOffsets(),
+				scroll = this.getScrolls();
+		var position = {
+			x: offset.x - scroll.x,
+			y: offset.y - scroll.y
+		};
+		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
+		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
+	},
+
+	getCoordinates: function(element){
+		if (isBody(this)) return this.getWindow().getCoordinates();
+		var position = this.getPosition(element),
+				size = this.getSize();
+		var obj = {
+			left: position.x,
+			top: position.y,
+			width: size.x,
+			height: size.y
+		};
+		obj.right = obj.left + obj.width;
+		obj.bottom = obj.top + obj.height;
+		return obj;
+	},
+
+	computePosition: function(obj){
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
+	},
+
+	setPosition: function(obj){
+		return this.setStyles(this.computePosition(obj));
+	}
+
+});
+
+
+Native.implement([Document, Window], {
+
+	getSize: function(){
+		if (Browser.Engine.presto || Browser.Engine.webkit){
+			var win = this.getWindow();
+			return {x: win.innerWidth, y: win.innerHeight};
+		}
+		var doc = getCompatElement(this);
+		return {x: doc.clientWidth, y: doc.clientHeight};
+	},
+
+	getScroll: function(){
+		var win = this.getWindow(), doc = getCompatElement(this);
+		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+	},
+
+	getScrollSize: function(){
+		var doc = getCompatElement(this), min = this.getSize();
+		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
+	},
+
+	getPosition: function(){
+		return {x: 0, y: 0};
+	},
+
+	getCoordinates: function(){
+		var size = this.getSize();
+		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+	}
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+	return styleString(element, style).toInt() || 0;
+};
+
+function borderBox(element){
+	return styleString(element, '-moz-box-sizing') == 'border-box';
+};
+
+function topBorder(element){
+	return styleNumber(element, 'border-top-width');
+};
+
+function leftBorder(element){
+	return styleNumber(element, 'border-left-width');
+};
+
+function isBody(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+};
+
+function getCompatElement(element){
+	var doc = element.getDocument();
+	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+};
+
+})();
+
+//aliases
+Element.alias('setPosition', 'position'); //compatability
+
+Native.implement([Window, Document, Element], {
+
+	getHeight: function(){
+		return this.getSize().y;
+	},
+
+	getWidth: function(){
+		return this.getSize().x;
+	},
+
+	getScrollTop: function(){
+		return this.getScroll().y;
+	},
+
+	getScrollLeft: function(){
+		return this.getScroll().x;
+	},
+
+	getScrollHeight: function(){
+		return this.getScrollSize().y;
+	},
+
+	getScrollWidth: function(){
+		return this.getScrollSize().x;
+	},
+
+	getTop: function(){
+		return this.getPosition().y;
+	},
+
+	getLeft: function(){
+		return this.getPosition().x;
+	}
+
+});
+/*
+---
+
+script: Selectors.js
+
+description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Selectors]
+
+...
+*/
+
+Native.implement([Document, Element], {
+
+	getElements: function(expression, nocash){
+		expression = expression.split(',');
+		var items, local = {};
+		for (var i = 0, l = expression.length; i < l; i++){
+			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
+			if (i != 0 && elements.item) elements = $A(elements);
+			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
+		}
+		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
+	}
+
+});
+
+Element.implement({
+
+	match: function(selector){
+		if (!selector || (selector == this)) return true;
+		var tagid = Selectors.Utils.parseTagAndID(selector);
+		var tag = tagid[0], id = tagid[1];
+		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
+		var parsed = Selectors.Utils.parseSelector(selector);
+		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
+	}
+
+});
+
+var Selectors = {Cache: {nth: {}, parsed: {}}};
+
+Selectors.RegExps = {
+	id: (/#([\w-]+)/),
+	tag: (/^(\w+|\*)/),
+	quick: (/^(\w+|\*)$/),
+	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
+	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
+};
+
+Selectors.Utils = {
+
+	chk: function(item, uniques){
+		if (!uniques) return true;
+		var uid = $uid(item);
+		if (!uniques[uid]) return uniques[uid] = true;
+		return false;
+	},
+
+	parseNthArgument: function(argument){
+		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
+		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
+		if (!parsed) return false;
+		var inta = parseInt(parsed[1], 10);
+		var a = (inta || inta === 0) ? inta : 1;
+		var special = parsed[2] || false;
+		var b = parseInt(parsed[3], 10) || 0;
+		if (a != 0){
+			b--;
+			while (b < 1) b += a;
+			while (b >= a) b -= a;
+		} else {
+			a = b;
+			special = 'index';
+		}
+		switch (special){
+			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
+			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
+			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
+			case 'first': parsed = {a: 0, special: 'index'}; break;
+			case 'last': parsed = {special: 'last-child'}; break;
+			case 'only': parsed = {special: 'only-child'}; break;
+			default: parsed = {a: (a - 1), special: 'index'};
+		}
+
+		return Selectors.Cache.nth[argument] = parsed;
+	},
+
+	parseSelector: function(selector){
+		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
+		var m, parsed = {classes: [], pseudos: [], attributes: []};
+		while ((m = Selectors.RegExps.combined.exec(selector))){
+			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
+			if (cn){
+				parsed.classes.push(cn);
+			} else if (pn){
+				var parser = Selectors.Pseudo.get(pn);
+				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
+				else parsed.attributes.push({name: pn, operator: '=', value: pa});
+			} else if (an){
+				parsed.attributes.push({name: an, operator: ao, value: av});
+			}
+		}
+		if (!parsed.classes.length) delete parsed.classes;
+		if (!parsed.attributes.length) delete parsed.attributes;
+		if (!parsed.pseudos.length) delete parsed.pseudos;
+		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
+		return Selectors.Cache.parsed[selector] = parsed;
+	},
+
+	parseTagAndID: function(selector){
+		var tag = selector.match(Selectors.RegExps.tag);
+		var id = selector.match(Selectors.RegExps.id);
+		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
+	},
+
+	filter: function(item, parsed, local){
+		var i;
+		if (parsed.classes){
+			for (i = parsed.classes.length; i--; i){
+				var cn = parsed.classes[i];
+				if (!Selectors.Filters.byClass(item, cn)) return false;
+			}
+		}
+		if (parsed.attributes){
+			for (i = parsed.attributes.length; i--; i){
+				var att = parsed.attributes[i];
+				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
+			}
+		}
+		if (parsed.pseudos){
+			for (i = parsed.pseudos.length; i--; i){
+				var psd = parsed.pseudos[i];
+				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
+			}
+		}
+		return true;
+	},
+
+	getByTagAndID: function(ctx, tag, id){
+		if (id){
+			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
+			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
+		} else {
+			return ctx.getElementsByTagName(tag);
+		}
+	},
+
+	search: function(self, expression, local){
+		var splitters = [];
+
+		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
+			splitters.push(m1);
+			return ':)' + m2;
+		}).split(':)');
+
+		var items, filtered, item;
+
+		for (var i = 0, l = selectors.length; i < l; i++){
+
+			var selector = selectors[i];
+
+			if (i == 0 && Selectors.RegExps.quick.test(selector)){
+				items = self.getElementsByTagName(selector);
+				continue;
+			}
+
+			var splitter = splitters[i - 1];
+
+			var tagid = Selectors.Utils.parseTagAndID(selector);
+			var tag = tagid[0], id = tagid[1];
+
+			if (i == 0){
+				items = Selectors.Utils.getByTagAndID(self, tag, id);
+			} else {
+				var uniques = {}, found = [];
+				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
+				items = found;
+			}
+
+			var parsed = Selectors.Utils.parseSelector(selector);
+
+			if (parsed){
+				filtered = [];
+				for (var m = 0, n = items.length; m < n; m++){
+					item = items[m];
+					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
+				}
+				items = filtered;
+			}
+
+		}
+
+		return items;
+
+	}
+
+};
+
+Selectors.Getters = {
+
+	' ': function(found, self, tag, id, uniques){
+		var items = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = items.length; i < l; i++){
+			var item = items[i];
+			if (Selectors.Utils.chk(item, uniques)) found.push(item);
+		}
+		return found;
+	},
+
+	'>': function(found, self, tag, id, uniques){
+		var children = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = children.length; i < l; i++){
+			var child = children[i];
+			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
+		}
+		return found;
+	},
+
+	'+': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+				break;
+			}
+		}
+		return found;
+	},
+
+	'~': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (!Selectors.Utils.chk(self, uniques)) break;
+				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+			}
+		}
+		return found;
+	}
+
+};
+
+Selectors.Filters = {
+
+	byTag: function(self, tag){
+		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
+	},
+
+	byID: function(self, id){
+		return (!id || (self.id && self.id == id));
+	},
+
+	byClass: function(self, klass){
+		return (self.className && self.className.contains && self.className.contains(klass, ' '));
+	},
+
+	byPseudo: function(self, parser, argument, local){
+		return parser.call(self, argument, local);
+	},
+
+	byAttribute: function(self, name, operator, value){
+		var result = Element.prototype.getProperty.call(self, name);
+		if (!result) return (operator == '!=');
+		if (!operator || value == undefined) return true;
+		switch (operator){
+			case '=': return (result == value);
+			case '*=': return (result.contains(value));
+			case '^=': return (result.substr(0, value.length) == value);
+			case '$=': return (result.substr(result.length - value.length) == value);
+			case '!=': return (result != value);
+			case '~=': return result.contains(value, ' ');
+			case '|=': return result.contains(value, '-');
+		}
+		return false;
+	}
+
+};
+
+Selectors.Pseudo = new Hash({
+
+	// w3c pseudo selectors
+
+	checked: function(){
+		return this.checked;
+	},
+	
+	empty: function(){
+		return !(this.innerText || this.textContent || '').length;
+	},
+
+	not: function(selector){
+		return !Element.match(this, selector);
+	},
+
+	contains: function(text){
+		return (this.innerText || this.textContent || '').contains(text);
+	},
+
+	'first-child': function(){
+		return Selectors.Pseudo.index.call(this, 0);
+	},
+
+	'last-child': function(){
+		var element = this;
+		while ((element = element.nextSibling)){
+			if (element.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'only-child': function(){
+		var prev = this;
+		while ((prev = prev.previousSibling)){
+			if (prev.nodeType == 1) return false;
+		}
+		var next = this;
+		while ((next = next.nextSibling)){
+			if (next.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'nth-child': function(argument, local){
+		argument = (argument == undefined) ? 'n' : argument;
+		var parsed = Selectors.Utils.parseNthArgument(argument);
+		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
+		var count = 0;
+		local.positions = local.positions || {};
+		var uid = $uid(this);
+		if (!local.positions[uid]){
+			var self = this;
+			while ((self = self.previousSibling)){
+				if (self.nodeType != 1) continue;
+				count ++;
+				var position = local.positions[$uid(self)];
+				if (position != undefined){
+					count = position + count;
+					break;
+				}
+			}
+			local.positions[uid] = count;
+		}
+		return (local.positions[uid] % parsed.a == parsed.b);
+	},
+
+	// custom pseudo selectors
+
+	index: function(index){
+		var element = this, count = 0;
+		while ((element = element.previousSibling)){
+			if (element.nodeType == 1 && ++count > index) return false;
+		}
+		return (count == index);
+	},
+
+	even: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
+	},
+
+	odd: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
+	},
+	
+	selected: function(){
+		return this.selected;
+	},
+	
+	enabled: function(){
+		return (this.disabled === false);
+	}
+
+});
+/*
+---
+
+script: DomReady.js
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires:
+- /Element.Event
+
+provides: [DomReady]
+
+...
+*/
+
+Element.Events.domready = {
+
+	onAdd: function(fn){
+		if (Browser.loaded) fn.call(this);
+	}
+
+};
+
+(function(){
+
+	var domready = function(){
+		if (Browser.loaded) return;
+		Browser.loaded = true;
+		window.fireEvent('domready');
+		document.fireEvent('domready');
+	};
+	
+	window.addEvent('load', domready);
+
+	if (Browser.Engine.trident){
+		var temp = document.createElement('div');
+		(function(){
+			($try(function(){
+				temp.doScroll(); // Technique by Diego Perini
+				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
+			})) ? domready() : arguments.callee.delay(50);
+		})();
+	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
+		(function(){
+			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
+		})();
+	} else {
+		document.addEvent('DOMContentLoaded', domready);
+	}
+
+})();
+/*
+---
+
+script: JSON.js
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+See Also: <http://www.json.org/>
+
+requires:
+- /Array
+- /String
+- /Number
+- /Function
+- /Hash
+
+provides: [JSON]
+
+...
+*/
+
+var JSON = new Hash(this.JSON && {
+	stringify: JSON.stringify,
+	parse: JSON.parse
+}).extend({
+	
+	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},
+
+	$replaceChars: function(chr){
+		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
+	},
+
+	encode: function(obj){
+		switch ($type(obj)){
+			case 'string':
+				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
+			case 'array':
+				return '[' + String(obj.map(JSON.encode).clean()) + ']';
+			case 'object': case 'hash':
+				var string = [];
+				Hash.each(obj, function(value, key){
+					var json = JSON.encode(value);
+					if (json) string.push(JSON.encode(key) + ':' + json);
+				});
+				return '{' + string + '}';
+			case 'number': case 'boolean': return String(obj);
+			case false: return 'null';
+		}
+		return null;
+	},
+
+	decode: function(string, secure){
+		if ($type(string) != 'string' || !string.length) return null;
+		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
+		return eval('(' + string + ')');
+	}
+
+});
+
+Native.implement([Hash, Array, String, Number], {
+
+	toJSON: function(){
+		return JSON.encode(this);
+	}
+
+});
+/*
+---
+
+script: Cookie.js
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+- Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires:
+- /Options
+
+provides: [Cookie]
+
+...
+*/
+
+var Cookie = new Class({
+
+	Implements: Options,
+
+	options: {
+		path: false,
+		domain: false,
+		duration: false,
+		secure: false,
+		document: document
+	},
+
+	initialize: function(key, options){
+		this.key = key;
+		this.setOptions(options);
+	},
+
+	write: function(value){
+		value = encodeURIComponent(value);
+		if (this.options.domain) value += '; domain=' + this.options.domain;
+		if (this.options.path) value += '; path=' + this.options.path;
+		if (this.options.duration){
+			var date = new Date();
+			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+			value += '; expires=' + date.toGMTString();
+		}
+		if (this.options.secure) value += '; secure';
+		this.options.document.cookie = this.key + '=' + value;
+		return this;
+	},
+
+	read: function(){
+		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+		return (value) ? decodeURIComponent(value[1]) : null;
+	},
+
+	dispose: function(){
+		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
+		return this;
+	}
+
+});
+
+Cookie.write = function(key, value, options){
+	return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+	return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+	return new Cookie(key, options).dispose();
+};
+/*
+---
+
+script: Swiff.js
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits: 
+- Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires:
+- /Options
+- /$util
+
+provides: [Swiff]
+
+...
+*/
+
+var Swiff = new Class({
+
+	Implements: [Options],
+
+	options: {
+		id: null,
+		height: 1,
+		width: 1,
+		container: null,
+		properties: {},
+		params: {
+			quality: 'high',
+			allowScriptAccess: 'always',
+			wMode: 'transparent',
+			swLiveConnect: true
+		},
+		callBacks: {},
+		vars: {}
+	},
+
+	toElement: function(){
+		return this.object;
+	},
+
+	initialize: function(path, options){
+		this.instance = 'Swiff_' + $time();
+
+		this.setOptions(options);
+		options = this.options;
+		var id = this.id = options.id || this.instance;
+		var container = document.id(options.container);
+
+		Swiff.CallBacks[this.instance] = {};
+
+		var params = options.params, vars = options.vars, callBacks = options.callBacks;
+		var properties = $extend({height: options.height, width: options.width}, options.properties);
+
+		var self = this;
+
+		for (var callBack in callBacks){
+			Swiff.CallBacks[this.instance][callBack] = (function(option){
+				return function(){
+					return option.apply(self.object, arguments);
+				};
+			})(callBacks[callBack]);
+			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+		}
+
+		params.flashVars = Hash.toQueryString(vars);
+		if (Browser.Engine.trident){
+			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			params.movie = path;
+		} else {
+			properties.type = 'application/x-shockwave-flash';
+			properties.data = path;
+		}
+		var build = '<object id="' + id + '"';
+		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+		build += '>';
+		for (var param in params){
+			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+		}
+		build += '</object>';
+		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+	},
+
+	replaces: function(element){
+		element = document.id(element, true);
+		element.parentNode.replaceChild(this.toElement(), element);
+		return this;
+	},
+
+	inject: function(element){
+		document.id(element, true).appendChild(this.toElement());
+		return this;
+	},
+
+	remote: function(){
+		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
+	}
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+	return eval(rs);
+};
+/*
+---
+
+script: Fx.js
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires:
+- /Chain
+- /Events
+- /Options
+
+provides: [Fx]
+
+...
+*/
+
+var Fx = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {
+		/*
+		onStart: $empty,
+		onCancel: $empty,
+		onComplete: $empty,
+		*/
+		fps: 50,
+		unit: false,
+		duration: 500,
+		link: 'ignore'
+	},
+
+	initialize: function(options){
+		this.subject = this.subject || this;
+		this.setOptions(options);
+		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
+		var wait = this.options.wait;
+		if (wait === false) this.options.link = 'cancel';
+	},
+
+	getTransition: function(){
+		return function(p){
+			return -(Math.cos(Math.PI * p) - 1) / 2;
+		};
+	},
+
+	step: function(){
+		var time = $time();
+		if (time < this.time + this.options.duration){
+			var delta = this.transition((time - this.time) / this.options.duration);
+			this.set(this.compute(this.from, this.to, delta));
+		} else {
+			this.set(this.compute(this.from, this.to, 1));
+			this.complete();
+		}
+	},
+
+	set: function(now){
+		return now;
+	},
+
+	compute: function(from, to, delta){
+		return Fx.compute(from, to, delta);
+	},
+
+	check: function(){
+		if (!this.timer) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	start: function(from, to){
+		if (!this.check(from, to)) return this;
+		this.from = from;
+		this.to = to;
+		this.time = 0;
+		this.transition = this.getTransition();
+		this.startTimer();
+		this.onStart();
+		return this;
+	},
+
+	complete: function(){
+		if (this.stopTimer()) this.onComplete();
+		return this;
+	},
+
+	cancel: function(){
+		if (this.stopTimer()) this.onCancel();
+		return this;
+	},
+
+	onStart: function(){
+		this.fireEvent('start', this.subject);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', this.subject);
+		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+	},
+
+	onCancel: function(){
+		this.fireEvent('cancel', this.subject).clearChain();
+	},
+
+	pause: function(){
+		this.stopTimer();
+		return this;
+	},
+
+	resume: function(){
+		this.startTimer();
+		return this;
+	},
+
+	stopTimer: function(){
+		if (!this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = $clear(this.timer);
+		return true;
+	},
+
+	startTimer: function(){
+		if (this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
+		return true;
+	}
+
+});
+
+Fx.compute = function(from, to, delta){
+	return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+/*
+---
+
+script: Fx.CSS.js
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires:
+- /Fx
+- /Element.Style
+
+provides: [Fx.CSS]
+
+...
+*/
+
+Fx.CSS = new Class({
+
+	Extends: Fx,
+
+	//prepares the base from/to object
+
+	prepare: function(element, property, values){
+		values = $splat(values);
+		var values1 = values[1];
+		if (!$chk(values1)){
+			values[1] = values[0];
+			values[0] = element.getStyle(property);
+		}
+		var parsed = values.map(this.parse);
+		return {from: parsed[0], to: parsed[1]};
+	},
+
+	//parses a value into an array
+
+	parse: function(value){
+		value = $lambda(value)();
+		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
+		return value.map(function(val){
+			val = String(val);
+			var found = false;
+			Fx.CSS.Parsers.each(function(parser, key){
+				if (found) return;
+				var parsed = parser.parse(val);
+				if ($chk(parsed)) found = {value: parsed, parser: parser};
+			});
+			found = found || {value: val, parser: Fx.CSS.Parsers.String};
+			return found;
+		});
+	},
+
+	//computes by a from and to prepared objects, using their parsers.
+
+	compute: function(from, to, delta){
+		var computed = [];
+		(Math.min(from.length, to.length)).times(function(i){
+			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+		});
+		computed.$family = {name: 'fx:css:value'};
+		return computed;
+	},
+
+	//serves the value as settable
+
+	serve: function(value, unit){
+		if ($type(value) != 'fx:css:value') value = this.parse(value);
+		var returned = [];
+		value.each(function(bit){
+			returned = returned.concat(bit.parser.serve(bit.value, unit));
+		});
+		return returned;
+	},
+
+	//renders the change to an element
+
+	render: function(element, property, value, unit){
+		element.setStyle(property, this.serve(value, unit));
+	},
+
+	//searches inside the page css to find the values for a selector
+
+	search: function(selector){
+		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+		var to = {};
+		Array.each(document.styleSheets, function(sheet, j){
+			var href = sheet.href;
+			if (href && href.contains('://') && !href.contains(document.domain)) return;
+			var rules = sheet.rules || sheet.cssRules;
+			Array.each(rules, function(rule, i){
+				if (!rule.style) return;
+				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+					return m.toLowerCase();
+				}) : null;
+				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
+				Element.Styles.each(function(value, style){
+					if (!rule.style[style] || Element.ShortStyles[style]) return;
+					value = String(rule.style[style]);
+					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
+				});
+			});
+		});
+		return Fx.CSS.Cache[selector] = to;
+	}
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = new Hash({
+
+	Color: {
+		parse: function(value){
+			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+		},
+		compute: function(from, to, delta){
+			return from.map(function(value, i){
+				return Math.round(Fx.compute(from[i], to[i], delta));
+			});
+		},
+		serve: function(value){
+			return value.map(Number);
+		}
+	},
+
+	Number: {
+		parse: parseFloat,
+		compute: Fx.compute,
+		serve: function(value, unit){
+			return (unit) ? value + unit : value;
+		}
+	},
+
+	String: {
+		parse: $lambda(false),
+		compute: $arguments(1),
+		serve: $arguments(0)
+	}
+
+});
+/*
+---
+
+script: Fx.Tween.js
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: 
+- /Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(property, now){
+		if (arguments.length == 1){
+			now = property;
+			property = this.property || this.options.property;
+		}
+		this.render(this.element, property, now, this.options.unit);
+		return this;
+	},
+
+	start: function(property, from, to){
+		if (!this.check(property, from, to)) return this;
+		var args = Array.flatten(arguments);
+		this.property = this.options.property || args.shift();
+		var parsed = this.prepare(this.element, this.property, args);
+		return this.parent(parsed.from, parsed.to);
+	}
+
+});
+
+Element.Properties.tween = {
+
+	set: function(options){
+		var tween = this.retrieve('tween');
+		if (tween) tween.cancel();
+		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('tween')){
+			if (options || !this.retrieve('tween:options')) this.set('tween', options);
+			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
+		}
+		return this.retrieve('tween');
+	}
+
+};
+
+Element.implement({
+
+	tween: function(property, from, to){
+		this.get('tween').start(arguments);
+		return this;
+	},
+
+	fade: function(how){
+		var fade = this.get('tween'), o = 'opacity', toggle;
+		how = $pick(how, 'toggle');
+		switch (how){
+			case 'in': fade.start(o, 1); break;
+			case 'out': fade.start(o, 0); break;
+			case 'show': fade.set(o, 1); break;
+			case 'hide': fade.set(o, 0); break;
+			case 'toggle':
+				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
+				fade.start(o, (flag) ? 0 : 1);
+				this.store('fade:flag', !flag);
+				toggle = true;
+			break;
+			default: fade.start(o, arguments);
+		}
+		if (!toggle) this.eliminate('fade:flag');
+		return this;
+	},
+
+	highlight: function(start, end){
+		if (!end){
+			end = this.retrieve('highlight:original', this.getStyle('background-color'));
+			end = (end == 'transparent') ? '#fff' : end;
+		}
+		var tween = this.get('tween');
+		tween.start('background-color', start || '#ffff88', end).chain(function(){
+			this.setStyle('background-color', this.retrieve('highlight:original'));
+			tween.callChain();
+		}.bind(this));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Morph.js
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires:
+- /Fx.CSS
+
+provides: [Fx.Morph]
+
+...
+*/
+
+Fx.Morph = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(now){
+		if (typeof now == 'string') now = this.search(now);
+		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+		return now;
+	},
+
+	start: function(properties){
+		if (!this.check(properties)) return this;
+		if (typeof properties == 'string') properties = this.search(properties);
+		var from = {}, to = {};
+		for (var p in properties){
+			var parsed = this.prepare(this.element, p, properties[p]);
+			from[p] = parsed.from;
+			to[p] = parsed.to;
+		}
+		return this.parent(from, to);
+	}
+
+});
+
+Element.Properties.morph = {
+
+	set: function(options){
+		var morph = this.retrieve('morph');
+		if (morph) morph.cancel();
+		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('morph')){
+			if (options || !this.retrieve('morph:options')) this.set('morph', options);
+			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
+		}
+		return this.retrieve('morph');
+	}
+
+};
+
+Element.implement({
+
+	morph: function(props){
+		this.get('morph').start(props);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Transitions.js
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires:
+- /Fx
+
+provides: [Fx.Transitions]
+
+...
+*/
+
+Fx.implement({
+
+	getTransition: function(){
+		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+		if (typeof trans == 'string'){
+			var data = trans.split(':');
+			trans = Fx.Transitions;
+			trans = trans[data[0]] || trans[data[0].capitalize()];
+			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+		}
+		return trans;
+	}
+
+});
+
+Fx.Transition = function(transition, params){
+	params = $splat(params);
+	return $extend(transition, {
+		easeIn: function(pos){
+			return transition(pos, params);
+		},
+		easeOut: function(pos){
+			return 1 - transition(1 - pos, params);
+		},
+		easeInOut: function(pos){
+			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
+		}
+	});
+};
+
+Fx.Transitions = new Hash({
+
+	linear: $arguments(0)
+
+});
+
+Fx.Transitions.extend = function(transitions){
+	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+	Pow: function(p, x){
+		return Math.pow(p, x[0] || 6);
+	},
+
+	Expo: function(p){
+		return Math.pow(2, 8 * (p - 1));
+	},
+
+	Circ: function(p){
+		return 1 - Math.sin(Math.acos(p));
+	},
+
+	Sine: function(p){
+		return 1 - Math.sin((1 - p) * Math.PI / 2);
+	},
+
+	Back: function(p, x){
+		x = x[0] || 1.618;
+		return Math.pow(p, 2) * ((x + 1) * p - x);
+	},
+
+	Bounce: function(p){
+		var value;
+		for (var a = 0, b = 1; 1; a += b, b /= 2){
+			if (p >= (7 - 4 * a) / 11){
+				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+				break;
+			}
+		}
+		return value;
+	},
+
+	Elastic: function(p, x){
+		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+	}
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+	Fx.Transitions[transition] = new Fx.Transition(function(p){
+		return Math.pow(p, [i + 2]);
+	});
+});
+/*
+---
+
+script: Request.js
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires:
+- /Element
+- /Chain
+- /Events
+- /Options
+- /Browser
+
+provides: [Request]
+
+...
+*/
+
+var Request = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {/*
+		onRequest: $empty,
+		onComplete: $empty,
+		onCancel: $empty,
+		onSuccess: $empty,
+		onFailure: $empty,
+		onException: $empty,*/
+		url: '',
+		data: '',
+		headers: {
+			'X-Requested-With': 'XMLHttpRequest',
+			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+		},
+		async: true,
+		format: false,
+		method: 'post',
+		link: 'ignore',
+		isSuccess: null,
+		emulation: true,
+		urlEncoded: true,
+		encoding: 'utf-8',
+		evalScripts: false,
+		evalResponse: false,
+		noCache: false
+	},
+
+	initialize: function(options){
+		this.xhr = new Browser.Request();
+		this.setOptions(options);
+		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+		this.headers = new Hash(this.options.headers);
+	},
+
+	onStateChange: function(){
+		if (this.xhr.readyState != 4 || !this.running) return;
+		this.running = false;
+		this.status = 0;
+		$try(function(){
+			this.status = this.xhr.status;
+		}.bind(this));
+		this.xhr.onreadystatechange = $empty;
+		if (this.options.isSuccess.call(this, this.status)){
+			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
+			this.success(this.response.text, this.response.xml);
+		} else {
+			this.response = {text: null, xml: null};
+			this.failure();
+		}
+	},
+
+	isSuccess: function(){
+		return ((this.status >= 200) && (this.status < 300));
+	},
+
+	processScripts: function(text){
+		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
+		return text.stripScripts(this.options.evalScripts);
+	},
+
+	success: function(text, xml){
+		this.onSuccess(this.processScripts(text), xml);
+	},
+
+	onSuccess: function(){
+		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+	},
+
+	failure: function(){
+		this.onFailure();
+	},
+
+	onFailure: function(){
+		this.fireEvent('complete').fireEvent('failure', this.xhr);
+	},
+
+	setHeader: function(name, value){
+		this.headers.set(name, value);
+		return this;
+	},
+
+	getHeader: function(name){
+		return $try(function(){
+			return this.xhr.getResponseHeader(name);
+		}.bind(this));
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!this.check(options)) return this;
+		this.running = true;
+
+		var type = $type(options);
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		var old = this.options;
+		options = $extend({data: old.data, url: old.url, method: old.method}, options);
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+		switch ($type(data)){
+			case 'element': data = document.id(data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(data);
+		}
+
+		if (this.options.format){
+			var format = 'format=' + this.options.format;
+			data = (data) ? format + '&' + data : format;
+		}
+
+		if (this.options.emulation && !['get', 'post'].contains(method)){
+			var _method = '_method=' + method;
+			data = (data) ? _method + '&' + data : _method;
+			method = 'post';
+		}
+
+		if (this.options.urlEncoded && method == 'post'){
+			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
+		}
+
+		if (this.options.noCache){
+			var noCache = 'noCache=' + new Date().getTime();
+			data = (data) ? noCache + '&' + data : noCache;
+		}
+
+		var trimPosition = url.lastIndexOf('/');
+		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+		if (data && method == 'get'){
+			url = url + (url.contains('?') ? '&' : '?') + data;
+			data = null;
+		}
+
+		this.xhr.open(method.toUpperCase(), url, this.options.async);
+
+		this.xhr.onreadystatechange = this.onStateChange.bind(this);
+
+		this.headers.each(function(value, key){
+			try {
+				this.xhr.setRequestHeader(key, value);
+			} catch (e){
+				this.fireEvent('exception', [key, value]);
+			}
+		}, this);
+
+		this.fireEvent('request');
+		this.xhr.send(data);
+		if (!this.options.async) this.onStateChange();
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.xhr.abort();
+		this.xhr.onreadystatechange = $empty;
+		this.xhr = new Browser.Request();
+		this.fireEvent('cancel');
+		return this;
+	}
+
+});
+
+(function(){
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+	methods[method] = function(){
+		var params = Array.link(arguments, {url: String.type, data: $defined});
+		return this.send($extend(params, {method: method}));
+	};
+});
+
+Request.implement(methods);
+
+})();
+
+Element.Properties.send = {
+
+	set: function(options){
+		var send = this.retrieve('send');
+		if (send) send.cancel();
+		return this.eliminate('send').store('send:options', $extend({
+			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+		}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('send')){
+			if (options || !this.retrieve('send:options')) this.set('send', options);
+			this.store('send', new Request(this.retrieve('send:options')));
+		}
+		return this.retrieve('send');
+	}
+
+};
+
+Element.implement({
+
+	send: function(url){
+		var sender = this.get('send');
+		sender.send({data: this, url: url || sender.options.url});
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.HTML.js
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires:
+- /Request
+- /Element
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.HTML = new Class({
+
+	Extends: Request,
+
+	options: {
+		update: false,
+		append: false,
+		evalScripts: true,
+		filter: false
+	},
+
+	processHTML: function(text){
+		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+		text = (match) ? match[1] : text;
+
+		var container = new Element('div');
+
+		return $try(function(){
+			var root = '<root>' + text + '</root>', doc;
+			if (Browser.Engine.trident){
+				doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = false;
+				doc.loadXML(root);
+			} else {
+				doc = new DOMParser().parseFromString(root, 'text/xml');
+			}
+			root = doc.getElementsByTagName('root')[0];
+			if (!root) return null;
+			for (var i = 0, k = root.childNodes.length; i < k; i++){
+				var child = Element.clone(root.childNodes[i], true, true);
+				if (child) container.grab(child);
+			}
+			return container;
+		}) || container.set('html', text);
+	},
+
+	success: function(text){
+		var options = this.options, response = this.response;
+
+		response.html = text.stripScripts(function(script){
+			response.javascript = script;
+		});
+
+		var temp = this.processHTML(response.html);
+
+		response.tree = temp.childNodes;
+		response.elements = temp.getElements('*');
+
+		if (options.filter) response.tree = response.elements.filter(options.filter);
+		if (options.update) document.id(options.update).empty().set('html', response.html);
+		else if (options.append) document.id(options.append).adopt(temp.getChildren());
+		if (options.evalScripts) $exec(response.javascript);
+
+		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+	}
+
+});
+
+Element.Properties.load = {
+
+	set: function(options){
+		var load = this.retrieve('load');
+		if (load) load.cancel();
+		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
+	},
+
+	get: function(options){
+		if (options || ! this.retrieve('load')){
+			if (options || !this.retrieve('load:options')) this.set('load', options);
+			this.store('load', new Request.HTML(this.retrieve('load:options')));
+		}
+		return this.retrieve('load');
+	}
+
+};
+
+Element.implement({
+
+	load: function(){
+		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.JSON.js
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires:
+- /Request JSON
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.JSON = new Class({
+
+	Extends: Request,
+
+	options: {
+		secure: true
+	},
+
+	initialize: function(options){
+		this.parent(options);
+		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
+	},
+
+	success: function(text){
+		this.response.json = JSON.decode(text, this.options.secure);
+		this.onSuccess(this.response.json, text);
+	}
+
+});
+/*
+---
+
+script: More.js
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+- Guillermo Rauch
+- Thomas Aylott
+- Scott Kyle
+
+requires:
+- core:1.2.4/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+	'version': '1.2.4.4',
+	'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
+};/*
+---
+
+script: MooTools.Lang.js
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Events
+- /MooTools.More
+
+provides: [MooTools.Lang]
+
+...
+*/
+
+(function(){
+
+	var data = {
+		language: 'en-US',
+		languages: {
+			'en-US': {}
+		},
+		cascades: ['en-US']
+	};
+	
+	var cascaded;
+
+	MooTools.lang = new Events();
+
+	$extend(MooTools.lang, {
+
+		setLanguage: function(lang){
+			if (!data.languages[lang]) return this;
+			data.language = lang;
+			this.load();
+			this.fireEvent('langChange', lang);
+			return this;
+		},
+
+		load: function() {
+			var langs = this.cascade(this.getCurrentLanguage());
+			cascaded = {};
+			$each(langs, function(set, setName){
+				cascaded[setName] = this.lambda(set);
+			}, this);
+		},
+
+		getCurrentLanguage: function(){
+			return data.language;
+		},
+
+		addLanguage: function(lang){
+			data.languages[lang] = data.languages[lang] || {};
+			return this;
+		},
+
+		cascade: function(lang){
+			var cascades = (data.languages[lang] || {}).cascades || [];
+			cascades.combine(data.cascades);
+			cascades.erase(lang).push(lang);
+			var langs = cascades.map(function(lng){
+				return data.languages[lng];
+			}, this);
+			return $merge.apply(this, langs);
+		},
+
+		lambda: function(set) {
+			(set || {}).get = function(key, args){
+				return $lambda(set[key]).apply(this, $splat(args));
+			};
+			return set;
+		},
+
+		get: function(set, key, args){
+			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
+		},
+
+		set: function(lang, set, members){
+			this.addLanguage(lang);
+			langData = data.languages[lang];
+			if (!langData[set]) langData[set] = {};
+			$extend(langData[set], members);
+			if (lang == this.getCurrentLanguage()){
+				this.load();
+				this.fireEvent('langChange', lang);
+			}
+			return this;
+		},
+
+		list: function(){
+			return Hash.getKeys(data.languages);
+		}
+
+	});
+
+})();/*
+---
+
+script: Log.js
+
+description: Provides basic logging functionality for plugins to implement.
+
+license: MIT-style license
+
+authors:
+- Guillermo Rauch
+- Thomas Aylott
+- Scott Kyle
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Log]
+
+...
+*/
+
+(function(){
+
+var global = this;
+
+var log = function(){
+	if (global.console && console.log){
+		try {
+			console.log.apply(console, arguments);
+		} catch(e) {
+			console.log(Array.slice(arguments));
+		}
+	} else {
+		Log.logged.push(arguments);
+	}
+	return this;
+};
+
+var disabled = function(){
+	this.logged.push(arguments);
+	return this;
+};
+
+this.Log = new Class({
+	
+	logged: [],
+	
+	log: disabled,
+	
+	resetLog: function(){
+		this.logged.empty();
+		return this;
+	},
+
+	enableLog: function(){
+		this.log = log;
+		this.logged.each(function(args){
+			this.log.apply(this, args);
+		}, this);
+		return this.resetLog();
+	},
+
+	disableLog: function(){
+		this.log = disabled;
+		return this;
+	}
+	
+});
+
+Log.extend(new Log).enableLog();
+
+// legacy
+Log.logger = function(){
+	return this.log.apply(this, arguments);
+};
+
+})();/*
+---
+
+script: Depender.js
+
+description: A stand alone dependency loader for the MooTools library.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Events
+- core:1.2.4/Request.JSON
+- /MooTools.More
+- /Log
+
+provides: Depender
+
+...
+*/
+
+var Depender = {
+
+	options: {
+		/* 
+		onRequire: $empty(options),
+		onRequirementLoaded: $empty([scripts, options]),
+		onScriptLoaded: $empty({
+			script: script, 
+			totalLoaded: percentOfTotalLoaded, 
+			loaded: scriptsState
+		}),
+		serial: false,
+		target: null,
+		noCache: false,
+		log: false,*/
+		loadedSources: [],
+		loadedScripts: ['Core', 'Browser', 'Array', 'String', 'Function', 'Number', 'Hash', 'Element', 'Event', 'Element.Event', 'Class', 'DomReady', 'Class.Extras', 'Request', 'JSON', 'Request.JSON', 'More', 'Depender', 'Log'],
+		useScriptInjection: true
+	},
+
+	loaded: [],
+
+	sources: {},
+
+	libs: {},
+
+	include: function(libs){
+		this.log('include: ', libs);
+		this.mapLoaded = false;
+		var loader = function(data){
+			this.libs = $merge(this.libs, data);
+			$each(this.libs, function(data, lib){
+				if (data.scripts) this.loadSource(lib, data.scripts);
+			}, this);
+		}.bind(this);
+		if ($type(libs) == 'string'){
+			this.log('fetching libs ', libs);
+			this.request(libs, loader);
+		} else {
+			loader(libs);
+		}
+		return this;
+	},
+
+	required: [],
+
+	require: function(options){
+		var loaded = function(){
+			var scripts = this.calculateDependencies(options.scripts);
+			if (options.sources){
+				options.sources.each(function(source){
+					scripts.combine(this.libs[source].files);
+				}, this);
+			}
+			if (options.serial) scripts.combine(this.getLoadedScripts());
+			options.scripts = scripts;
+			this.required.push(options);
+			this.fireEvent('require', options);
+			this.loadScripts(options.scripts);
+		};
+		if (this.mapLoaded) loaded.call(this);
+		else this.addEvent('mapLoaded', loaded.bind(this));
+		return this;
+	},
+
+	cleanDoubleSlash: function(str){
+		if (!str) return str;
+		var prefix = '';
+		if (str.test(/^http:\/\//)){
+			prefix = 'http://';
+			str = str.substring(7, str.length);
+		}
+		str = str.replace(/\/\//g, '/');
+		return prefix + str;
+	},
+
+	request: function(url, callback){
+		new Request.JSON({
+			url: url,
+			secure: false,
+			onSuccess: callback
+		}).send();
+	},
+
+	loadSource: function(lib, source){
+		if (this.libs[lib].files){
+			this.dataLoaded();
+			return;
+		}
+		this.log('loading source: ', source);
+		this.request(this.cleanDoubleSlash(source + '/scripts.json'), function(result){
+			this.log('loaded source: ', source);
+			this.libs[lib].files = result;
+			this.dataLoaded();
+		}.bind(this));
+	},
+
+	dataLoaded: function(){
+		var loaded = true;
+		$each(this.libs, function(v, k){
+			if (!this.libs[k].files) loaded = false;
+		}, this);
+		if (loaded){
+			this.mapTree();
+			this.mapLoaded = true;
+			this.calculateLoaded();
+			this.lastLoaded = this.getLoadedScripts().getLength();
+			this.fireEvent('mapLoaded');
+			this.removeEvents('mapLoaded');
+		}
+	},
+
+	calculateLoaded: function(){
+		var set = function(script){
+			this.scriptsState[script] = true;
+		}.bind(this);
+		if (this.options.loadedScripts) this.options.loadedScripts.each(set);
+		if (this.options.loadedSources){
+			this.options.loadedSources.each(function(lib){
+				$each(this.libs[lib].files, function(dir){
+					$each(dir, function(data, file){
+						set(file);
+					}, this);
+				}, this);
+			}, this);
+		}
+	},
+
+	deps: {},
+
+	pathMap: {},
+
+	mapTree: function(){
+		$each(this.libs, function(data, source){
+			$each(data.files, function(scripts, folder){
+				$each(scripts, function(details, script){
+					var path = source + ':' + folder + ':' + script;
+					if (this.deps[path]) return;
+					this.deps[path] = details.deps;
+					this.pathMap[script] = path;
+				}, this);
+			}, this);
+		}, this);
+	},
+
+	getDepsForScript: function(script){
+		return this.deps[this.pathMap[script]] || [];
+	},
+
+	calculateDependencies: function(scripts){
+		var reqs = [];
+		$splat(scripts).each(function(script){
+			if (script == 'None' || !script) return;
+			var deps = this.getDepsForScript(script);
+			if (!deps){
+				if (window.console && console.warn) console.warn('dependencies not mapped: script: %o, map: %o, :deps: %o', script, this.pathMap, this.deps);
+			} else {
+				deps.each(function(scr){
+					if (scr == script || scr == 'None' || !scr) return;
+					if (!reqs.contains(scr)) reqs.combine(this.calculateDependencies(scr));
+					reqs.include(scr);
+				}, this);
+			}
+			reqs.include(script);
+		}, this);
+		return reqs;
+	},
+
+	getPath: function(script){
+		try {
+			var chunks = this.pathMap[script].split(':');
+			var lib = this.libs[chunks[0]];
+			var dir = (lib.path || lib.scripts) + '/';
+			chunks.shift();
+			return this.cleanDoubleSlash(dir + chunks.join('/') + '.js');
+		} catch(e){
+			return script;
+		}
+	},
+
+	loadScripts: function(scripts){
+		scripts = scripts.filter(function(s){
+			if (!this.scriptsState[s] && s != 'None'){
+				this.scriptsState[s] = false;
+				return true;
+			}
+		}, this);
+		if (scripts.length){
+			scripts.each(function(scr){
+				this.loadScript(scr);
+			}, this);
+		} else {
+			this.check();
+		}
+	},
+
+	toLoad: [],
+
+	loadScript: function(script){
+		if (this.scriptsState[script] && this.toLoad.length){
+			this.loadScript(this.toLoad.shift());
+			return;
+		} else if (this.loading){
+			this.toLoad.push(script);
+			return;
+		}
+		var finish = function(){
+			this.loading = false;
+			this.scriptLoaded(script);
+			if (this.toLoad.length) this.loadScript(this.toLoad.shift());
+		}.bind(this);
+		var error = function(){
+			this.log('could not load: ', scriptPath);
+		}.bind(this);
+		this.loading = true;
+		var scriptPath = this.getPath(script);
+		if (this.options.useScriptInjection){
+			this.log('injecting script: ', scriptPath);
+			var loaded = function(){
+				this.log('loaded script: ', scriptPath);
+				finish();
+			}.bind(this);
+			new Element('script', {
+				src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
+				events: {
+					load: loaded,
+					readystatechange: function(){
+						if (['loaded', 'complete'].contains(this.readyState)) loaded();
+					},
+					error: error
+				}
+			}).inject(this.options.target || document.head);
+		} else {
+			this.log('requesting script: ', scriptPath);
+			new Request({
+				url: scriptPath,
+				noCache: this.options.noCache,
+				onComplete: function(js){
+					this.log('loaded script: ', scriptPath);
+					$exec(js);
+					finish();
+				}.bind(this),
+				onFailure: error,
+				onException: error
+			}).send();
+		}
+	},
+
+	scriptsState: $H(),
+	
+	getLoadedScripts: function(){
+		return this.scriptsState.filter(function(state){
+			return state;
+		});
+	},
+
+	scriptLoaded: function(script){
+		this.log('loaded script: ', script);
+		this.scriptsState[script] = true;
+		this.check();
+		var loaded = this.getLoadedScripts();
+		var loadedLength = loaded.getLength();
+		var toLoad = this.scriptsState.getLength();
+		this.fireEvent('scriptLoaded', {
+			script: script,
+			totalLoaded: (loadedLength / toLoad * 100).round(),
+			currentLoaded: ((loadedLength - this.lastLoaded) / (toLoad - this.lastLoaded) * 100).round(),
+			loaded: loaded
+		});
+		if (loadedLength == toLoad) this.lastLoaded = loadedLength;
+	},
+
+	lastLoaded: 0,
+
+	check: function(){
+		var incomplete = [];
+		this.required.each(function(required){
+			var loaded = [];
+			required.scripts.each(function(script){
+				if (this.scriptsState[script]) loaded.push(script);
+			}, this);
+			if (required.onStep){
+				required.onStep({
+					percent: loaded.length / required.scripts.length * 100,
+					scripts: loaded
+				});
+			};
+			if (required.scripts.length != loaded.length) return;
+			required.callback();
+			this.required.erase(required);
+			this.fireEvent('requirementLoaded', [loaded, required]);
+		}, this);
+	}
+
+};
+
+$extend(Depender, new Events);
+$extend(Depender, new Options);
+$extend(Depender, new Log);
+
+Depender._setOptions = Depender.setOptions;
+Depender.setOptions = function(){
+	Depender._setOptions.apply(Depender, arguments);
+	if (this.options.log) Depender.enableLog();
+	return this;
+};
+/*
+---
+
+script: Class.Refactor.js
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Class.refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+	$each(refactors, function(item, name){
+		var origin = original.prototype[name];
+		if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
+			var old = this.previous;
+			this.previous = origin;
+			var value = item.apply(this, arguments);
+			this.previous = old;
+			return value;
+		}); else original.implement(name, item);
+	});
+
+	return original;
+
+};/*
+---
+
+script: Class.Binds.js
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Class
+- /MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+    return binds;
+};
+
+Class.Mutators.initialize = function(initialize){
+	return function(){
+		$splat(this.Binds).each(function(name){
+			var original = this[name];
+			if (original) this[name] = original.bind(this);
+		}, this);
+		return initialize.apply(this, arguments);
+	};
+};
+/*
+---
+
+script: Class.Occlude.js
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+- Aaron Newton
+
+requires: 
+- core/1.2.4/Class
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+	occlude: function(property, element){
+		element = document.id(element || this.element);
+		var instance = element.retrieve(property || this.property);
+		if (instance && !$defined(this.occluded))
+			return this.occluded = instance;
+
+		this.occluded = false;
+		element.store(property || this.property, this);
+		return this.occluded;
+	}
+
+});/*
+---
+
+script: Chain.Wait.js
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+- Aaron Newton
+
+requires: 
+- core:1.2.4/Chain 
+- core:1.2.4/Element
+- core:1.2.4/Fx
+- /MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+	var wait = {
+		wait: function(duration){
+			return this.chain(function(){
+				this.callChain.delay($pick(duration, 500), this);
+			}.bind(this));
+		}
+	};
+
+	Chain.implement(wait);
+
+	if (window.Fx){
+		Fx.implement(wait);
+		['Css', 'Tween', 'Elements'].each(function(cls){
+			if (Fx[cls]) Fx[cls].implement(wait);
+		});
+	}
+
+	Element.implement({
+		chains: function(effects){
+			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
+				effect = this.get(effect);
+				if (!effect) return;
+				effect.setOptions({
+					link:'chain'
+				});
+			}, this);
+			return this;
+		},
+		pauseFx: function(duration, effect){
+			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
+			return this;
+		}
+	});
+
+})();/*
+---
+
+script: Array.Extras.js
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Array
+
+provides: [Array.Extras]
+
+...
+*/
+Array.implement({
+
+	min: function(){
+		return Math.min.apply(null, this);
+	},
+
+	max: function(){
+		return Math.max.apply(null, this);
+	},
+
+	average: function(){
+		return this.length ? this.sum() / this.length : 0;
+	},
+
+	sum: function(){
+		var result = 0, l = this.length;
+		if (l){
+			do {
+				result += this[--l];
+			} while (l);
+		}
+		return result;
+	},
+
+	unique: function(){
+		return [].combine(this);
+	},
+
+	shuffle: function(){
+		for (var i = this.length; i && --i;){
+			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+			this[i] = this[r];
+			this[r] = temp;
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Date.English.US.js
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.English.US]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Date', {
+
+	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['month', 'date', 'year'],
+	shortDate: '%m/%d/%Y',
+	shortTime: '%I:%M%p',
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1st, 2nd, 3rd, etc.
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'less than a minute ago',
+	minuteAgo: 'about a minute ago',
+	minutesAgo: '{delta} minutes ago',
+	hourAgo: 'about an hour ago',
+	hoursAgo: 'about {delta} hours ago',
+	dayAgo: '1 day ago',
+	daysAgo: '{delta} days ago',
+	weekAgo: '1 week ago',
+	weeksAgo: '{delta} weeks ago',
+	monthAgo: '1 month ago',
+	monthsAgo: '{delta} months ago',
+	yearAgo: '1 year ago',
+	yearsAgo: '{delta} years ago',
+	lessThanMinuteUntil: 'less than a minute from now',
+	minuteUntil: 'about a minute from now',
+	minutesUntil: '{delta} minutes from now',
+	hourUntil: 'about an hour from now',
+	hoursUntil: 'about {delta} hours from now',
+	dayUntil: '1 day from now',
+	daysUntil: '{delta} days from now',
+	weekUntil: '1 week from now',
+	weeksUntil: '{delta} weeks from now',
+	monthUntil: '1 month from now',
+	monthsUntil: '{delta} months from now',
+	yearUntil: '1 year from now',
+	yearsUntil: '{delta} years from now'
+
+});
+/*
+---
+
+script: Date.js
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+- Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+- Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- core:1.2.4/Number
+- core:1.2.4/Lang
+- core:1.2.4/Date.English.US
+- /MooTools.More
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+if (!Date.now) Date.now = $time;
+
+Date.Methods = {
+	ms: 'Milliseconds',
+	year: 'FullYear',
+	min: 'Minutes',
+	mo: 'Month',
+	sec: 'Seconds',
+	hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
+	Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(what, length){
+	return new Array(length - String(what).length + 1).join('0') + what;
+};
+
+Date.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				prop = prop.toLowerCase();
+				var m = Date.Methods;
+				if (m[prop]) this['set' + m[prop]](value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		prop = prop.toLowerCase();
+		var m = Date.Methods;
+		if (m[prop]) return this['get' + m[prop]]();
+		return null;
+	},
+
+	clone: function(){
+		return new Date(this.get('time'));
+	},
+
+	increment: function(interval, times){
+		interval = interval || 'day';
+		times = $pick(times, 1);
+
+		switch (interval){
+			case 'year':
+				return this.increment('month', times * 12);
+			case 'month':
+				var d = this.get('date');
+				this.set('date', 1).set('mo', this.get('mo') + times);
+				return this.set('date', d.min(this.get('lastdayofmonth')));
+			case 'week':
+				return this.increment('day', times * 7);
+			case 'day':
+				return this.set('date', this.get('date') + times);
+		}
+
+		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+		return this.set('time', this.get('time') + times * Date.units[interval]());
+	},
+
+	decrement: function(interval, times){
+		return this.increment(interval, -1 * $pick(times, 1));
+	},
+
+	isLeapYear: function(){
+		return Date.isLeapYear(this.get('year'));
+	},
+
+	clearTime: function(){
+		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+	},
+
+	diff: function(date, resolution){
+		if ($type(date) == 'string') date = Date.parse(date);
+		
+		return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
+	},
+
+	getLastDayOfMonth: function(){
+		return Date.daysInMonth(this.get('mo'), this.get('year'));
+	},
+
+	getDayOfYear: function(){
+		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) 
+			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+	},
+
+	getWeek: function(){
+		return (this.get('dayofyear') / 7).ceil();
+	},
+	
+	getOrdinal: function(day){
+		return Date.getMsg('ordinal', day || this.get('date'));
+	},
+
+	getTimezone: function(){
+		return this.toString()
+			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+	},
+
+	getGMTOffset: function(){
+		var off = this.get('timezoneOffset');
+		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+	},
+
+	setAMPM: function(ampm){
+		ampm = ampm.toUpperCase();
+		var hr = this.get('hr');
+		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+		return this;
+	},
+
+	getAMPM: function(){
+		return (this.get('hr') < 12) ? 'AM' : 'PM';
+	},
+
+	parse: function(str){
+		this.set('time', Date.parse(str));
+		return this;
+	},
+
+	isValid: function(date) {
+		return !!(date || this).valueOf();
+	},
+
+	format: function(f){
+		if (!this.isValid()) return 'invalid date';
+		f = f || '%x %X';
+		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
+		var d = this;
+		return f.replace(/%([a-z%])/gi,
+			function($0, $1){
+				switch ($1){
+					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
+					case 'A': return Date.getMsg('days')[d.get('day')];
+					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
+					case 'B': return Date.getMsg('months')[d.get('month')];
+					case 'c': return d.toString();
+					case 'd': return pad(d.get('date'), 2);
+					case 'H': return pad(d.get('hr'), 2);
+					case 'I': return ((d.get('hr') % 12) || 12);
+					case 'j': return pad(d.get('dayofyear'), 3);
+					case 'm': return pad((d.get('mo') + 1), 2);
+					case 'M': return pad(d.get('min'), 2);
+					case 'o': return d.get('ordinal');
+					case 'p': return Date.getMsg(d.get('ampm'));
+					case 'S': return pad(d.get('seconds'), 2);
+					case 'U': return pad(d.get('week'), 2);
+					case 'w': return d.get('day');
+					case 'x': return d.format(Date.getMsg('shortDate'));
+					case 'X': return d.format(Date.getMsg('shortTime'));
+					case 'y': return d.get('year').toString().substr(2);
+					case 'Y': return d.get('year');
+					case 'T': return d.get('GMTOffset');
+					case 'Z': return d.get('Timezone');
+				}
+				return $1;
+			}
+		);
+	},
+
+	toISOString: function(){
+		return this.format('iso8601');
+	}
+
+});
+
+Date.alias('toISOString', 'toJSON');
+Date.alias('diff', 'compare');
+Date.alias('format', 'strftime');
+
+var formats = {
+	db: '%Y-%m-%d %H:%M:%S',
+	compact: '%Y%m%dT%H%M%S',
+	iso8601: '%Y-%m-%dT%H:%M:%S%T',
+	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
+	'short': '%d %b %H:%M',
+	'long': '%B %d, %Y %H:%M'
+};
+
+var parsePatterns = [];
+var nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+	var ret = -1;
+	var translated = Date.getMsg(type + 's');
+
+	switch ($type(word)){
+		case 'object':
+			ret = translated[word.get(type)];
+			break;
+		case 'number':
+			ret = translated[month - 1];
+			if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
+			break;
+		case 'string':
+			var match = translated.filter(function(name){
+				return this.test(name);
+			}, new RegExp('^' + word, 'i'));
+			if (!match.length)    throw new Error('Invalid ' + type + ' string');
+			if (match.length > 1) throw new Error('Ambiguous ' + type);
+			ret = match[0];
+	}
+
+	return (num) ? translated.indexOf(ret) : ret;
+};
+
+Date.extend({
+
+	getMsg: function(key, args) {
+		return MooTools.lang.get('Date', key, args);
+	},
+
+	units: {
+		ms: $lambda(1),
+		second: $lambda(1000),
+		minute: $lambda(60000),
+		hour: $lambda(3600000),
+		day: $lambda(86400000),
+		week: $lambda(608400000),
+		month: function(month, year){
+			var d = new Date;
+			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
+		},
+		year: function(year){
+			year = year || new Date().get('year');
+			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+		}
+	},
+
+	daysInMonth: function(month, year){
+		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+	},
+
+	isLeapYear: function(year){
+		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+	},
+
+	parse: function(from){
+		var t = $type(from);
+		if (t == 'number') return new Date(from);
+		if (t != 'string') return from;
+		from = from.clean();
+		if (!from.length) return null;
+
+		var parsed;
+		parsePatterns.some(function(pattern){
+			var bits = pattern.re.exec(from);
+			return (bits) ? (parsed = pattern.handler(bits)) : false;
+		});
+
+		return parsed || new Date(nativeParse(from));
+	},
+
+	parseDay: function(day, num){
+		return parseWord('day', day, num);
+	},
+
+	parseMonth: function(month, num){
+		return parseWord('month', month, num);
+	},
+
+	parseUTC: function(value){
+		var localDate = new Date(value);
+		var utcSeconds = Date.UTC(
+			localDate.get('year'),
+			localDate.get('mo'),
+			localDate.get('date'),
+			localDate.get('hr'),
+			localDate.get('min'),
+			localDate.get('sec')
+		);
+		return new Date(utcSeconds);
+	},
+
+	orderIndex: function(unit){
+		return Date.getMsg('dateOrder').indexOf(unit) + 1;
+	},
+
+	defineFormat: function(name, format){
+		formats[name] = format;
+	},
+
+	defineFormats: function(formats){
+		for (var name in formats) Date.defineFormat(name, formats[name]);
+	},
+
+	parsePatterns: parsePatterns, // this is deprecated
+	
+	defineParser: function(pattern){
+		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+	},
+	
+	defineParsers: function(){
+		Array.flatten(arguments).each(Date.defineParser);
+	},
+	
+	define2DigitYearStart: function(year){
+		startYear = year % 100;
+		startCentury = year - startYear;
+	}
+
+});
+
+var startCentury = 1900;
+var startYear = 70;
+
+var regexOf = function(type){
+	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+		return name.substr(0, 3);
+	}).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+	switch(key){
+		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+			return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
+		case 'X':
+			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
+	}
+	return null;
+};
+
+var keys = {
+	d: /[0-2]?[0-9]|3[01]/,
+	H: /[01]?[0-9]|2[0-3]/,
+	I: /0?[1-9]|1[0-2]/,
+	M: /[0-5]?\d/,
+	s: /\d+/,
+	o: /[a-z]*/,
+	p: /[ap]\.?m\.?/,
+	y: /\d{2}|\d{4}/,
+	Y: /\d{4}/,
+	T: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+	currentLanguage = language;
+	
+	keys.a = keys.A = regexOf('days');
+	keys.b = keys.B = regexOf('months');
+	
+	parsePatterns.each(function(pattern, i){
+		if (pattern.format) parsePatterns[i] = build(pattern.format);
+	});
+};
+
+var build = function(format){
+	if (!currentLanguage) return {format: format};
+	
+	var parsed = [];
+	var re = (format.source || format) // allow format to be regex
+	 .replace(/%([a-z])/gi,
+		function($0, $1){
+			return replacers($1) || $0;
+		}
+	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+	 .replace(/%([a-z%])/gi,
+		function($0, $1){
+			var p = keys[$1];
+			if (!p) return $1;
+			parsed.push($1);
+			return '(' + p.source + ')';
+		}
+	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words
+
+	return {
+		format: format,
+		re: new RegExp('^' + re + '$', 'i'),
+		handler: function(bits){
+			bits = bits.slice(1).associate(parsed);
+			var date = new Date().clearTime();
+			if ('d' in bits) handle.call(date, 'd', 1);
+			if ('m' in bits || 'b' in bits || 'B' in bits) handle.call(date, 'm', 1);
+			for (var key in bits) handle.call(date, key, bits[key]);
+			return date;
+		}
+	};
+};
+
+var handle = function(key, value){
+	if (!value) return this;
+
+	switch(key){
+		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+		case 'd': return this.set('date', value);
+		case 'H': case 'I': return this.set('hr', value);
+		case 'm': return this.set('mo', value - 1);
+		case 'M': return this.set('min', value);
+		case 'p': return this.set('ampm', value.replace(/\./g, ''));
+		case 'S': return this.set('sec', value);
+		case 's': return this.set('ms', ('0.' + value) * 1000);
+		case 'w': return this.set('day', value);
+		case 'Y': return this.set('year', value);
+		case 'y':
+			value = +value;
+			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+			return this.set('year', value);
+		case 'T':
+			if (value == 'Z') value = '+00';
+			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+			return this.set('time', this - offset * 60000);
+	}
+
+	return this;
+};
+
+Date.defineParsers(
+	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+	'%o %b %d %X %T %Y' // "Thu Oct 22 08:11:23 +0000 2009"
+);
+
+MooTools.lang.addEvent('langChange', function(language){
+	if (MooTools.lang.get('Date')) recompile(language);
+}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
+
+})();/*
+---
+
+script: Date.Extras.js
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- /Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+	timeDiffInWords: function(relative_to){
+		return Date.distanceOfTimeInWords(this, relative_to || new Date);
+	},
+
+	timeDiff: function(to, joiner){
+		if (to == null) to = new Date;
+		var delta = ((to - this) / 1000).toInt();
+		if (!delta) return '0s';
+		
+		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
+		var duration, vals = [];
+		
+		for (var step in durations){
+			if (!delta) break;
+			if ((duration = durations[step])){
+				vals.unshift((delta % duration) + step);
+				delta = (delta / duration).toInt();
+			} else {
+				vals.unshift(delta + step);
+			}
+		}
+		
+		return vals.join(joiner || ':');
+	}
+
+});
+
+Date.alias('timeDiffInWords', 'timeAgoInWords');
+
+Date.extend({
+
+	distanceOfTimeInWords: function(from, to){
+		return Date.getTimePhrase(((to - from) / 1000).toInt());
+	},
+
+	getTimePhrase: function(delta){
+		var suffix = (delta < 0) ? 'Until' : 'Ago';
+		if (delta < 0) delta *= -1;
+		
+		var units = {
+			minute: 60,
+			hour: 60,
+			day: 24,
+			week: 7,
+			month: 52 / 12,
+			year: 12,
+			eon: Infinity
+		};
+		
+		var msg = 'lessThanMinute';
+		
+		for (var unit in units){
+			var interval = units[unit];
+			if (delta < 1.5 * interval){
+				if (delta > 0.75 * interval) msg = unit;
+				break;
+			}
+			delta /= interval;
+			msg = unit + 's';
+		}
+		
+		return Date.getMsg(msg + suffix).substitute({delta: delta.round()});
+	}
+
+});
+
+
+Date.defineParsers(
+
+	{
+		// "today", "tomorrow", "yesterday"
+		re: /^(?:tod|tom|yes)/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			switch(bits[0]){
+				case 'tom': return d.increment();
+				case 'yes': return d.decrement();
+				default: 	return d;
+			}
+		}
+	},
+
+	{
+		// "next Wednesday", "last Thursday"
+		re: /^(next|last) ([a-z]+)$/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			var day = d.getDay();
+			var newDay = Date.parseDay(bits[2], true);
+			var addDays = newDay - day;
+			if (newDay <= day) addDays += 7;
+			if (bits[1] == 'last') addDays -= 7;
+			return d.set('date', d.getDate() + addDays);
+		}
+	}
+
+);
+/*
+---
+
+script: Hash.Extras.js
+
+description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Hash.base
+- /MooTools.More
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+	getFromPath: function(notation){
+		var source = this.getClean();
+		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
+			if (!source) return null;
+			var prop = arguments[2] || arguments[1] || arguments[0];
+			source = (prop in source) ? source[prop] : null;
+			return match;
+		});
+		return source;
+	},
+
+	cleanValues: function(method){
+		method = method || $defined;
+		this.each(function(v, k){
+			if (!method(v)) this.erase(k);
+		}, this);
+		return this;
+	},
+
+	run: function(){
+		var args = arguments;
+		this.each(function(v, k){
+			if ($type(v) == 'function') v.run(args);
+		});
+	}
+
+});/*
+---
+
+script: String.Extras.js
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Guillermo Rauch
+
+requires:
+- core:1.2.4/String
+- core:1.2.4/$util
+- core:1.2.4/Array
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+  
+var special = ['À','à','Ã?','á','Â','â','Ã','ã','Ä','ä','Ã…','Ã¥','Ä‚','ă','Ä„','Ä…','Ć','ć','ÄŒ','Ä?','Ç','ç', 'ÄŽ','Ä?','Ä?','Ä‘', 'È','è','É','é','Ê','ê','Ë','ë','Äš','Ä›','Ę','Ä™', 'Äž','ÄŸ','ÃŒ','ì','Ã?','í','ÃŽ','î','Ã?','ï', 'Ĺ','ĺ','Ľ','ľ','Å?','Å‚', 'Ñ','ñ','Ň','ň','Ń','Å„','Ã’','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','Å‘','Ř','Å™','Å”','Å•','Å ','Å¡','Åž','ÅŸ','Åš','Å›', 'Ť','Å¥','Ť','Å¥','Å¢','Å£','Ù','ù','Ú','ú','Û','û','Ãœ','ü','Å®','ů', 'Ÿ','ÿ','ý','Ã?','Ž','ž','Ź','ź','Å»','ż', 'Þ','þ','Ã?','ð','ß','Å’','Å“','Æ','æ','µ'];
+
+var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];
+
+var tidymap = {
+	"[\xa0\u2002\u2003\u2009]": " ",
+	"\xb7": "*",
+	"[\u2018\u2019]": "'",
+	"[\u201c\u201d]": '"',
+	"\u2026": "...",
+	"\u2013": "-",
+	"\u2014": "--",
+	"\uFFFD": "&raquo;"
+};
+
+var getRegForTag = function(tag, contents) {
+	tag = tag || '';
+	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
+	reg = new RegExp(regstr, "gi");
+	return reg;
+};
+
+String.implement({
+
+	standardize: function(){
+		var text = this;
+		special.each(function(ch, i){
+			text = text.replace(new RegExp(ch, 'g'), standard[i]);
+		});
+		return text;
+	},
+
+	repeat: function(times){
+		return new Array(times + 1).join(this);
+	},
+
+	pad: function(length, str, dir){
+		if (this.length >= length) return this;
+		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
+		if (!dir || dir == 'right') return this + pad;
+		if (dir == 'left') return pad + this;
+		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+	},
+
+	getTags: function(tag, contents){
+		return this.match(getRegForTag(tag, contents)) || [];
+	},
+
+	stripTags: function(tag, contents){
+		return this.replace(getRegForTag(tag, contents), '');
+	},
+
+	tidy: function(){
+		var txt = this.toString();
+		$each(tidymap, function(value, key){
+			txt = txt.replace(new RegExp(key, 'g'), value);
+		});
+		return txt;
+	}
+
+});
+
+})();/*
+---
+
+script: String.QueryString.js
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- /MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+	parseQueryString: function(){
+		var vars = this.split(/[&;]/), res = {};
+		if (vars.length) vars.each(function(val){
+			var index = val.indexOf('='),
+				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
+				value = decodeURIComponent(val.substr(index + 1)),
+				obj = res;
+			keys.each(function(key, i){
+				var current = obj[key];
+				if(i < keys.length - 1)
+					obj = obj[key] = current || {};
+				else if($type(current) == 'array')
+					current.push(value);
+				else
+					obj[key] = $defined(current) ? [current, value] : value;
+			});
+		});
+		return res;
+	},
+
+	cleanQueryString: function(method){
+		return this.split('&').filter(function(val){
+			var index = val.indexOf('='),
+			key = index < 0 ? '' : val.substr(0, index),
+			value = val.substr(index + 1);
+			return method ? method.run([key, value]) : $chk(value);
+		}).join('&');
+	}
+
+});/*
+---
+
+script: URI.js
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge
+- Aaron Newton
+
+requires:
+- core:1.2.4/Selectors
+- /String.QueryString
+
+provides: URI
+
+...
+*/
+
+var URI = new Class({
+
+	Implements: Options,
+
+	options: {
+		/*base: false*/
+	},
+
+	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+	initialize: function(uri, options){
+		this.setOptions(options);
+		var base = this.options.base || URI.base;
+		if(!uri) uri = base;
+		
+		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
+		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+	},
+
+	parse: function(value, base){
+		var bits = value.match(this.regex);
+		if (!bits) return false;
+		bits.shift();
+		return this.merge(bits.associate(this.parts), base);
+	},
+
+	merge: function(bits, base){
+		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+		if (base){
+			this.parts.every(function(part){
+				if (bits[part]) return false;
+				bits[part] = base[part] || '';
+				return true;
+			});
+		}
+		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+		return bits;
+	},
+
+	parseDirectory: function(directory, baseDirectory) {
+		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+		if (!directory.test(URI.regs.directoryDot)) return directory;
+		var result = [];
+		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+			if (dir == '..' && result.length > 0) result.pop();
+			else if (dir != '.') result.push(dir);
+		});
+		return result.join('/') + '/';
+	},
+
+	combine: function(bits){
+		return bits.value || bits.scheme + '://' +
+			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+			(bits.directory || '/') + (bits.file || '') +
+			(bits.query ? '?' + bits.query : '') +
+			(bits.fragment ? '#' + bits.fragment : '');
+	},
+
+	set: function(part, value, base){
+		if (part == 'value'){
+			var scheme = value.match(URI.regs.scheme);
+			if (scheme) scheme = scheme[1];
+			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
+			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+		} else if (part == 'data') {
+			this.setData(value);
+		} else {
+			this.parsed[part] = value;
+		}
+		return this;
+	},
+
+	get: function(part, base){
+		switch(part){
+			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+			case 'data' : return this.getData();
+		}
+		return this.parsed[part] || '';
+	},
+
+	go: function(){
+		document.location.href = this.toString();
+	},
+
+	toURI: function(){
+		return this;
+	},
+
+	getData: function(key, part){
+		var qs = this.get(part || 'query');
+		if (!$chk(qs)) return key ? null : {};
+		var obj = qs.parseQueryString();
+		return key ? obj[key] : obj;
+	},
+
+	setData: function(values, merge, part){
+		if (typeof values == 'string'){
+			data = this.getData();
+			data[arguments[0]] = arguments[1];
+			values = data;
+		} else if (merge) {
+			values = $merge(this.getData(), values);
+		}
+		return this.set(part || 'query', Hash.toQueryString(values));
+	},
+
+	clearData: function(part){
+		return this.set(part || 'query', '');
+	}
+
+});
+
+URI.prototype.toString = URI.prototype.valueOf = function(){
+	return this.get('value');
+};
+
+URI.regs = {
+	endSlash: /\/$/,
+	scheme: /^(\w+):/,
+	directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
+
+String.implement({
+
+	toURI: function(options){
+		return new URI(this, options);
+	}
+
+});/*
+---
+
+script: URI.Relative.js
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+- Sebastian Markbåge
+
+
+requires:
+- /Class.refactor
+- /URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+	combine: function(bits, base){
+		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+			return this.previous.apply(this, arguments);
+		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+		var baseDir = base.directory.split('/'),
+			relDir = bits.directory.split('/'),
+			path = '',
+			offset;
+
+		var i = 0;
+		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+		return (path || (bits.file ? '' : './')) + end;
+	},
+
+	toAbsolute: function(base){
+		base = new URI(base);
+		if (base) base.set('directory', '').set('file', '');
+		return this.toRelative(base);
+	},
+
+	toRelative: function(base){
+		return this.get('value', new URI(base));
+	}
+
+});/*
+---
+
+script: Element.Forms.js
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+	tidy: function(){
+		this.set('value', this.get('value').tidy());
+	},
+
+	getTextInRange: function(start, end){
+		return this.get('value').substring(start, end);
+	},
+
+	getSelectedText: function(){
+		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		return document.selection.createRange().text;
+	},
+
+	getSelectedRange: function() {
+		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
+		var pos = {start: 0, end: 0};
+		var range = this.getDocument().selection.createRange();
+		if (!range || range.parentElement() != this) return pos;
+		var dup = range.duplicate();
+		if (this.type == 'text') {
+			pos.start = 0 - dup.moveStart('character', -100000);
+			pos.end = pos.start + range.text.length;
+		} else {
+			var value = this.get('value');
+			var offset = value.length;
+			dup.moveToElementText(this);
+			dup.setEndPoint('StartToEnd', range);
+			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+			pos.end = offset - dup.text.length;
+			dup.setEndPoint('StartToStart', range);
+			pos.start = offset - dup.text.length;
+		}
+		return pos;
+	},
+
+	getSelectionStart: function(){
+		return this.getSelectedRange().start;
+	},
+
+	getSelectionEnd: function(){
+		return this.getSelectedRange().end;
+	},
+
+	setCaretPosition: function(pos){
+		if (pos == 'end') pos = this.get('value').length;
+		this.selectRange(pos, pos);
+		return this;
+	},
+
+	getCaretPosition: function(){
+		return this.getSelectedRange().start;
+	},
+
+	selectRange: function(start, end){
+		if (this.setSelectionRange) {
+			this.focus();
+			this.setSelectionRange(start, end);
+		} else {
+			var value = this.get('value');
+			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+			start = value.substr(0, start).replace(/\r/g, '').length;
+			var range = this.createTextRange();
+			range.collapse(true);
+			range.moveEnd('character', start + diff);
+			range.moveStart('character', start);
+			range.select();
+		}
+		return this;
+	},
+
+	insertAtCursor: function(value, select){
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
+		else this.setCaretPosition(pos.start + value.length);
+		return this;
+	},
+
+	insertAroundCursor: function(options, select){
+		options = $extend({
+			before: '',
+			defaultMiddle: '',
+			after: ''
+		}, options);
+		var value = this.getSelectedText() || options.defaultMiddle;
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		if (pos.start == pos.end){
+			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+		} else {
+			var current = text.substring(pos.start, pos.end);
+			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+			var selStart = pos.start + options.before.length;
+			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
+			else this.setCaretPosition(selStart + text.length);
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Elements.From.js
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- /MooTools.More
+
+provides: [Elements.from]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+	if ($pick(excludeScripts, true)) text = text.stripScripts();
+
+	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
+
+	if (match){
+		container = new Element('table');
+		var tag = match[1].toLowerCase();
+		if (['td', 'th', 'tr'].contains(tag)){
+			container = new Element('tbody').inject(container);
+			if (tag != 'tr') container = new Element('tr').inject(container);
+		}
+	}
+
+	return (container || new Element('div')).set('html', text).getChildren();
+};/*
+---
+
+script: Element.Delegation.js
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+credits:
+- "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Daniel Steigerwald
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Selectors
+- /MooTools.More
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(addEvent, removeEvent){
+	
+	var match = /(.*?):relay\(([^)]+)\)$/,
+		combinators = /[+>~\s]/,
+		splitType = function(type){
+			var bits = type.match(match);
+			return !bits ? {event: type} : {
+				event: bits[1],
+				selector: bits[2]
+			};
+		},
+		check = function(e, selector){
+			var t = e.target;
+			if (combinators.test(selector = selector.trim())){
+				var els = this.getElements(selector);
+				for (var i = els.length; i--; ){
+					var el = els[i];
+					if (t == el || el.hasChild(t)) return el;
+				}
+			} else {
+				for ( ; t && t != this; t = t.parentNode){
+					if (Element.match(t, selector)) return document.id(t);
+				}
+			}
+			return null;
+		};
+
+	Element.implement({
+
+		addEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var monitors = this.retrieve('$moo:delegateMonitors', {});
+				if (!monitors[type]){
+					var monitor = function(e){
+						var el = check.call(this, e, splitted.selector);
+						if (el) this.fireEvent(type, [e, el], 0, el);
+					}.bind(this);
+					monitors[type] = monitor;
+					addEvent.call(this, splitted.event, monitor);
+				}
+			}
+			return addEvent.apply(this, arguments);
+		},
+
+		removeEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var events = this.retrieve('events');
+				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
+
+				if (fn) removeEvent.apply(this, [type, fn]);
+				else removeEvent.apply(this, type);
+
+				events = this.retrieve('events');
+				if (events && events[type] && events[type].keys.length == 0){
+					var monitors = this.retrieve('$moo:delegateMonitors', {});
+					removeEvent.apply(this, [splitted.event, monitors[type]]);
+					delete monitors[type];
+				}
+				return this;
+			}
+			return removeEvent.apply(this, arguments);
+		},
+
+		fireEvent: function(type, args, delay, bind){
+			var events = this.retrieve('events');
+			if (!events || !events[type]) return this;
+			events[type].keys.each(function(fn){
+				fn.create({bind: bind || this, delay: delay, arguments: args})();
+			}, this);
+			return this;
+		}
+
+	});
+
+})(Element.prototype.addEvent, Element.prototype.removeEvent);/*
+---
+
+script: Element.Measure.js
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Style
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+Element.implement({
+
+	measure: function(fn){
+		var vis = function(el) {
+			return !!(!el || el.offsetHeight || el.offsetWidth);
+		};
+		if (vis(this)) return fn.apply(this);
+		var parent = this.getParent(),
+			restorers = [],
+			toMeasure = []; 
+		while (!vis(parent) && parent != document.body) {
+			toMeasure.push(parent.expose());
+			parent = parent.getParent();
+		}
+		var restore = this.expose();
+		var result = fn.apply(this);
+		restore();
+		toMeasure.each(function(restore){
+			restore();
+		});
+		return result;
+	},
+
+	expose: function(){
+		if (this.getStyle('display') != 'none') return $empty;
+		var before = this.style.cssText;
+		this.setStyles({
+			display: 'block',
+			position: 'absolute',
+			visibility: 'hidden'
+		});
+		return function(){
+			this.style.cssText = before;
+		}.bind(this);
+	},
+
+	getDimensions: function(options){
+		options = $merge({computeSize: false},options);
+		var dim = {};
+		var getSize = function(el, options){
+			return (options.computeSize)?el.getComputedSize(options):el.getSize();
+		};
+		var parent = this.getParent('body');
+		if (parent && this.getStyle('display') == 'none'){
+			dim = this.measure(function(){
+				return getSize(this, options);
+			});
+		} else if (parent){
+			try { //safari sometimes crashes here, so catch it
+				dim = getSize(this, options);
+			}catch(e){}
+		} else {
+			dim = {x: 0, y: 0};
+		}
+		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
+	},
+
+	getComputedSize: function(options){
+		options = $merge({
+			styles: ['padding','border'],
+			plains: {
+				height: ['top','bottom'],
+				width: ['left','right']
+			},
+			mode: 'both'
+		}, options);
+		var size = {width: 0,height: 0};
+		switch (options.mode){
+			case 'vertical':
+				delete size.width;
+				delete options.plains.width;
+				break;
+			case 'horizontal':
+				delete size.height;
+				delete options.plains.height;
+				break;
+		}
+		var getStyles = [];
+		//this function might be useful in other places; perhaps it should be outside this function?
+		$each(options.plains, function(plain, key){
+			plain.each(function(edge){
+				options.styles.each(function(style){
+					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
+				});
+			});
+		});
+		var styles = {};
+		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
+		var subtracted = [];
+		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
+			var capitalized = key.capitalize();
+			size['total' + capitalized] = size['computed' + capitalized] = 0;
+			plain.each(function(edge){ //top, left, right, bottom
+				size['computed' + edge.capitalize()] = 0;
+				getStyles.each(function(style, i){ //padding, border, etc.
+					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
+					if (style.test(edge)){
+						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
+						size['total' + capitalized] = size['total' + capitalized] + styles[style];
+						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
+					}
+					//if width != width (so, padding-left, for instance), then subtract that from the total
+					if (style.test(edge) && key != style &&
+						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
+						subtracted.push(style);
+						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
+					}
+				});
+			});
+		});
+
+		['Width', 'Height'].each(function(value){
+			var lower = value.toLowerCase();
+			if(!$chk(size[lower])) return;
+
+			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
+			size['total' + value] = size[lower] + size['total' + value];
+			delete size['computed' + value];
+		}, this);
+
+		return $extend(styles, size);
+	}
+
+});/*
+---
+
+script: Element.Pin.js
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+	var supportsPositionFixed = false;
+	window.addEvent('domready', function(){
+		var test = new Element('div').setStyles({
+			position: 'fixed',
+			top: 0,
+			right: 0
+		}).inject(document.body);
+		supportsPositionFixed = (test.offsetTop === 0);
+		test.dispose();
+	});
+
+	Element.implement({
+
+		pin: function(enable){
+			if (this.getStyle('display') == 'none') return null;
+			
+			var p,
+					scroll = window.getScroll();
+			if (enable !== false){
+				p = this.getPosition();
+				if (!this.retrieve('pinned')){
+					var pos = {
+						top: p.y - scroll.y,
+						left: p.x - scroll.x
+					};
+					if (supportsPositionFixed){
+						this.setStyle('position', 'fixed').setStyles(pos);
+					} else {
+						this.store('pinnedByJS', true);
+						this.setStyles({
+							position: 'absolute',
+							top: p.y,
+							left: p.x
+						}).addClass('isPinned');
+						this.store('scrollFixer', (function(){
+							if (this.retrieve('pinned'))
+								var scroll = window.getScroll();
+								this.setStyles({
+									top: pos.top.toInt() + scroll.y,
+									left: pos.left.toInt() + scroll.x
+								});
+						}).bind(this));
+						window.addEvent('scroll', this.retrieve('scrollFixer'));
+					}
+					this.store('pinned', true);
+				}
+			} else {
+				var op;
+				if (!Browser.Engine.trident){
+					var parent = this.getParent();
+					op = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+				}
+				p = this.getPosition(op);
+				this.store('pinned', false);
+				var reposition;
+				if (supportsPositionFixed && !this.retrieve('pinnedByJS')){
+					reposition = {
+						top: p.y + scroll.y,
+						left: p.x + scroll.x
+					};
+				} else {
+					this.store('pinnedByJS', false);
+					window.removeEvent('scroll', this.retrieve('scrollFixer'));
+					reposition = {
+						top: p.y,
+						left: p.x
+					};
+				}
+				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
+			}
+			return this;
+		},
+
+		unpin: function(){
+			return this.pin(false);
+		},
+
+		togglepin: function(){
+			this.pin(!this.retrieve('pinned'));
+		}
+
+	});
+
+})();/*
+---
+
+script: Element.Position.js
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Element.Measure
+
+provides: [Elements.Position]
+
+...
+*/
+
+(function(){
+
+var original = Element.prototype.position;
+
+Element.implement({
+
+	position: function(options){
+		//call original position if the options are x/y values
+		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
+		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
+		options = $merge({
+			// minimum: { x: 0, y: 0 },
+			// maximum: { x: 0, y: 0},
+			relativeTo: document.body,
+			position: {
+				x: 'center', //left, center, right
+				y: 'center' //top, center, bottom
+			},
+			edge: false,
+			offset: {x: 0, y: 0},
+			returnPos: false,
+			relFixedPosition: false,
+			ignoreMargins: false,
+			ignoreScroll: false,
+			allowNegative: false
+		}, options);
+		//compute the offset of the parent positioned element if this element is in one
+		var parentOffset = {x: 0, y: 0}, 
+				parentPositioned = false;
+		/* dollar around getOffsetParent should not be necessary, but as it does not return
+		 * a mootools extended element in IE, an error occurs on the call to expose. See:
+		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
+		var offsetParent = this.measure(function(){
+			return document.id(this.getOffsetParent());
+		});
+		if (offsetParent && offsetParent != this.getDocument().body){
+			parentOffset = offsetParent.measure(function(){
+				return this.getPosition();
+			});
+			parentPositioned = offsetParent != document.id(options.relativeTo);
+			options.offset.x = options.offset.x - parentOffset.x;
+			options.offset.y = options.offset.y - parentOffset.y;
+		}
+		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
+		//topRight, topLeft, centerTop, centerBottom, center
+		var fixValue = function(option){
+			if ($type(option) != 'string') return option;
+			option = option.toLowerCase();
+			var val = {};
+			if (option.test('left')) val.x = 'left';
+			else if (option.test('right')) val.x = 'right';
+			else val.x = 'center';
+			if (option.test('upper') || option.test('top')) val.y = 'top';
+			else if (option.test('bottom')) val.y = 'bottom';
+			else val.y = 'center';
+			return val;
+		};
+		options.edge = fixValue(options.edge);
+		options.position = fixValue(options.position);
+		if (!options.edge){
+			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
+			else options.edge = {x:'left', y:'top'};
+		}
+
+		this.setStyle('position', 'absolute');
+		var rel = document.id(options.relativeTo) || document.body,
+				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
+				top = calc.y, left = calc.x;
+
+		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
+		var pos = {},
+				prefY = options.offset.y,
+				prefX = options.offset.x,
+				winSize = window.getSize();
+		switch(options.position.x){
+			case 'left':
+				pos.x = left + prefX;
+				break;
+			case 'right':
+				pos.x = left + prefX + rel.offsetWidth;
+				break;
+			default: //center
+				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
+				break;
+		}
+		switch(options.position.y){
+			case 'top':
+				pos.y = top + prefY;
+				break;
+			case 'bottom':
+				pos.y = top + prefY + rel.offsetHeight;
+				break;
+			default: //center
+				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
+				break;
+		}
+		if (options.edge){
+			var edgeOffset = {};
+
+			switch(options.edge.x){
+				case 'left':
+					edgeOffset.x = 0;
+					break;
+				case 'right':
+					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
+					break;
+				default: //center
+					edgeOffset.x = -(dim.totalWidth/2);
+					break;
+			}
+			switch(options.edge.y){
+				case 'top':
+					edgeOffset.y = 0;
+					break;
+				case 'bottom':
+					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
+					break;
+				default: //center
+					edgeOffset.y = -(dim.totalHeight/2);
+					break;
+			}
+			pos.x += edgeOffset.x;
+			pos.y += edgeOffset.y;
+		}
+		pos = {
+			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
+			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
+		};
+		var xy = {left: 'x', top: 'y'};
+		['minimum', 'maximum'].each(function(minmax) {
+			['left', 'top'].each(function(lr) {
+				var val = options[minmax] ? options[minmax][xy[lr]] : null;
+				if (val != null && pos[lr] < val) pos[lr] = val;
+			});
+		});
+		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
+			var winScroll = window.getScroll();
+			pos.top+= winScroll.y;
+			pos.left+= winScroll.x;
+		}
+		if (options.ignoreScroll) {
+			var relScroll = rel.getScroll();
+			pos.top-= relScroll.y;
+			pos.left-= relScroll.x;
+		}
+		if (options.ignoreMargins) {
+			pos.left += (
+				options.edge.x == 'right' ? dim['margin-right'] : 
+				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) : 
+					- dim['margin-left']
+			);
+			pos.top += (
+				options.edge.y == 'bottom' ? dim['margin-bottom'] : 
+				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) : 
+					- dim['margin-top']
+			);
+		}
+		pos.left = Math.ceil(pos.left);
+		pos.top = Math.ceil(pos.top);
+		if (options.returnPos) return pos;
+		else this.setStyles(pos);
+		return this;
+	}
+
+});
+
+})();/*
+---
+
+script: Element.Shortcuts.js
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+	isDisplayed: function(){
+		return this.getStyle('display') != 'none';
+	},
+
+	isVisible: function(){
+		var w = this.offsetWidth,
+			h = this.offsetHeight;
+		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
+	},
+
+	toggle: function(){
+		return this[this.isDisplayed() ? 'hide' : 'show']();
+	},
+
+	hide: function(){
+		var d;
+		try {
+			//IE fails here if the element is not in the dom
+			d = this.getStyle('display');
+		} catch(e){}
+		return this.store('originalDisplay', d || '').setStyle('display', 'none');
+	},
+
+	show: function(display){
+		display = display || this.retrieve('originalDisplay') || 'block';
+		return this.setStyle('display', (display == 'none') ? 'block' : display);
+	},
+
+	swapClass: function(remove, add){
+		return this.removeClass(remove).addClass(add);
+	}
+
+});
+/*
+---
+
+script: IframeShim.js
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/Options Events
+- /Element.Position
+- /Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+var IframeShim = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		className: 'iframeShim',
+		src: 'javascript:false;document.write("");',
+		display: false,
+		zIndex: null,
+		margin: 0,
+		offset: {x: 0, y: 0},
+		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
+	},
+
+	property: 'IframeShim',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.makeShim();
+		return this;
+	},
+
+	makeShim: function(){
+		if(this.options.browsers){
+			var zIndex = this.element.getStyle('zIndex').toInt();
+
+			if (!zIndex){
+				zIndex = 1;
+				var pos = this.element.getStyle('position');
+				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+				this.element.setStyle('zIndex', zIndex);
+			}
+			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+			if (zIndex < 0) zIndex = 1;
+			this.shim = new Element('iframe', {
+				src: this.options.src,
+				scrolling: 'no',
+				frameborder: 0,
+				styles: {
+					zIndex: zIndex,
+					position: 'absolute',
+					border: 'none',
+					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+				},
+				'class': this.options.className
+			}).store('IframeShim', this);
+			var inject = (function(){
+				this.shim.inject(this.element, 'after');
+				this[this.options.display ? 'show' : 'hide']();
+				this.fireEvent('inject');
+			}).bind(this);
+			if (!IframeShim.ready) window.addEvent('load', inject);
+			else inject();
+		} else {
+			this.position = this.hide = this.show = this.dispose = $lambda(this);
+		}
+	},
+
+	position: function(){
+		if (!IframeShim.ready || !this.shim) return this;
+		var size = this.element.measure(function(){ 
+			return this.getSize(); 
+		});
+		if (this.options.margin != undefined){
+			size.x = size.x - (this.options.margin * 2);
+			size.y = size.y - (this.options.margin * 2);
+			this.options.offset.x += this.options.margin;
+			this.options.offset.y += this.options.margin;
+		}
+		this.shim.set({width: size.x, height: size.y}).position({
+			relativeTo: this.element,
+			offset: this.options.offset
+		});
+		return this;
+	},
+
+	hide: function(){
+		if (this.shim) this.shim.setStyle('display', 'none');
+		return this;
+	},
+
+	show: function(){
+		if (this.shim) this.shim.setStyle('display', 'block');
+		return this.position();
+	},
+
+	dispose: function(){
+		if (this.shim) this.shim.dispose();
+		return this;
+	},
+
+	destroy: function(){
+		if (this.shim) this.shim.destroy();
+		return this;
+	}
+
+});
+
+window.addEvent('load', function(){
+	IframeShim.ready = true;
+});/*
+---
+
+script: Mask.js
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- /Class.Binds
+- /Element.Position
+- /IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['position'],
+
+	options: {
+		// onShow: $empty,
+		// onHide: $empty,
+		// onDestroy: $empty,
+		// onClick: $empty,
+		//inject: {
+		//  where: 'after',
+		//  target: null,
+		//},
+		// hideOnClick: false,
+		// id: null,
+		// destroyOnHide: false,
+		style: {},
+		'class': 'mask',
+		maskMargins: false,
+		useIframeShim: true,
+		iframeShimOptions: {}
+	},
+
+	initialize: function(target, options){
+		this.target = document.id(target) || document.id(document.body);
+		this.target.store('Mask', this);
+		this.setOptions(options);
+		this.render();
+		this.inject();
+	},
+	
+	render: function() {
+		this.element = new Element('div', {
+			'class': this.options['class'],
+			id: this.options.id || 'mask-' + $time(),
+			styles: $merge(this.options.style, {
+				display: 'none'
+			}),
+			events: {
+				click: function(){
+					this.fireEvent('click');
+					if (this.options.hideOnClick) this.hide();
+				}.bind(this)
+			}
+		});
+		this.hidden = true;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	inject: function(target, where){
+		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
+		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
+		this.element.inject(target, where);
+		if (this.options.useIframeShim) {
+			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+			this.addEvents({
+				show: this.shim.show.bind(this.shim),
+				hide: this.shim.hide.bind(this.shim),
+				destroy: this.shim.destroy.bind(this.shim)
+			});
+		}
+	},
+
+	position: function(){
+		this.resize(this.options.width, this.options.height);
+		this.element.position({
+			relativeTo: this.target,
+			position: 'topLeft',
+			ignoreMargins: !this.options.maskMargins,
+			ignoreScroll: this.target == document.body
+		});
+		return this;
+	},
+
+	resize: function(x, y){
+		var opt = {
+			styles: ['padding', 'border']
+		};
+		if (this.options.maskMargins) opt.styles.push('margin');
+		var dim = this.target.getComputedSize(opt);
+		if (this.target == document.body) {
+			var win = window.getSize();
+			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+		}
+		this.element.setStyles({
+			width: $pick(x, dim.totalWidth, dim.x),
+			height: $pick(y, dim.totalHeight, dim.y)
+		});
+		return this;
+	},
+
+	show: function(){
+		if (!this.hidden) return this;
+		window.addEvent('resize', this.position);
+		this.position();
+		this.showMask.apply(this, arguments);
+		return this;
+	},
+
+	showMask: function(){
+		this.element.setStyle('display', 'block');
+		this.hidden = false;
+		this.fireEvent('show');
+	},
+
+	hide: function(){
+		if (this.hidden) return this;
+		window.removeEvent('resize', this.position);
+		this.hideMask.apply(this, arguments);
+		if (this.options.destroyOnHide) return this.destroy();
+		return this;
+	},
+
+	hideMask: function(){
+		this.element.setStyle('display', 'none');
+		this.hidden = true;
+		this.fireEvent('hide');
+	},
+
+	toggle: function(){
+		this[this.hidden ? 'show' : 'hide']();
+	},
+
+	destroy: function(){
+		this.hide();
+		this.element.destroy();
+		this.fireEvent('destroy');
+		this.target.eliminate('mask');
+	}
+
+});
+
+Element.Properties.mask = {
+
+	set: function(options){
+		var mask = this.retrieve('mask');
+		return this.eliminate('mask').store('mask:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('mask')){
+			if (this.retrieve('mask')) this.retrieve('mask').destroy();
+			if (options || !this.retrieve('mask:options')) this.set('mask', options);
+			this.store('mask', new Mask(this, this.retrieve('mask:options')));
+		}
+		return this.retrieve('mask');
+	}
+
+};
+
+Element.implement({
+
+	mask: function(options){
+		this.get('mask', options).show();
+		return this;
+	},
+
+	unmask: function(){
+		this.get('mask').hide();
+		return this;
+	}
+
+});/*
+---
+
+script: Spinner.js
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Tween
+- /Class.refactor
+- /Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+	Extends: Mask,
+
+	options: {
+		/*message: false,*/
+		'class':'spinner',
+		containerPosition: {},
+		content: {
+			'class':'spinner-content'
+		},
+		messageContainer: {
+			'class':'spinner-msg'
+		},
+		img: {
+			'class':'spinner-img'
+		},
+		fxOptions: {
+			link: 'chain'
+		}
+	},
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		this.target.store('spinner', this);
+
+		//add this to events for when noFx is true; parent methods handle hide/show
+		var deactivate = function(){ this.active = false; }.bind(this);
+		this.addEvents({
+			hide: deactivate,
+			show: deactivate
+		});
+	},
+
+	render: function(){
+		this.parent();
+		this.element.set('id', this.options.id || 'spinner-'+$time());
+		this.content = document.id(this.options.content) || new Element('div', this.options.content);
+		this.content.inject(this.element);
+		if (this.options.message) {
+			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+			this.msg.inject(this.content);
+		}
+		if (this.options.img) {
+			this.img = document.id(this.options.img) || new Element('div', this.options.img);
+			this.img.inject(this.content);
+		}
+		this.element.set('tween', this.options.fxOptions);
+	},
+
+	show: function(noFx){
+		if (this.active) return this.chain(this.show.bind(this));
+		if (!this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	showMask: function(noFx){
+		var pos = function(){
+			this.content.position($merge({
+				relativeTo: this.element
+			}, this.options.containerPosition));
+		}.bind(this);
+		if (noFx) {
+			this.parent();
+			pos();
+		} else {
+			this.element.setStyles({
+				display: 'block',
+				opacity: 0
+			}).tween('opacity', this.options.style.opacity || 0.9);
+			pos();
+			this.hidden = false;
+			this.fireEvent('show');
+			this.callChain();
+		}
+	},
+
+	hide: function(noFx){
+		if (this.active) return this.chain(this.hide.bind(this));
+		if (this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	hideMask: function(noFx){
+		if (noFx) return this.parent();
+		this.element.tween('opacity', 0).get('tween').chain(function(){
+			this.element.setStyle('display', 'none');
+			this.hidden = true;
+			this.fireEvent('hide');
+			this.callChain();
+		}.bind(this));
+	},
+
+	destroy: function(){
+		this.content.destroy();
+		this.parent();
+		this.target.eliminate('spinner');
+	}
+
+});
+
+Spinner.implement(new Chain);
+
+if (window.Request) {
+	Request = Class.refactor(Request, {
+		
+		options: {
+			useSpinner: false,
+			spinnerOptions: {},
+			spinnerTarget: false
+		},
+		
+		initialize: function(options){
+			this._send = this.send;
+			this.send = function(options){
+				if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
+				else this._send(options);
+				return this;
+			};
+			this.previous(options);
+			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+			if (this.options.useSpinner && update) {
+				this.spinner = update.get('spinner', this.options.spinnerOptions);
+				['onComplete', 'onException', 'onCancel'].each(function(event){
+					this.addEvent(event, this.spinner.hide.bind(this.spinner));
+				}, this);
+			}
+		},
+		
+		getSpinner: function(){
+			return this.spinner;
+		}
+		
+	});
+}
+
+Element.Properties.spinner = {
+
+	set: function(options){
+		var spinner = this.retrieve('spinner');
+		return this.eliminate('spinner').store('spinner:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('spinner')){
+			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
+			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
+			new Spinner(this, this.retrieve('spinner:options'));
+		}
+		return this.retrieve('spinner');
+	}
+
+};
+
+Element.implement({
+
+	spin: function(options){
+		this.get('spinner', options).show();
+		return this;
+	},
+
+	unspin: function(){
+		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
+		this.get('spinner', opt.options).hide(opt.callback);
+		return this;
+	}
+
+});/*
+---
+
+script: Form.Request.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Event
+- core:1.2.4/Request.HTML
+- /Class.Binds
+- /Class.Occlude
+- /Spinner
+- /String.QueryString
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+	Form.Request = new Class({
+
+		Binds: ['onSubmit', 'onFormValidate'],
+
+		Implements: [Options, Events, Class.Occlude],
+
+		options: {
+			//onFailure: $empty,
+			//onSuccess: #empty, //aliased to onComplete,
+			//onSend: $empty
+			requestOptions: {
+				evalScripts: true,
+				useSpinner: true,
+				emulation: false,
+				link: 'ignore'
+			},
+			extraData: {},
+			resetForm: true
+		},
+
+		property: 'form.request',
+
+		initialize: function(form, update, options) {
+			this.element = document.id(form);
+			if (this.occlude()) return this.occluded;
+			this.update = document.id(update);
+			this.setOptions(options);
+			this.makeRequest();
+			if (this.options.resetForm) {
+				this.request.addEvent('success', function(){
+					$try(function(){ this.element.reset(); }.bind(this));
+					if (window.OverText) OverText.update();
+				}.bind(this));
+			}
+			this.attach();
+		},
+
+		toElement: function() {
+			return this.element;
+		},
+
+		makeRequest: function(){
+			this.request = new Request.HTML($merge({
+					update: this.update,
+					emulation: false,
+					spinnerTarget: this.element,
+					method: this.element.get('method') || 'post'
+			}, this.options.requestOptions)).addEvents({
+				success: function(text, xml){
+					['complete', 'success'].each(function(evt){
+						this.fireEvent(evt, [this.update, text, xml]);
+					}, this);
+				}.bind(this),
+				failure: function(xhr){
+					this.fireEvent('complete').fireEvent('failure', xhr);
+				}.bind(this),
+				exception: function(){
+					this.fireEvent('failure', xhr);
+				}.bind(this)
+			});
+		},
+
+		attach: function(attach){
+			attach = $pick(attach, true);
+			method = attach ? 'addEvent' : 'removeEvent';
+			
+			var fv = this.element.retrieve('validator');
+			if (fv) fv[method]('onFormValidate', this.onFormValidate);
+			if (!fv || !attach) this.element[method]('submit', this.onSubmit);
+		},
+
+		detach: function(){
+			this.attach(false);
+		},
+
+		//public method
+		enable: function(){
+			this.attach();
+		},
+
+		//public method
+		disable: function(){
+			this.detach();
+		},
+
+		onFormValidate: function(valid, form, e) {
+			var fv = this.element.retrieve('validator');
+			if (valid || (fv && !fv.options.stopOnFailure)) {
+				if (e && e.stop) e.stop();
+				this.send();
+			}
+		},
+
+		onSubmit: function(e){
+			if (this.element.retrieve('validator')) {
+				//form validator was created after Form.Request
+				this.detach();
+				return;
+			}
+			e.stop();
+			this.send();
+		},
+
+		send: function(){
+			var str = this.element.toQueryString().trim();
+			var data = $H(this.options.extraData).toQueryString();
+			if (str) str += "&" + data;
+			else str = data;
+			this.fireEvent('send', [this.element, str.parseQueryString()]);
+			this.request.send({data: str, url: this.element.get("action")});
+			return this;
+		}
+
+	});
+
+	Element.Properties.formRequest = {
+
+		set: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			var updater = this.retrieve('form.request');
+			if (update) {
+				if (updater) updater.update = document.id(update);
+				this.store('form.request:update', update);
+			}
+			if (opt.options) {
+				if (updater) updater.setOptions(opt.options);
+				this.store('form.request:options', opt.options);
+			}
+			return this;
+		},
+
+		get: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			if (opt.options || update || !this.retrieve('form.request')){
+				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
+				if (update) this.set('form.request', update);
+				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
+			}
+			return this.retrieve('form.request');
+		}
+
+	};
+
+	Element.implement({
+
+		formUpdate: function(update, options){
+			this.get('form.request', update, options).send();
+			return this;
+		}
+
+	});
+
+})();/*
+---
+
+script: Fx.Reveal.js
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Morph
+- /Element.Shortcuts
+- /Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+Fx.Reveal = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {/*	  
+		onShow: $empty(thisElement),
+		onHide: $empty(thisElement),
+		onComplete: $empty(thisElement),
+		heightOverride: null,
+		widthOverride: null, */
+		link: 'cancel',
+		styles: ['padding', 'border', 'margin'],
+		transitionOpacity: !Browser.Engine.trident4,
+		mode: 'vertical',
+		display: 'block',
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
+	},
+
+	dissolve: function(){
+		try {
+			if (!this.hiding && !this.showing){
+				if (this.element.getStyle('display') != 'none'){
+					this.hiding = true;
+					this.showing = false;
+					this.hidden = true;
+					this.cssText = this.element.style.cssText;
+					var startStyles = this.element.getComputedSize({
+						styles: this.options.styles,
+						mode: this.options.mode
+					});
+					this.element.setStyle('display', this.options.display);
+					if (this.options.transitionOpacity) startStyles.opacity = 1;
+					var zero = {};
+					$each(startStyles, function(style, name){
+						zero[name] = [style, 0];
+					}, this);
+					this.element.setStyle('overflow', 'hidden');
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					this.$chain.unshift(function(){
+						if (this.hidden){
+							this.hiding = false;
+							$each(startStyles, function(style, name){
+								startStyles[name] = style;
+							}, this);
+							this.element.style.cssText = this.cssText;
+							this.element.setStyle('display', 'none');
+							if (hideThese) hideThese.setStyle('visibility', 'visible');
+						}
+						this.fireEvent('hide', this.element);
+						this.callChain();
+					}.bind(this));
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					this.start(zero);
+				} else {
+					this.callChain.delay(10, this);
+					this.fireEvent('complete', this.element);
+					this.fireEvent('hide', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.dissolve.bind(this));
+			} else if (this.options.link == 'cancel' && !this.hiding){
+				this.cancel();
+				this.dissolve();
+			}
+		} catch(e){
+			this.hiding = false;
+			this.element.setStyle('display', 'none');
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('hide', this.element);
+		}
+		return this;
+	},
+
+	reveal: function(){
+		try {
+			if (!this.showing && !this.hiding){
+				if (this.element.getStyle('display') == 'none' ||
+					 this.element.getStyle('visiblity') == 'hidden' ||
+					 this.element.getStyle('opacity') == 0){
+					this.showing = true;
+					this.hiding = this.hidden =  false;
+					var startStyles;
+					this.cssText = this.element.style.cssText;
+					//toggle display, but hide it
+					this.element.measure(function(){
+						//create the styles for the opened/visible state
+						startStyles = this.element.getComputedSize({
+							styles: this.options.styles,
+							mode: this.options.mode
+						});
+					}.bind(this));
+					$each(startStyles, function(style, name){
+						startStyles[name] = style;
+					});
+					//if we're overridding height/width
+					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
+					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
+					if (this.options.transitionOpacity) {
+						this.element.setStyle('opacity', 0);
+						startStyles.opacity = 1;
+					}
+					//create the zero state for the beginning of the transition
+					var zero = {
+						height: 0,
+						display: this.options.display
+					};
+					$each(startStyles, function(style, name){ zero[name] = 0; });
+					//set to zero
+					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
+					//hide inputs
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					//start the effect
+					this.start(startStyles);
+					this.$chain.unshift(function(){
+						this.element.style.cssText = this.cssText;
+						this.element.setStyle('display', this.options.display);
+						if (!this.hidden) this.showing = false;
+						if (hideThese) hideThese.setStyle('visibility', 'visible');
+						this.callChain();
+						this.fireEvent('show', this.element);
+					}.bind(this));
+				} else {
+					this.callChain();
+					this.fireEvent('complete', this.element);
+					this.fireEvent('show', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.reveal.bind(this));
+			} else if (this.options.link == 'cancel' && !this.showing){
+				this.cancel();
+				this.reveal();
+			}
+		} catch(e){
+			this.element.setStyles({
+				display: this.options.display,
+				visiblity: 'visible',
+				opacity: 1
+			});
+			this.showing = false;
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('show', this.element);
+		}
+		return this;
+	},
+
+	toggle: function(){
+		if (this.element.getStyle('display') == 'none' ||
+			 this.element.getStyle('visiblity') == 'hidden' ||
+			 this.element.getStyle('opacity') == 0){
+			this.reveal();
+		} else {
+			this.dissolve();
+		}
+		return this;
+	},
+
+	cancel: function(){
+		this.parent.apply(this, arguments);
+		this.element.style.cssText = this.cssText;
+		this.hidding = false;
+		this.showing = false;
+	}
+
+});
+
+Element.Properties.reveal = {
+
+	set: function(options){
+		var reveal = this.retrieve('reveal');
+		if (reveal) reveal.cancel();
+		return this.eliminate('reveal').store('reveal:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('reveal')){
+			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
+			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
+		}
+		return this.retrieve('reveal');
+	}
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+	reveal: function(options){
+		this.get('reveal', options).reveal();
+		return this;
+	},
+
+	dissolve: function(options){
+		this.get('reveal', options).dissolve();
+		return this;
+	},
+
+	nix: function(){
+		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
+		this.get('reveal', params.options).dissolve().chain(function(){
+			this[params.destroy ? 'destroy' : 'dispose']();
+		}.bind(this));
+		return this;
+	},
+
+	wink: function(){
+		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
+		var reveal = this.get('reveal', params.options);
+		reveal.reveal().chain(function(){
+			(function(){
+				reveal.dissolve();
+			}).delay(params.duration || 2000);
+		});
+	}
+
+
+});/*
+---
+
+script: Form.Request.Append.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Request
+- /Fx.Reveal
+- /Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+	Extends: Form.Request,
+
+	options: {
+		//onBeforeEffect: $empty,
+		useReveal: true,
+		revealOptions: {},
+		inject: 'bottom'
+	},
+
+	makeRequest: function(){
+		this.request = new Request.HTML($merge({
+				url: this.element.get('action'),
+				method: this.element.get('method') || 'post',
+				spinnerTarget: this.element
+			}, this.options.requestOptions, {
+				evalScripts: false
+			})
+		).addEvents({
+			success: function(tree, elements, html, javascript){
+				var container;
+				var kids = Elements.from(html);
+				if (kids.length == 1) {
+					container = kids[0];
+				} else {
+					 container = new Element('div', {
+						styles: {
+							display: 'none'
+						}
+					}).adopt(kids);
+				}
+				container.inject(this.update, this.options.inject);
+				if (this.options.requestOptions.evalScripts) $exec(javascript);
+				this.fireEvent('beforeEffect', container);
+				var finish = function(){
+					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
+				}.bind(this);
+				if (this.options.useReveal) {
+					container.get('reveal', this.options.revealOptions).chain(finish);
+					container.reveal();
+				} else {
+					finish();
+				}
+			}.bind(this),
+			failure: function(xhr){
+				this.fireEvent('failure', xhr);
+			}.bind(this)
+		});
+	}
+
+});/*
+---
+
+script: Form.Validator.English.js
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.English]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Form.Validator', {
+
+	required:'This field is required.',
+	minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
+	maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
+	integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+	numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+	digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+	alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
+	alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
+	dateSuchAs:'Please enter a valid date such as {date}',
+	dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+	email:'Please enter a valid email address. For example "fred at domain.com".',
+	url:'Please enter a valid URL such as http://www.google.com.',
+	currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
+	oneRequired:'Please enter something for at least one of these inputs.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Warning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'There can be no spaces in this input.',
+	reqChkByNode: 'No items are selected.',
+	requiredChk: 'This field is required.',
+	reqChkByName: 'Please select a {label}.',
+	match: 'This field needs to match the {matchName} field',
+	startDate: 'the start date',
+	endDate: 'the end date',
+	currendDate: 'the current date',
+	afterDate: 'The date should be the same or after {label}.',
+	beforeDate: 'The date should be the same or before {label}.',
+	startMonth: 'Please select a start month',
+	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+/*
+---
+
+script: Form.Validator.js
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Selectors
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/JSON
+- /Lang- /Class.Binds
+- /Date Element.Forms
+- /Form.Validator.English
+- /Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = new Class({
+
+	Implements: [Options],
+
+	options: {
+		errorMsg: 'Validation failed.',
+		test: function(field){return true;}
+	},
+
+	initialize: function(className, options){
+		this.setOptions(options);
+		this.className = className;
+	},
+
+	test: function(field, props){
+		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
+		else return false;
+	},
+
+	getError: function(field, props){
+		var err = this.options.errorMsg;
+		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
+		return err;
+	},
+
+	getProps: function(field){
+		if (!document.id(field)) return {};
+		return field.get('validatorProps');
+	}
+
+});
+
+Element.Properties.validatorProps = {
+
+	set: function(props){
+		return this.eliminate('validatorProps').store('validatorProps', props);
+	},
+
+	get: function(props){
+		if (props) this.set(props);
+		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
+		if (this.getProperty('validatorProps')){
+			try {
+				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
+			}catch(e){
+				return {};
+			}
+		} else {
+			var vals = this.get('class').split(' ').filter(function(cls){
+				return cls.test(':');
+			});
+			if (!vals.length){
+				this.store('validatorProps', {});
+			} else {
+				props = {};
+				vals.each(function(cls){
+					var split = cls.split(':');
+					if (split[1]) {
+						try {
+							props[split[0]] = JSON.decode(split[1]);
+						} catch(e) {}
+					}
+				});
+				this.store('validatorProps', props);
+			}
+		}
+		return this.retrieve('validatorProps');
+	}
+
+};
+
+Form.Validator = new Class({
+
+	Implements:[Options, Events],
+
+	Binds: ['onSubmit'],
+
+	options: {/*
+		onFormValidate: $empty(isValid, form, event),
+		onElementValidate: $empty(isValid, field, className, warn),
+		onElementPass: $empty(field),
+		onElementFail: $empty(field, validatorsFailed) */
+		fieldSelectors: 'input, select, textarea',
+		ignoreHidden: true,
+		ignoreDisabled: true,
+		useTitles: false,
+		evaluateOnSubmit: true,
+		evaluateFieldsOnBlur: true,
+		evaluateFieldsOnChange: true,
+		serial: true,
+		stopOnFailure: true,
+		warningPrefix: function(){
+			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+		},
+		errorPrefix: function(){
+			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+		}
+	},
+
+	initialize: function(form, options){
+		this.setOptions(options);
+		this.element = document.id(form);
+		this.element.store('validator', this);
+		this.warningPrefix = $lambda(this.options.warningPrefix)();
+		this.errorPrefix = $lambda(this.options.errorPrefix)();
+		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
+		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	getFields: function(){
+		return (this.fields = this.element.getElements(this.options.fieldSelectors));
+	},
+
+	watchFields: function(fields){
+		fields.each(function(el){
+			if (this.options.evaluateFieldsOnBlur)
+				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
+			if (this.options.evaluateFieldsOnChange)
+				el.addEvent('change', this.validationMonitor.pass([el, true], this));
+		}, this);
+	},
+
+	validationMonitor: function(){
+		$clear(this.timer);
+		this.timer = this.validateField.delay(50, this, arguments);
+	},
+
+	onSubmit: function(event){
+		if (!this.validate(event) && event) event.preventDefault();
+		else this.reset();
+	},
+
+	reset: function(){
+		this.getFields().each(this.resetField, this);
+		return this;
+	},
+
+	validate: function(event){
+		var result = this.getFields().map(function(field){
+			return this.validateField(field, true);
+		}, this).every(function(v){ return v;});
+		this.fireEvent('formValidate', [result, this.element, event]);
+		if (this.options.stopOnFailure && !result && event) event.preventDefault();
+		return result;
+	},
+
+	validateField: function(field, force){
+		if (this.paused) return true;
+		field = document.id(field);
+		var passed = !field.hasClass('validation-failed');
+		var failed, warned;
+		if (this.options.serial && !force){
+			failed = this.element.getElement('.validation-failed');
+			warned = this.element.getElement('.warning');
+		}
+		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+			var validators = field.className.split(' ').some(function(cn){
+				return this.getValidator(cn);
+			}, this);
+			var validatorsFailed = [];
+			field.className.split(' ').each(function(className){
+				if (className && !this.test(className, field)) validatorsFailed.include(className);
+			}, this);
+			passed = validatorsFailed.length === 0;
+			if (validators && !field.hasClass('warnOnly')){
+				if (passed){
+					field.addClass('validation-passed').removeClass('validation-failed');
+					this.fireEvent('elementPass', field);
+				} else {
+					field.addClass('validation-failed').removeClass('validation-passed');
+					this.fireEvent('elementFail', [field, validatorsFailed]);
+				}
+			}
+			if (!warned){
+				var warnings = field.className.split(' ').some(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.getValidator(cn.replace(/^warn-/,''));
+					else return null;
+				}, this);
+				field.removeClass('warning');
+				var warnResult = field.className.split(' ').map(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.test(cn.replace(/^warn-/,''), field, true);
+					else return null;
+				}, this);
+			}
+		}
+		return passed;
+	},
+
+	test: function(className, field, warn){
+		field = document.id(field);
+		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+		var validator = this.getValidator(className);
+		if (field.hasClass('ignoreValidation')) return true;
+		warn = $pick(warn, false);
+		if (field.hasClass('warnOnly')) warn = true;
+		var isValid = validator ? validator.test(field) : true;
+		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+		if (warn) return true;
+		return isValid;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (field){
+			field.className.split(' ').each(function(className){
+				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+				field.removeClass('validation-failed');
+				field.removeClass('warning');
+				field.removeClass('validation-passed');
+			}, this);
+		}
+		return this;
+	},
+
+	stop: function(){
+		this.paused = true;
+		return this;
+	},
+
+	start: function(){
+		this.paused = false;
+		return this;
+	},
+
+	ignoreField: function(field, warn){
+		field = document.id(field);
+		if (field){
+			this.enforceField(field);
+			if (warn) field.addClass('warnOnly');
+			else field.addClass('ignoreValidation');
+		}
+		return this;
+	},
+
+	enforceField: function(field){
+		field = document.id(field);
+		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+		return this;
+	}
+
+});
+
+Form.Validator.getMsg = function(key){
+	return MooTools.lang.get('Form.Validator', key);
+};
+
+Form.Validator.adders = {
+
+	validators:{},
+
+	add : function(className, options){
+		this.validators[className] = new InputValidator(className, options);
+		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+		//extend these validators into it
+		//this allows validators to be global and/or per instance
+		if (!this.initialize){
+			this.implement({
+				validators: this.validators
+			});
+		}
+	},
+
+	addAllThese : function(validators){
+		$A(validators).each(function(validator){
+			this.add(validator[0], validator[1]);
+		}, this);
+	},
+
+	getValidator: function(className){
+		return this.validators[className.split(':')[0]];
+	}
+
+};
+
+$extend(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+	errorMsg: false,
+	test: function(element){
+		if (element.type == 'select-one' || element.type == 'select')
+			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+		else
+			return ((element.get('value') == null) || (element.get('value').length == 0));
+	}
+
+});
+
+Form.Validator.addAllThese([
+
+	['required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element){
+			return !Form.Validator.getValidator('IsEmpty').test(element);
+		}
+	}],
+
+	['minLength', {
+		errorMsg: function(element, props){
+			if ($type(props.minLength))
+				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
+			else return true;
+		}
+	}],
+
+	['maxLength', {
+		errorMsg: function(element, props){
+			//props is {maxLength:10}
+			if ($type(props.maxLength))
+				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			//if the value is <= than the maxLength value, element passes test
+			return (element.get('value').length <= $pick(props.maxLength, 10000));
+		}
+	}],
+
+	['validate-integer', {
+		errorMsg: Form.Validator.getMsg.pass('integer'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-numeric', {
+		errorMsg: Form.Validator.getMsg.pass('numeric'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||
+				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-digits', {
+		errorMsg: Form.Validator.getMsg.pass('digits'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+		}
+	}],
+
+	['validate-alpha', {
+		errorMsg: Form.Validator.getMsg.pass('alpha'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-alphanum', {
+		errorMsg: Form.Validator.getMsg.pass('alphanum'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+		}
+	}],
+
+	['validate-date', {
+		errorMsg: function(element, props){
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+			} else {
+				return Form.Validator.getMsg('dateInFormatMDY');
+			}
+		},
+		test: function(element, props){
+			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+			var d;
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				d = Date.parse(element.get('value'));
+				var formatted = d.format(format);
+				if (formatted != 'invalid date') element.set('value', formatted);
+				return !isNaN(d);
+			} else {
+				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
+				if (!regex.test(element.get('value'))) return false;
+				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
+				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
+					(parseInt(RegExp.$2, 10) == d.getDate()) &&
+					(parseInt(RegExp.$3, 10) == d.getFullYear());
+			}
+		}
+	}],
+
+	['validate-email', {
+		errorMsg: Form.Validator.getMsg.pass('email'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-url', {
+		errorMsg: Form.Validator.getMsg.pass('url'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-currency-dollar', {
+		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+		test: function(element){
+			// [$]1[##][,###]+[.##]
+			// [$]1###+[.##]
+			// [$]0.##
+			// [$].##
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-one-required', {
+		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+		test: function(element, props){
+			var p = document.id(props['validate-one-required']) || element.getParent();
+			return p.getElements('input').some(function(el){
+				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+				return el.get('value');
+			});
+		}
+	}]
+
+]);
+
+Element.Properties.validator = {
+
+	set: function(options){
+		var validator = this.retrieve('validator');
+		if (validator) validator.setOptions(options);
+		return this.store('validator:options');
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('validator')){
+			if (options || !this.retrieve('validator:options')) this.set('validator', options);
+			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
+		}
+		return this.retrieve('validator');
+	}
+
+};
+
+Element.implement({
+
+	validate: function(options){
+		this.set('validator', options);
+		return this.get('validator', options).validate();
+	}
+
+});
+//legacy
+var FormValidator = Form.Validator;/*
+---
+
+script: Form.Validator.Inline.js
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+	Extends: Form.Validator,
+
+	options: {
+		scrollToErrorsOnSubmit: true,
+		scrollFxOptions: {
+			transition: 'quad:out',
+			offset: {
+				y: -20
+			}
+		}
+	},
+
+	initialize: function(form, options){
+		this.parent(form, options);
+		this.addEvent('onElementValidate', function(isValid, field, className, warn){
+			var validator = this.getValidator(className);
+			if (!isValid && validator.getError(field)){
+				if (warn) field.addClass('warning');
+				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+				this.insertAdvice(advice, field);
+				this.showAdvice(className, field);
+			} else {
+				this.hideAdvice(className, field);
+			}
+		});
+	},
+
+	makeAdvice: function(className, field, error, warn){
+		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
+			errorMsg += (this.options.useTitles) ? field.title || error:error;
+		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+		var advice = this.getAdvice(className, field);
+		if(advice) {
+			advice = advice.set('html', errorMsg);
+		} else {
+			advice = new Element('div', {
+				html: errorMsg,
+				styles: { display: 'none' },
+				id: 'advice-' + className + '-' + this.getFieldId(field)
+			}).addClass(cssClass);
+		}
+		field.store('advice-' + className, advice);
+		return advice;
+	},
+
+	getFieldId : function(field){
+		return field.id ? field.id : field.id = 'input_' + field.name;
+	},
+
+	showAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && !field.retrieve(this.getPropName(className))
+				&& (advice.getStyle('display') == 'none'
+				|| advice.getStyle('visiblity') == 'hidden'
+				|| advice.getStyle('opacity') == 0)){
+			field.store(this.getPropName(className), true);
+			if (advice.reveal) advice.reveal();
+			else advice.setStyle('display', 'block');
+		}
+	},
+
+	hideAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && field.retrieve(this.getPropName(className))){
+			field.store(this.getPropName(className), false);
+			//if Fx.Reveal.js is present, transition the advice out
+			if (advice.dissolve) advice.dissolve();
+			else advice.setStyle('display', 'none');
+		}
+	},
+
+	getPropName: function(className){
+		return 'advice' + className;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (!field) return this;
+		this.parent(field);
+		field.className.split(' ').each(function(className){
+			this.hideAdvice(className, field);
+		}, this);
+		return this;
+	},
+
+	getAllAdviceMessages: function(field, force){
+		var advice = [];
+		if (field.hasClass('ignoreValidation') && !force) return advice;
+		var validators = field.className.split(' ').some(function(cn){
+			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+			if (warner) cn = cn.replace(/^warn-/, '');
+			var validator = this.getValidator(cn);
+			if (!validator) return;
+			advice.push({
+				message: validator.getError(field),
+				warnOnly: warner,
+				passed: validator.test(),
+				validator: validator
+			});
+		}, this);
+		return advice;
+	},
+
+	getAdvice: function(className, field){
+		return field.retrieve('advice-' + className);
+	},
+
+	insertAdvice: function(advice, field){
+		//Check for error position prop
+		var props = field.get('validatorProps');
+		//Build advice
+		if (!props.msgPos || !document.id(props.msgPos)){
+			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+			else advice.inject(document.id(field), 'after');
+		} else {
+			document.id(props.msgPos).grab(advice);
+		}
+	},
+
+	validateField: function(field, force){
+		var result = this.parent(field, force);
+		if (this.options.scrollToErrorsOnSubmit && !result){
+			var failed = document.id(this).getElement('.validation-failed');
+			var par = document.id(this).getParent();
+			while (par != document.body && par.getScrollSize().y == par.getSize().y){
+				par = par.getParent();
+			}
+			var fx = par.retrieve('fvScroller');
+			if (!fx && window.Fx && Fx.Scroll){
+				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+				par.store('fvScroller', fx);
+			}
+			if (failed){
+				if (fx) fx.toElement(failed);
+				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+			}
+		}
+		return result;
+	}
+
+});
+/*
+---
+
+script: Form.Validator.Extras.js
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+	['validate-enforce-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-ignore-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-nospace', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('noSpace');
+		},
+		test: function(element, props){
+			return !element.get('value').test(/\s/);
+		}
+	}],
+
+	['validate-toggle-oncheck', {
+		test: function(element, props){
+			var fv = element.getParent('form').retrieve('validator');
+			if (!fv) return true;
+			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+			if (!element.checked){
+				eleArr.each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			} else {
+				eleArr.each(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-reqchk-bynode', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('reqChkByNode');
+		},
+		test: function(element, props){
+			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+				return item.checked;
+			});
+		}
+	}],
+
+	['validate-required-check', {
+		errorMsg: function(element, props){
+			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+		},
+		test: function(element, props){
+			return !!element.checked;
+		}
+	}],
+
+	['validate-reqchk-byname', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+		},
+		test: function(element, props){
+			var grpName = props.groupName || element.get('name');
+			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+				return item.checked;
+			});
+			var fv = element.getParent('form').retrieve('validator');
+			if (oneCheckedItem && fv) fv.resetField(element);
+			return oneCheckedItem;
+		}
+	}],
+
+	['validate-match', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+		},
+		test: function(element, props){
+			var eleVal = element.get('value');
+			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+			return eleVal && matchVal ? eleVal == matchVal : true;
+		}
+	}],
+
+	['validate-after-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('afterDate').substitute({
+				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+			var end = Date.parse(element.get('value'));
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-before-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('beforeDate').substitute({
+				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = Date.parse(element.get('value'));
+			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-custom-required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element, props){
+			return element.get('value') != props.emptyValue;
+		}
+	}],
+
+	['validate-same-month', {
+		errorMsg: function(element, props){
+			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+			var eleVal = element.get('value');
+			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+		},
+		test: function(element, props){
+			var d1 = Date.parse(element.get('value'));
+			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+		}
+	}],
+
+
+	['validate-cc-num', {
+		errorMsg: function(element){
+			var ccNum = element.get('value').replace(/[^0-9]/g, '');
+			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+		},
+		test: function(element){
+			// required is a different test
+			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }
+
+			// Clean number value
+			var ccNum = element.get('value');
+			ccNum = ccNum.replace(/[^0-9]/g, '');
+
+			var valid_type = false;
+
+			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
+
+			if (valid_type) {
+				var sum = 0;
+				var cur = 0;
+
+				for(var i=ccNum.length-1; i>=0; --i) {
+					cur = ccNum.charAt(i).toInt();
+					if (cur == 0) { continue; }
+
+					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
+					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }
+
+					sum += cur;
+				}
+				if ((sum % 10) == 0) { return true; }
+			}
+
+			var chunks = '';
+			while (ccNum != '') {
+				chunks += ' ' + ccNum.substr(0,4);
+				ccNum = ccNum.substr(4);
+			}
+
+			element.getParent('form').retrieve('validator').ignoreField(element);
+			element.set('value', chunks.clean());
+			element.getParent('form').retrieve('validator').enforceField(element);
+			return false;
+		}
+	}]
+
+
+]);/*
+---
+
+script: OverText.js
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- /Class.Binds
+- /Class.Occlude
+- /Element.Position
+- /Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+	options: {/*
+		textOverride: null,
+		onFocus: $empty()
+		onTextHide: $empty(textEl, inputEl),
+		onTextShow: $empty(textEl, inputEl), */
+		element: 'label',
+		positionOptions: {
+			position: 'upperLeft',
+			edge: 'upperLeft',
+			offset: {
+				x: 4,
+				y: 2
+			}
+		},
+		poll: false,
+		pollInterval: 250,
+		wrap: false
+	},
+
+	property: 'OverText',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.attach(this.element);
+		OverText.instances.push(this);
+		if (this.options.poll) this.poll();
+		return this;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	attach: function(){
+		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
+		if (!val) return;
+		this.text = new Element(this.options.element, {
+			'class': 'overTxtLabel',
+			styles: {
+				lineHeight: 'normal',
+				position: 'absolute',
+				cursor: 'text'
+			},
+			html: val,
+			events: {
+				click: this.hide.pass(this.options.element == 'label', this)
+			}
+		}).inject(this.element, 'after');
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+
+		if (this.options.wrap) {
+			this.textHolder = new Element('div', {
+				styles: {
+					lineHeight: 'normal',
+					position: 'relative'
+				},
+				'class':'overTxtWrapper'
+			}).adopt(this.text).inject(this.element, 'before');
+		}
+
+		this.element.addEvents({
+			focus: this.focus,
+			blur: this.assert,
+			change: this.assert
+		}).store('OverTextDiv', this.text);
+		window.addEvent('resize', this.reposition.bind(this));
+		this.assert(true);
+		this.reposition();
+	},
+
+	wrap: function(){
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+	},
+
+	startPolling: function(){
+		this.pollingPaused = false;
+		return this.poll();
+	},
+
+	poll: function(stop){
+		//start immediately
+		//pause on focus
+		//resumeon blur
+		if (this.poller && !stop) return this;
+		var test = function(){
+			if (!this.pollingPaused) this.assert(true);
+		}.bind(this);
+		if (stop) $clear(this.poller);
+		else this.poller = test.periodical(this.options.pollInterval, this);
+		return this;
+	},
+
+	stopPolling: function(){
+		this.pollingPaused = true;
+		return this.poll(true);
+	},
+
+	focus: function(){
+		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
+		this.hide();
+	},
+
+	hide: function(suppressFocus, force){
+		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+			this.text.hide();
+			this.fireEvent('textHide', [this.text, this.element]);
+			this.pollingPaused = true;
+			if (!suppressFocus){
+				try {
+					this.element.fireEvent('focus');
+					this.element.focus();
+				} catch(e){} //IE barfs if you call focus on hidden elements
+			}
+		}
+		return this;
+	},
+
+	show: function(){
+		if (this.text && !this.text.isDisplayed()){
+			this.text.show();
+			this.reposition();
+			this.fireEvent('textShow', [this.text, this.element]);
+			this.pollingPaused = false;
+		}
+		return this;
+	},
+
+	assert: function(suppressFocus){
+		this[this.test() ? 'show' : 'hide'](suppressFocus);
+	},
+
+	test: function(){
+		var v = this.element.get('value');
+		return !v;
+	},
+
+	reposition: function(){
+		this.assert(true);
+		if (!this.element.isVisible()) return this.stopPolling().hide();
+		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
+		return this;
+	}
+
+});
+
+OverText.instances = [];
+
+$extend(OverText, {
+
+	each: function(fn) {
+		return OverText.instances.map(function(ot, i){
+			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
+			return null; //the input or the text was destroyed
+		});
+	},
+	
+	update: function(){
+
+		return OverText.each(function(ot){
+			return ot.reposition();
+		});
+
+	},
+
+	hideAll: function(){
+
+		return OverText.each(function(ot){
+			return ot.hide(true, true);
+		});
+
+	},
+
+	showAll: function(){
+		return OverText.each(function(ot) {
+			return ot.show();
+		});
+	}
+
+});
+
+if (window.Fx && Fx.Reveal) {
+	Fx.Reveal.implement({
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
+	});
+}/*
+---
+
+script: Fx.Elements.js
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx.CSS
+- /MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(elements, options){
+		this.elements = this.subject = $$(elements);
+		this.parent(options);
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var i in from){
+			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+		}
+		return now;
+	},
+
+	set: function(now){
+		for (var i in now){
+			var iNow = now[i];
+			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+		}
+		return this;
+	},
+
+	start: function(obj){
+		if (!this.check(obj)) return this;
+		var from = {}, to = {};
+		for (var i in obj){
+			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+			for (var p in iProps){
+				var parsed = this.prepare(this.elements[i], p, iProps[p]);
+				iFrom[p] = parsed.from;
+				iTo[p] = parsed.to;
+			}
+		}
+		return this.parent(from, to);
+	}
+
+});/*
+---
+
+script: Fx.Accordion.js
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Event
+- /Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {/*
+		onActive: $empty(toggler, section),
+		onBackground: $empty(toggler, section),
+		fixedHeight: false,
+		fixedWidth: false,
+		*/
+		display: 0,
+		show: false,
+		height: true,
+		width: false,
+		opacity: true,
+		alwaysHide: false,
+		trigger: 'click',
+		initialDisplayFx: true,
+		returnHeightToAuto: true
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {
+			'container': Element.type, //deprecated
+			'options': Object.type,
+			'togglers': $defined,
+			'elements': $defined
+		});
+		this.parent(params.elements, params.options);
+		this.togglers = $$(params.togglers);
+		this.previous = -1;
+		this.internalChain = new Chain();
+		if (this.options.alwaysHide) this.options.wait = true;
+		if ($chk(this.options.show)){
+			this.options.display = false;
+			this.previous = this.options.show;
+		}
+		if (this.options.start){
+			this.options.display = false;
+			this.options.show = false;
+		}
+		this.effects = {};
+		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
+		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
+		this.elements.each(function(el, i){
+			if (this.options.show === i){
+				this.fireEvent('active', [this.togglers[i], el]);
+			} else {
+				for (var fx in this.effects) el.setStyle(fx, 0);
+			}
+		}, this);
+		if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
+		if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
+		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+	},
+
+	addSection: function(toggler, element){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		this.togglers.include(toggler);
+		this.elements.include(element);
+		var idx = this.togglers.indexOf(toggler);
+		var displayer = this.display.bind(this, idx);
+		toggler.store('accordion:display', displayer);
+		toggler.addEvent(this.options.trigger, displayer);
+		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+		element.fullOpacity = 1;
+		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
+		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
+		element.setStyle('overflow', 'hidden');
+		if (!test){
+			for (var fx in this.effects) element.setStyle(fx, 0);
+		}
+		return this;
+	},
+
+	detach: function(){
+		this.togglers.each(function(toggler) {
+			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+		}, this);
+	},
+
+	display: function(index, useFx){
+		if (!this.check(index, useFx)) return this;
+		useFx = $pick(useFx, true);
+		if (this.options.returnHeightToAuto){
+			var prev = this.elements[this.previous];
+			if (prev && !this.selfHidden){
+				for (var fx in this.effects){
+					prev.setStyle(fx, prev[this.effects[fx]]);
+				}
+			}
+		}
+		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
+		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
+		this.previous = index;
+		var obj = {};
+		this.elements.each(function(el, i){
+			obj[i] = {};
+			var hide;
+			if (i != index){
+				hide = true;
+			} else if (this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0 && this.options.width)){
+				hide = true;
+				this.selfHidden = true;
+			}
+			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
+		}, this);
+		this.internalChain.chain(function(){
+			if (this.options.returnHeightToAuto && !this.selfHidden){
+				var el = this.elements[index];
+				if (el) el.setStyle('height', 'auto');
+			};
+		}.bind(this));
+		return useFx ? this.start(obj) : this.set(obj);
+	}
+
+});
+
+/*
+	Compatibility with 1.2.0
+*/
+var Accordion = new Class({
+
+	Extends: Fx.Accordion,
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		var params = Array.link(arguments, {'container': Element.type});
+		this.container = params.container;
+	},
+
+	addSection: function(toggler, element, pos){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		var len = this.togglers.length;
+		if (len && (!test || pos)){
+			pos = $pick(pos, len - 1);
+			toggler.inject(this.togglers[pos], 'before');
+			element.inject(toggler, 'after');
+		} else if (this.container && !test){
+			toggler.inject(this.container);
+			element.inject(this.container);
+		}
+		return this.parent.apply(this, arguments);
+	}
+
+});/*
+---
+
+script: Fx.Move.js
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Fx.Morph
+- /Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {
+		relativeTo: document.body,
+		position: 'center',
+		edge: false,
+		offset: {x: 0, y: 0}
+	},
+
+	start: function(destination){
+		return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
+	}
+
+});
+
+Element.Properties.move = {
+
+	set: function(options){
+		var morph = this.retrieve('move');
+		if (morph) morph.cancel();
+		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('move')){
+			if (options || !this.retrieve('move:options')) this.set('move', options);
+			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
+		}
+		return this.retrieve('move');
+	}
+
+};
+
+Element.implement({
+
+	move: function(options){
+		this.get('move').start(options);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Scroll.js
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+Fx.Scroll = new Class({
+
+	Extends: Fx,
+
+	options: {
+		offset: {x: 0, y: 0},
+		wheelStops: true
+	},
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var cancel = this.cancel.bind(this, false);
+
+		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+		var stopper = this.element;
+
+		if (this.options.wheelStops){
+			this.addEvent('start', function(){
+				stopper.addEvent('mousewheel', cancel);
+			}, true);
+			this.addEvent('complete', function(){
+				stopper.removeEvent('mousewheel', cancel);
+			}, true);
+		}
+	},
+
+	set: function(){
+		var now = Array.flatten(arguments);
+		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
+		this.element.scrollTo(now[0], now[1]);
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(x, y){
+		if (!this.check(x, y)) return this;
+		var scrollSize = this.element.getScrollSize(),
+			scroll = this.element.getScroll(), 
+			values = {x: x, y: y};
+		for (var z in values){
+			var max = scrollSize[z];
+			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
+			else values[z] = scroll[z];
+			values[z] += this.options.offset[z];
+		}
+		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
+	},
+
+	toTop: function(){
+		return this.start(false, 0);
+	},
+
+	toLeft: function(){
+		return this.start(0, false);
+	},
+
+	toRight: function(){
+		return this.start('right', false);
+	},
+
+	toBottom: function(){
+		return this.start(false, 'bottom');
+	},
+
+	toElement: function(el){
+		var position = document.id(el).getPosition(this.element);
+		return this.start(position.x, position.y);
+	},
+
+	scrollIntoView: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x','y'];
+		var to = {};
+		el = document.id(el);
+		var pos = el.getPosition(this.element);
+		var size = el.getSize();
+		var scroll = this.element.getScroll();
+		var containerSize = this.element.getSize();
+		var edge = {
+			x: pos.x + size.x,
+			y: pos.y + size.y
+		};
+		['x','y'].each(function(axis) {
+			if (axes.contains(axis)) {
+				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
+			}
+			if (to[axis] == null) to[axis] = scroll[axis];
+			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	},
+
+	scrollToCenter: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x', 'y'];
+		el = $(el);
+		var to = {},
+			pos = el.getPosition(this.element),
+			size = el.getSize(),
+			scroll = this.element.getScroll(),
+			containerSize = this.element.getSize(),
+			edge = {
+				x: pos.x + size.x,
+				y: pos.y + size.y
+			};
+
+		['x','y'].each(function(axis){
+			if(axes.contains(axis)){
+				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
+			}
+			if(to[axis] == null) to[axis] = scroll[axis];
+			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Slide.js
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Fx Element.Style
+- /MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+	Extends: Fx,
+
+	options: {
+		mode: 'vertical',
+		wrapper: false,
+		hideOverflow: true
+	},
+
+	initialize: function(element, options){
+		this.addEvent('complete', function(){
+			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
+			if (this.open) this.wrapper.setStyle('height', '');
+			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
+		}, true);
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var wrapper = this.element.retrieve('wrapper');
+		var styles = this.element.getStyles('margin', 'position', 'overflow');
+		if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
+		if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
+		this.wrapper = wrapper || new Element('div', {
+			styles: styles
+		}).wraps(this.element);
+		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
+		this.now = [];
+		this.open = true;
+	},
+
+	vertical: function(){
+		this.margin = 'margin-top';
+		this.layout = 'height';
+		this.offset = this.element.offsetHeight;
+	},
+
+	horizontal: function(){
+		this.margin = 'margin-left';
+		this.layout = 'width';
+		this.offset = this.element.offsetWidth;
+	},
+
+	set: function(now){
+		this.element.setStyle(this.margin, now[0]);
+		this.wrapper.setStyle(this.layout, now[1]);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(how, mode){
+		if (!this.check(how, mode)) return this;
+		this[mode || this.options.mode]();
+		var margin = this.element.getStyle(this.margin).toInt();
+		var layout = this.wrapper.getStyle(this.layout).toInt();
+		var caseIn = [[margin, layout], [0, this.offset]];
+		var caseOut = [[margin, layout], [-this.offset, 0]];
+		var start;
+		switch (how){
+			case 'in': start = caseIn; break;
+			case 'out': start = caseOut; break;
+			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+		}
+		return this.parent(start[0], start[1]);
+	},
+
+	slideIn: function(mode){
+		return this.start('in', mode);
+	},
+
+	slideOut: function(mode){
+		return this.start('out', mode);
+	},
+
+	hide: function(mode){
+		this[mode || this.options.mode]();
+		this.open = false;
+		return this.set([-this.offset, 0]);
+	},
+
+	show: function(mode){
+		this[mode || this.options.mode]();
+		this.open = true;
+		return this.set([0, this.offset]);
+	},
+
+	toggle: function(mode){
+		return this.start('toggle', mode);
+	}
+
+});
+
+Element.Properties.slide = {
+
+	set: function(options){
+		var slide = this.retrieve('slide');
+		if (slide) slide.cancel();
+		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('slide')){
+			if (options || !this.retrieve('slide:options')) this.set('slide', options);
+			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
+		}
+		return this.retrieve('slide');
+	}
+
+};
+
+Element.implement({
+
+	slide: function(how, mode){
+		how = how || 'toggle';
+		var slide = this.get('slide'), toggle;
+		switch (how){
+			case 'hide': slide.hide(mode); break;
+			case 'show': slide.show(mode); break;
+			case 'toggle':
+				var flag = this.retrieve('slide:flag', slide.open);
+				slide[flag ? 'slideOut' : 'slideIn'](mode);
+				this.store('slide:flag', !flag);
+				toggle = true;
+			break;
+			default: slide.start(how, mode);
+		}
+		if (!toggle) this.eliminate('slide:flag');
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Selectors
+- /Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+var SmoothScroll = Fx.SmoothScroll = new Class({
+
+	Extends: Fx.Scroll,
+
+	initialize: function(options, context){
+		context = context || document;
+		this.doc = context.getDocument();
+		var win = context.getWindow();
+		this.parent(this.doc, options);
+		this.links = $$(this.options.links || this.doc.links);
+		var location = win.location.href.match(/^[^#]*/)[0] + '#';
+		this.links.each(function(link){
+			if (link.href.indexOf(location) != 0) {return;}
+			var anchor = link.href.substr(location.length);
+			if (anchor) this.useLink(link, anchor);
+		}, this);
+		if (!Browser.Engine.webkit419) {
+			this.addEvent('complete', function(){
+				win.location.hash = this.anchor;
+			}, true);
+		}
+	},
+
+	useLink: function(link, anchor){
+		var el;
+		link.addEvent('click', function(event){
+			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+			if (el) {
+				event.preventDefault();
+				this.anchor = anchor;
+				this.toElement(el).chain(function(){
+					this.fireEvent('scrolledTo', [link, el]);
+				}.bind(this));
+				link.blur();
+			}
+		}.bind(this));
+	}
+});/*
+---
+
+script: Fx.Sort.js
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Fx.Elements
+- /Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {
+		mode: 'vertical'
+	},
+
+	initialize: function(elements, options){
+		this.parent(elements, options);
+		this.elements.each(function(el){
+			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+		});
+		this.setDefaultOrder();
+	},
+
+	setDefaultOrder: function(){
+		this.currentOrder = this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	sort: function(newOrder){
+		if ($type(newOrder) != 'array') return false;
+		var top = 0,
+			left = 0,
+			next = {},
+			zero = {},
+			vert = this.options.mode == 'vertical';
+		var current = this.elements.map(function(el, index){
+			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+			var val;
+			if (vert){
+				val = {
+					top: top,
+					margin: size['margin-top'],
+					height: size.totalHeight
+				};
+				top += val.height - size['margin-top'];
+			} else {
+				val = {
+					left: left,
+					margin: size['margin-left'],
+					width: size.totalWidth
+				};
+				left += val.width;
+			}
+			var plain = vert ? 'top' : 'left';
+			zero[index] = {};
+			var start = el.getStyle(plain).toInt();
+			zero[index][plain] = start || 0;
+			return val;
+		}, this);
+		this.set(zero);
+		newOrder = newOrder.map(function(i){ return i.toInt(); });
+		if (newOrder.length != this.elements.length){
+			this.currentOrder.each(function(index){
+				if (!newOrder.contains(index)) newOrder.push(index);
+			});
+			if (newOrder.length > this.elements.length)
+				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+		}
+		var margin = top = left = 0;
+		newOrder.each(function(item, index){
+			var newPos = {};
+			if (vert){
+				newPos.top = top - current[item].top - margin;
+				top += current[item].height;
+			} else {
+				newPos.left = left - current[item].left;
+				left += current[item].width;
+			}
+			margin = margin + current[item].margin;
+			next[item]=newPos;
+		}, this);
+		var mapped = {};
+		$A(newOrder).sort().each(function(index){
+			mapped[index] = next[index];
+		});
+		this.start(mapped);
+		this.currentOrder = newOrder;
+		return this;
+	},
+
+	rearrangeDOM: function(newOrder){
+		newOrder = newOrder || this.currentOrder;
+		var parent = this.elements[0].getParent();
+		var rearranged = [];
+		this.elements.setStyle('opacity', 0);
+		//move each element and store the new default order
+		newOrder.each(function(index){
+			rearranged.push(this.elements[index].inject(parent).setStyles({
+				top: 0,
+				left: 0
+			}));
+		}, this);
+		this.elements.setStyle('opacity', 1);
+		this.elements = $$(rearranged);
+		this.setDefaultOrder();
+		return this;
+	},
+
+	getDefaultOrder: function(){
+		return this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	forward: function(){
+		return this.sort(this.getDefaultOrder());
+	},
+
+	backward: function(){
+		return this.sort(this.getDefaultOrder().reverse());
+	},
+
+	reverse: function(){
+		return this.sort(this.currentOrder.reverse());
+	},
+
+	sortByElements: function(elements){
+		return this.sort(elements.map(function(el){
+			return this.elements.indexOf(el);
+		}, this));
+	},
+
+	swap: function(one, two){
+		if ($type(one) == 'element') one = this.elements.indexOf(one);
+		if ($type(two) == 'element') two = this.elements.indexOf(two);
+		
+		var newOrder = $A(this.currentOrder);
+		newOrder[this.currentOrder.indexOf(one)] = two;
+		newOrder[this.currentOrder.indexOf(two)] = one;
+		return this.sort(newOrder);
+	}
+
+});/*
+---
+
+script: Drag.js
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Tom Occhinno
+- Jan Kassens
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- /MooTools.More
+
+provides: [Drag]
+
+*/
+
+var Drag = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onBeforeStart: $empty(thisElement),
+		onStart: $empty(thisElement, event),
+		onSnap: $empty(thisElement)
+		onDrag: $empty(thisElement, event),
+		onCancel: $empty(thisElement),
+		onComplete: $empty(thisElement, event),*/
+		snap: 6,
+		unit: 'px',
+		grid: false,
+		style: true,
+		limit: false,
+		handle: false,
+		invert: false,
+		preventDefault: false,
+		stopPropagation: false,
+		modifiers: {x: 'left', y: 'top'}
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
+		this.element = document.id(params.element);
+		this.document = this.element.getDocument();
+		this.setOptions(params.options || {});
+		var htype = $type(this.options.handle);
+		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+		this.mouse = {'now': {}, 'pos': {}};
+		this.value = {'start': {}, 'now': {}};
+
+		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
+
+		this.bound = {
+			start: this.start.bind(this),
+			check: this.check.bind(this),
+			drag: this.drag.bind(this),
+			stop: this.stop.bind(this),
+			cancel: this.cancel.bind(this),
+			eventStop: $lambda(false)
+		};
+		this.attach();
+	},
+
+	attach: function(){
+		this.handles.addEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	detach: function(){
+		this.handles.removeEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	start: function(event){
+		if (event.rightClick) return;
+		if (this.options.preventDefault) event.preventDefault();
+		if (this.options.stopPropagation) event.stopPropagation();
+		this.mouse.start = event.page;
+		this.fireEvent('beforeStart', this.element);
+		var limit = this.options.limit;
+		this.limit = {x: [], y: []};
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
+			else this.value.now[z] = this.element[this.options.modifiers[z]];
+			if (this.options.invert) this.value.now[z] *= -1;
+			this.mouse.pos[z] = event.page[z] - this.value.now[z];
+			if (limit && limit[z]){
+				for (var i = 2; i--; i){
+					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
+				}
+			}
+		}
+		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
+		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
+		this.document.addEvent(this.selection, this.bound.eventStop);
+	},
+
+	check: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+		if (distance > this.options.snap){
+			this.cancel();
+			this.document.addEvents({
+				mousemove: this.bound.drag,
+				mouseup: this.bound.stop
+			});
+			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+		}
+	},
+
+	drag: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		this.mouse.now = event.page;
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+			if (this.options.invert) this.value.now[z] *= -1;
+			if (this.options.limit && this.limit[z]){
+				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
+					this.value.now[z] = this.limit[z][1];
+				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
+					this.value.now[z] = this.limit[z][0];
+				}
+			}
+			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
+			if (this.options.style) {
+				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
+			} else {
+				this.element[this.options.modifiers[z]] = this.value.now[z];
+			}
+		}
+		this.fireEvent('drag', [this.element, event]);
+	},
+
+	cancel: function(event){
+		this.document.removeEvent('mousemove', this.bound.check);
+		this.document.removeEvent('mouseup', this.bound.cancel);
+		if (event){
+			this.document.removeEvent(this.selection, this.bound.eventStop);
+			this.fireEvent('cancel', this.element);
+		}
+	},
+
+	stop: function(event){
+		this.document.removeEvent(this.selection, this.bound.eventStop);
+		this.document.removeEvent('mousemove', this.bound.drag);
+		this.document.removeEvent('mouseup', this.bound.stop);
+		if (event) this.fireEvent('complete', [this.element, event]);
+	}
+
+});
+
+Element.implement({
+
+	makeResizable: function(options){
+		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
+		this.store('resizer', drag);
+		return drag.addEvent('drag', function(){
+			this.fireEvent('resize', drag);
+		}.bind(this));
+	}
+
+});
+/*
+---
+
+script: Drag.Move.js
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Tom Occhinno
+- Jan Kassens
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+	Extends: Drag,
+
+	options: {/*
+		onEnter: $empty(thisElement, overed),
+		onLeave: $empty(thisElement, overed),
+		onDrop: $empty(thisElement, overed, event),*/
+		droppables: [],
+		container: false,
+		precalculate: false,
+		includeMargins: true,
+		checkDroppables: true
+	},
+
+	initialize: function(element, options){
+		this.parent(element, options);
+		element = this.element;
+		
+		this.droppables = $$(this.options.droppables);
+		this.container = document.id(this.options.container);
+		
+		if (this.container && $type(this.container) != 'element')
+			this.container = document.id(this.container.getDocument().body);
+		
+		var styles = element.getStyles('left', 'top', 'position');
+		if (styles.left == 'auto' || styles.top == 'auto')
+			element.setPosition(element.getPosition(element.getOffsetParent()));
+		
+		if (styles.position == 'static')
+			element.setStyle('position', 'absolute');
+
+		this.addEvent('start', this.checkDroppables, true);
+
+		this.overed = null;
+	},
+
+	start: function(event){
+		if (this.container) this.options.limit = this.calculateLimit();
+		
+		if (this.options.precalculate){
+			this.positions = this.droppables.map(function(el){
+				return el.getCoordinates();
+			});
+		}
+		
+		this.parent(event);
+	},
+	
+	calculateLimit: function(){
+		var offsetParent = this.element.getOffsetParent(),
+			containerCoordinates = this.container.getCoordinates(offsetParent),
+			containerBorder = {},
+			elementMargin = {},
+			elementBorder = {},
+			containerMargin = {},
+			offsetParentPadding = {};
+
+		['top', 'right', 'bottom', 'left'].each(function(pad){
+			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
+			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
+			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
+			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
+			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+		}, this);
+
+		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
+			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
+			left = 0,
+			top = 0,
+			right = containerCoordinates.right - containerBorder.right - width,
+			bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+		if (this.options.includeMargins){
+			left += elementMargin.left;
+			top += elementMargin.top;
+		} else {
+			right += elementMargin.right;
+			bottom += elementMargin.bottom;
+		}
+		
+		if (this.element.getStyle('position') == 'relative'){
+			var coords = this.element.getCoordinates(offsetParent);
+			coords.left -= this.element.getStyle('left').toInt();
+			coords.top -= this.element.getStyle('top').toInt();
+			
+			left += containerBorder.left - coords.left;
+			top += containerBorder.top - coords.top;
+			right += elementMargin.left - coords.left;
+			bottom += elementMargin.top - coords.top;
+			
+			if (this.container != offsetParent){
+				left += containerMargin.left + offsetParentPadding.left;
+				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
+			}
+		} else {
+			left -= elementMargin.left;
+			top -= elementMargin.top;
+			
+			if (this.container == offsetParent){
+				right -= containerBorder.left;
+				bottom -= containerBorder.top;
+			} else {
+				left += containerCoordinates.left + containerBorder.left;
+				top += containerCoordinates.top + containerBorder.top;
+			}
+		}
+		
+		return {
+			x: [left, right],
+			y: [top, bottom]
+		};
+	},
+
+	checkAgainst: function(el, i){
+		el = (this.positions) ? this.positions[i] : el.getCoordinates();
+		var now = this.mouse.now;
+		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+	},
+
+	checkDroppables: function(){
+		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
+		if (this.overed != overed){
+			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+			if (overed) this.fireEvent('enter', [this.element, overed]);
+			this.overed = overed;
+		}
+	},
+
+	drag: function(event){
+		this.parent(event);
+		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+	},
+
+	stop: function(event){
+		this.checkDroppables();
+		this.fireEvent('drop', [this.element, this.overed, event]);
+		this.overed = null;
+		return this.parent(event);
+	}
+
+});
+
+Element.implement({
+
+	makeDraggable: function(options){
+		var drag = new Drag.Move(this, options);
+		this.store('dragger', drag);
+		return drag;
+	}
+
+});
+/*
+---
+
+script: Slider.js
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Dimensions
+- /Class.Binds
+- /Drag
+- /Element.Dimensions
+- /Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+	Implements: [Events, Options],
+
+	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+	options: {/*
+		onTick: $empty(intPosition),
+		onChange: $empty(intStep),
+		onComplete: $empty(strStep),*/
+		onTick: function(position){
+			if (this.options.snap) position = this.toPosition(this.step);
+			this.knob.setStyle(this.property, position);
+		},
+		initialStep: 0,
+		snap: false,
+		offset: 0,
+		range: false,
+		wheel: false,
+		steps: 100,
+		mode: 'horizontal'
+	},
+
+	initialize: function(element, knob, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.knob = document.id(knob);
+		this.previousChange = this.previousEnd = this.step = -1;
+		var offset, limit = {}, modifiers = {'x': false, 'y': false};
+		switch (this.options.mode){
+			case 'vertical':
+				this.axis = 'y';
+				this.property = 'top';
+				offset = 'offsetHeight';
+				break;
+			case 'horizontal':
+				this.axis = 'x';
+				this.property = 'left';
+				offset = 'offsetWidth';
+		}
+		
+		this.full = this.element.measure(function(){ 
+			this.half = this.knob[offset] / 2; 
+			return this.element[offset] - this.knob[offset] + (this.options.offset * 2); 
+		}.bind(this));
+		
+		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
+		this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
+		this.range = this.max - this.min;
+		this.steps = this.options.steps || this.full;
+		this.stepSize = Math.abs(this.range) / this.steps;
+		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
+
+		this.knob.setStyle('position', 'relative').setStyle(this.property, this.options.initialStep ? this.toPosition(this.options.initialStep) : - this.options.offset);
+		modifiers[this.axis] = this.property;
+		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];
+
+		var dragOptions = {
+			snap: 0,
+			limit: limit,
+			modifiers: modifiers,
+			onDrag: this.draggedKnob,
+			onStart: this.draggedKnob,
+			onBeforeStart: (function(){
+				this.isDragging = true;
+			}).bind(this),
+			onCancel: function() {
+				this.isDragging = false;
+			}.bind(this),
+			onComplete: function(){
+				this.isDragging = false;
+				this.draggedKnob();
+				this.end();
+			}.bind(this)
+		};
+		if (this.options.snap){
+			dragOptions.grid = Math.ceil(this.stepWidth);
+			dragOptions.limit[this.axis][1] = this.full;
+		}
+
+		this.drag = new Drag(this.knob, dragOptions);
+		this.attach();
+	},
+
+	attach: function(){
+		this.element.addEvent('mousedown', this.clickedElement);
+		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+		this.drag.attach();
+		return this;
+	},
+
+	detach: function(){
+		this.element.removeEvent('mousedown', this.clickedElement);
+		this.element.removeEvent('mousewheel', this.scrolledElement);
+		this.drag.detach();
+		return this;
+	},
+
+	set: function(step){
+		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+		if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+		this.step = Math.round(step);
+		this.checkStep();
+		this.fireEvent('tick', this.toPosition(this.step));
+		this.end();
+		return this;
+	},
+
+	clickedElement: function(event){
+		if (this.isDragging || event.target == this.knob) return;
+
+		var dir = this.range < 0 ? -1 : 1;
+		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+		this.fireEvent('tick', position);
+		this.end();
+	},
+
+	scrolledElement: function(event){
+		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
+		event.stop();
+	},
+
+	draggedKnob: function(){
+		var dir = this.range < 0 ? -1 : 1;
+		var position = this.drag.value.now[this.axis];
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+	},
+
+	checkStep: function(){
+		if (this.previousChange != this.step){
+			this.previousChange = this.step;
+			this.fireEvent('change', this.step);
+		}
+	},
+
+	end: function(){
+		if (this.previousEnd !== this.step){
+			this.previousEnd = this.step;
+			this.fireEvent('complete', this.step + '');
+		}
+	},
+
+	toStep: function(position){
+		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
+	},
+
+	toPosition: function(step){
+		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
+	}
+
+});/*
+---
+
+script: Sortables.js
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+- Tom Occhino
+
+requires:
+- /Drag.Move
+
+provides: [Slider]
+
+...
+*/
+
+var Sortables = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onSort: $empty(element, clone),
+		onStart: $empty(element, clone),
+		onComplete: $empty(element),*/
+		snap: 4,
+		opacity: 1,
+		clone: false,
+		revert: false,
+		handle: false,
+		constrain: false
+	},
+
+	initialize: function(lists, options){
+		this.setOptions(options);
+		this.elements = [];
+		this.lists = [];
+		this.idle = true;
+
+		this.addLists($$(document.id(lists) || lists));
+		if (!this.options.clone) this.options.revert = false;
+		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
+	},
+
+	attach: function(){
+		this.addLists(this.lists);
+		return this;
+	},
+
+	detach: function(){
+		this.lists = this.removeLists(this.lists);
+		return this;
+	},
+
+	addItems: function(){
+		Array.flatten(arguments).each(function(element){
+			this.elements.push(element);
+			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+		}, this);
+		return this;
+	},
+
+	addLists: function(){
+		Array.flatten(arguments).each(function(list){
+			this.lists.push(list);
+			this.addItems(list.getChildren());
+		}, this);
+		return this;
+	},
+
+	removeItems: function(){
+		return $$(Array.flatten(arguments).map(function(element){
+			this.elements.erase(element);
+			var start = element.retrieve('sortables:start');
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+			
+			return element;
+		}, this));
+	},
+
+	removeLists: function(){
+		return $$(Array.flatten(arguments).map(function(list){
+			this.lists.erase(list);
+			this.removeItems(list.getChildren());
+			
+			return list;
+		}, this));
+	},
+
+	getClone: function(event, element){
+		if (!this.options.clone) return new Element('div').inject(document.body);
+		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+		var clone = element.clone(true).setStyles({
+			margin: '0px',
+			position: 'absolute',
+			visibility: 'hidden',
+			'width': element.getStyle('width')
+		});
+		//prevent the duplicated radio inputs from unchecking the real one
+		if (clone.get('html').test('radio')) {
+			clone.getElements('input[type=radio]').each(function(input, i) {
+				input.set('name', 'clone_' + i);
+			});
+		}
+		
+		return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
+	},
+
+	getDroppables: function(){
+		var droppables = this.list.getChildren();
+		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
+		return droppables.erase(this.clone).erase(this.element);
+	},
+
+	insert: function(dragging, element){
+		var where = 'inside';
+		if (this.lists.contains(element)){
+			this.list = element;
+			this.drag.droppables = this.getDroppables();
+		} else {
+			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+		}
+		this.element.inject(element, where);
+		this.fireEvent('sort', [this.element, this.clone]);
+	},
+
+	start: function(event, element){
+		if (!this.idle) return;
+		this.idle = false;
+		this.element = element;
+		this.opacity = element.get('opacity');
+		this.list = element.getParent();
+		this.clone = this.getClone(event, element);
+
+		this.drag = new Drag.Move(this.clone, {
+			snap: this.options.snap,
+			container: this.options.constrain && this.element.getParent(),
+			droppables: this.getDroppables(),
+			onSnap: function(){
+				event.stop();
+				this.clone.setStyle('visibility', 'visible');
+				this.element.set('opacity', this.options.opacity || 0);
+				this.fireEvent('start', [this.element, this.clone]);
+			}.bind(this),
+			onEnter: this.insert.bind(this),
+			onCancel: this.reset.bind(this),
+			onComplete: this.end.bind(this)
+		});
+
+		this.clone.inject(this.element, 'before');
+		this.drag.start(event);
+	},
+
+	end: function(){
+		this.drag.detach();
+		this.element.set('opacity', this.opacity);
+		if (this.effect){
+			var dim = this.element.getStyles('width', 'height');
+			var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
+			this.effect.element = this.clone;
+			this.effect.start({
+				top: pos.top,
+				left: pos.left,
+				width: dim.width,
+				height: dim.height,
+				opacity: 0.25
+			}).chain(this.reset.bind(this));
+		} else {
+			this.reset();
+		}
+	},
+
+	reset: function(){
+		this.idle = true;
+		this.clone.destroy();
+		this.fireEvent('complete', this.element);
+	},
+
+	serialize: function(){
+		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
+		var serial = this.lists.map(function(list){
+			return list.getChildren().map(params.modifier || function(element){
+				return element.get('id');
+			}, this);
+		}, this);
+
+		var index = params.index;
+		if (this.lists.length == 1) index = 0;
+		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+	}
+
+});
+/*
+---
+
+script: Request.JSONP.js
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+- Guillermo Rauch
+
+requires:
+- core:1.2.4/Element
+- core:1.2.4/Request
+- /Log
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+	Implements: [Chain, Events, Options, Log],
+
+	options: {/*
+		onRetry: $empty(intRetries),
+		onRequest: $empty(scriptElement),
+		onComplete: $empty(data),
+		onSuccess: $empty(data),
+		onCancel: $empty(),
+		log: false,
+		*/
+		url: '',
+		data: {},
+		retries: 0,
+		timeout: 0,
+		link: 'ignore',
+		callbackKey: 'callback',
+		injectScript: document.head
+	},
+
+	initialize: function(options){
+		this.setOptions(options);
+		if (this.options.log) this.enableLog();
+		this.running = false;
+		this.requests = 0;
+		this.triesRemaining = [];
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!$chk(arguments[1]) && !this.check(options)) return this;
+
+		var type = $type(options), 
+				old = this.options, 
+				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		options = $extend({data: old.data, url: old.url}, options);
+
+		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
+		var remaining = this.triesRemaining[index];
+
+		(function(){
+			var script = this.getScript(options);
+			this.log('JSONP retrieving script with url: ' + script.get('src'));
+			this.fireEvent('request', script);
+			this.running = true;
+
+			(function(){
+				if (remaining){
+					this.triesRemaining[index] = remaining - 1;
+					if (script){
+						script.destroy();
+						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
+					}
+				} else if(script && this.options.timeout){
+					script.destroy();
+					this.cancel().fireEvent('failure');
+				}
+			}).delay(this.options.timeout, this);
+		}).delay(Browser.Engine.trident ? 50 : 0, this);
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.fireEvent('cancel');
+		return this;
+	},
+
+	getScript: function(options){
+		var index = Request.JSONP.counter,
+				data;
+		Request.JSONP.counter++;
+
+		switch ($type(options.data)){
+			case 'element': data = document.id(options.data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(options.data);
+		}
+
+		var src = options.url + 
+			 (options.url.test('\\?') ? '&' :'?') + 
+			 (options.callbackKey || this.options.callbackKey) + 
+			 '=Request.JSONP.request_map.request_'+ index + 
+			 (data ? '&' + data : '');
+		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+
+		var script = new Element('script', {type: 'text/javascript', src: src});
+		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
+		return script.inject(this.options.injectScript);
+	},
+
+	success: function(args, script){
+		if (script) script.destroy();
+		this.running = false;
+		this.log('JSONP successfully retrieved: ', args);
+		this.fireEvent('complete', args).fireEvent('success', args).callChain();
+	}
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};/*
+---
+
+script: Request.Queue.js
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Element
+- core:1.2.4/Request
+- /Log
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+	options: {/*
+		onRequest: $empty(argsPassedToOnRequest),
+		onSuccess: $empty(argsPassedToOnSuccess),
+		onComplete: $empty(argsPassedToOnComplete),
+		onCancel: $empty(argsPassedToOnCancel),
+		onException: $empty(argsPassedToOnException),
+		onFailure: $empty(argsPassedToOnFailure),
+		onEnd: $empty,
+		*/
+		stopOnFailure: true,
+		autoAdvance: true,
+		concurrent: 1,
+		requests: {}
+	},
+
+	initialize: function(options){
+		if(options){
+			var requests = options.requests;
+			delete options.requests;	
+		}
+		this.setOptions(options);
+		this.requests = new Hash;
+		this.queue = [];
+		this.reqBinders = {};
+		
+		if(requests) this.addRequests(requests);
+	},
+
+	addRequest: function(name, request){
+		this.requests.set(name, request);
+		this.attach(name, request);
+		return this;
+	},
+
+	addRequests: function(obj){
+		$each(obj, function(req, name){
+			this.addRequest(name, req);
+		}, this);
+		return this;
+	},
+
+	getName: function(req){
+		return this.requests.keyOf(req);
+	},
+
+	attach: function(name, req){
+		if (req._groupSend) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			if(!this.reqBinders[name]) this.reqBinders[name] = {};
+			this.reqBinders[name][evt] = function(){
+				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
+			}.bind(this);
+			req.addEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req._groupSend = req.send;
+		req.send = function(options){
+			this.send(name, options);
+			return req;
+		}.bind(this);
+		return this;
+	},
+
+	removeRequest: function(req){
+		var name = $type(req) == 'object' ? this.getName(req) : req;
+		if (!name && $type(name) != 'string') return this;
+		req = this.requests.get(name);
+		if (!req) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			req.removeEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req.send = req._groupSend;
+		delete req._groupSend;
+		return this;
+	},
+
+	getRunning: function(){
+		return this.requests.filter(function(r){
+			return r.running;
+		});
+	},
+
+	isRunning: function(){
+		return !!(this.getRunning().getKeys().length);
+	},
+
+	send: function(name, options){
+		var q = function(){
+			this.requests.get(name)._groupSend(options);
+			this.queue.erase(q);
+		}.bind(this);
+		q.name = name;
+		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+		else q();
+		return this;
+	},
+
+	hasNext: function(name){
+		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+	},
+
+	resume: function(){
+		this.error = false;
+		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
+		return this;
+	},
+
+	runNext: function(name){
+		if (!this.queue.length) return this;
+		if (!name){
+			this.queue[0]();
+		} else {
+			var found;
+			this.queue.each(function(q){
+				if (!found && q.name == name){
+					found = true;
+					q();
+				}
+			});
+		}
+		return this;
+	},
+
+	runAll: function() {
+		this.queue.each(function(q) {
+			q();
+		});
+		return this;
+	},
+
+	clear: function(name){
+		if (!name){
+			this.queue.empty();
+		} else {
+			this.queue = this.queue.map(function(q){
+				if (q.name != name) return q;
+				else return false;
+			}).filter(function(q){ return q; });
+		}
+		return this;
+	},
+
+	cancel: function(name){
+		this.requests.get(name).cancel();
+		return this;
+	},
+
+	onRequest: function(){
+		this.fireEvent('request', arguments);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', arguments);
+		if (!this.queue.length) this.fireEvent('end');
+	},
+
+	onCancel: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('cancel', arguments);
+	},
+
+	onSuccess: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('success', arguments);
+	},
+
+	onFailure: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('failure', arguments);
+	},
+
+	onException: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('exception', arguments);
+	}
+
+});
+/*
+---
+
+script: Request.Periodical.js
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Request
+- /MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+	options: {
+		initialDelay: 5000,
+		delay: 5000,
+		limit: 60000
+	},
+
+	startTimer: function(data){
+		var fn = function(){
+			if (!this.running) this.send({data: data});
+		};
+		this.timer = fn.delay(this.options.initialDelay, this);
+		this.lastDelay = this.options.initialDelay;
+		this.completeCheck = function(response){
+			$clear(this.timer);
+			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+			this.timer = fn.delay(this.lastDelay, this);
+		};
+		return this.addEvent('complete', this.completeCheck);
+	},
+
+	stopTimer: function(){
+		$clear(this.timer);
+		return this.removeEvent('complete', this.completeCheck);
+	}
+
+});/*
+---
+
+script: Assets.js
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Element.Event
+- /MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+	javascript: function(source, properties){
+		properties = $extend({
+			onload: $empty,
+			document: document,
+			check: $lambda(true)
+		}, properties);
+		
+		if (properties.onLoad) properties.onload = properties.onLoad;
+		
+		var script = new Element('script', {src: source, type: 'text/javascript'});
+
+		var load = properties.onload.bind(script), 
+			check = properties.check, 
+			doc = properties.document;
+		delete properties.onload;
+		delete properties.check;
+		delete properties.document;
+
+		script.addEvents({
+			load: load,
+			readystatechange: function(){
+				if (['loaded', 'complete'].contains(this.readyState)) load();
+			}
+		}).set(properties);
+
+		if (Browser.Engine.webkit419) var checker = (function(){
+			if (!$try(check)) return;
+			$clear(checker);
+			load();
+		}).periodical(50);
+
+		return script.inject(doc.head);
+	},
+
+	css: function(source, properties){
+		return new Element('link', $merge({
+			rel: 'stylesheet',
+			media: 'screen',
+			type: 'text/css',
+			href: source
+		}, properties)).inject(document.head);
+	},
+
+	image: function(source, properties){
+		properties = $merge({
+			onload: $empty,
+			onabort: $empty,
+			onerror: $empty
+		}, properties);
+		var image = new Image();
+		var element = document.id(image) || new Element('img');
+		['load', 'abort', 'error'].each(function(name){
+			var type = 'on' + name;
+			var cap = name.capitalize();
+			if (properties['on' + cap]) properties[type] = properties['on' + cap];
+			var event = properties[type];
+			delete properties[type];
+			image[type] = function(){
+				if (!image) return;
+				if (!element.parentNode){
+					element.width = image.width;
+					element.height = image.height;
+				}
+				image = image.onload = image.onabort = image.onerror = null;
+				event.delay(1, element, element);
+				element.fireEvent(name, element, 1);
+			};
+		});
+		image.src = element.src = source;
+		if (image && image.complete) image.onload.delay(1);
+		return element.set(properties);
+	},
+
+	images: function(sources, options){
+		options = $merge({
+			onComplete: $empty,
+			onProgress: $empty,
+			onError: $empty,
+			properties: {}
+		}, options);
+		sources = $splat(sources);
+		var images = [];
+		var counter = 0;
+		return new Elements(sources.map(function(source){
+			return Asset.image(source, $extend(options.properties, {
+				onload: function(){
+					options.onProgress.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				},
+				onerror: function(){
+					options.onError.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				}
+			}));
+		}));
+	}
+
+};/*
+---
+
+script: Color.js
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Array
+- core:1.2.4/String
+- core:1.2.4/Number
+- core:1.2.4/Hash
+- core:1.2.4/Function
+- core:1.2.4/$util
+
+provides: [Color]
+
+...
+*/
+
+var Color = new Native({
+
+	initialize: function(color, type){
+		if (arguments.length >= 3){
+			type = 'rgb'; color = Array.slice(arguments, 0, 3);
+		} else if (typeof color == 'string'){
+			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+			else if (color.match(/hsb/)) color = color.hsbToRgb();
+			else color = color.hexToRgb(true);
+		}
+		type = type || 'rgb';
+		switch (type){
+			case 'hsb':
+				var old = color;
+				color = color.hsbToRgb();
+				color.hsb = old;
+			break;
+			case 'hex': color = color.hexToRgb(true); break;
+		}
+		color.rgb = color.slice(0, 3);
+		color.hsb = color.hsb || color.rgbToHsb();
+		color.hex = color.rgbToHex();
+		return $extend(color, this);
+	}
+
+});
+
+Color.implement({
+
+	mix: function(){
+		var colors = Array.slice(arguments);
+		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
+		var rgb = this.slice();
+		colors.each(function(color){
+			color = new Color(color);
+			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+		});
+		return new Color(rgb, 'rgb');
+	},
+
+	invert: function(){
+		return new Color(this.map(function(value){
+			return 255 - value;
+		}));
+	},
+
+	setHue: function(value){
+		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+	},
+
+	setSaturation: function(percent){
+		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+	},
+
+	setBrightness: function(percent){
+		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+	}
+
+});
+
+var $RGB = function(r, g, b){
+	return new Color([r, g, b], 'rgb');
+};
+
+var $HSB = function(h, s, b){
+	return new Color([h, s, b], 'hsb');
+};
+
+var $HEX = function(hex){
+	return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+	rgbToHsb: function(){
+		var red = this[0],
+				green = this[1],
+				blue = this[2],
+				hue = 0;
+		var max = Math.max(red, green, blue),
+				min = Math.min(red, green, blue);
+		var delta = max - min;
+		var brightness = max / 255,
+				saturation = (max != 0) ? delta / max : 0;
+		if(saturation != 0) {
+			var rr = (max - red) / delta;
+			var gr = (max - green) / delta;
+			var br = (max - blue) / delta;
+			if (red == max) hue = br - gr;
+			else if (green == max) hue = 2 + rr - br;
+			else hue = 4 + gr - rr;
+			hue /= 6;
+			if (hue < 0) hue++;
+		}
+		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+	},
+
+	hsbToRgb: function(){
+		var br = Math.round(this[2] / 100 * 255);
+		if (this[1] == 0){
+			return [br, br, br];
+		} else {
+			var hue = this[0] % 360;
+			var f = hue % 60;
+			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+			switch (Math.floor(hue / 60)){
+				case 0: return [br, t, p];
+				case 1: return [q, br, p];
+				case 2: return [p, br, t];
+				case 3: return [p, q, br];
+				case 4: return [t, p, br];
+				case 5: return [br, p, q];
+			}
+		}
+		return false;
+	}
+
+});
+
+String.implement({
+
+	rgbToHsb: function(){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHsb() : null;
+	},
+
+	hsbToRgb: function(){
+		var hsb = this.match(/\d{1,3}/g);
+		return (hsb) ? hsb.hsbToRgb() : null;
+	}
+
+});
+/*
+---
+
+script: Group.js
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Events
+- /MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+var Group = new Class({
+
+	initialize: function(){
+		this.instances = Array.flatten(arguments);
+		this.events = {};
+		this.checker = {};
+	},
+
+	addEvent: function(type, fn){
+		this.checker[type] = this.checker[type] || {};
+		this.events[type] = this.events[type] || [];
+		if (this.events[type].contains(fn)) return false;
+		else this.events[type].push(fn);
+		this.instances.each(function(instance, i){
+			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
+		}, this);
+		return this;
+	},
+
+	check: function(type, instance, i){
+		this.checker[type][i] = true;
+		var every = this.instances.every(function(current, j){
+			return this.checker[type][j] || false;
+		}, this);
+		if (!every) return;
+		this.checker[type] = {};
+		this.events[type].each(function(event){
+			event.call(this, this.instances, instance);
+		}, this);
+	}
+
+});
+/*
+---
+
+script: Hash.Cookie.js
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Aaron Newton
+
+requires:
+- core:1.2.4/Cookie
+- core:1.2.4/JSON
+- /MooTools.More
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+	Extends: Cookie,
+
+	options: {
+		autoSave: true
+	},
+
+	initialize: function(name, options){
+		this.parent(name, options);
+		this.load();
+	},
+
+	save: function(){
+		var value = JSON.encode(this.hash);
+		if (!value || value.length > 4096) return false; //cookie would be truncated!
+		if (value == '{}') this.dispose();
+		else this.write(value);
+		return true;
+	},
+
+	load: function(){
+		this.hash = new Hash(JSON.decode(this.read(), true));
+		return this;
+	}
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+		var value = method.apply(this.hash, arguments);
+		if (this.options.autoSave) this.save();
+		return value;
+	});
+});/*
+---
+
+script: HtmlTable.js
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- /Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		properties: {
+			cellpadding: 0,
+			cellspacing: 0,
+			border: 0
+		},
+		rows: [],
+		headers: [],
+		footers: []
+	},
+
+	property: 'HtmlTable',
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, table: Element.type});
+		this.setOptions(params.options);
+		this.element = params.table || new Element('table', this.options.properties);
+		if (this.occlude()) return this.occluded;
+		this.build();
+	},
+
+	build: function(){
+		this.element.store('HtmlTable', this);
+
+		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+		$$(this.body.rows);
+
+		if (this.options.headers.length) this.setHeaders(this.options.headers);
+		else this.thead = document.id(this.element.tHead);
+		if (this.thead) this.head = document.id(this.thead.rows[0]);
+
+		if (this.options.footers.length) this.setFooters(this.options.footers);
+		this.tfoot = document.id(this.element.tFoot);
+		if (this.tfoot) this.foot = document.id(this.thead.rows[0]);
+
+		this.options.rows.each(function(row){
+			this.push(row);
+		}, this);
+
+		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+				this[method] = this.element[method].bind(this.element);
+		}, this);
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	empty: function(){
+		this.body.empty();
+		return this;
+	},
+
+	set: function(what, items) {
+		var target = (what == 'headers') ? 'tHead' : 'tFoot';
+		this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
+		var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
+		if (what == 'headers') this.head = document.id(this.thead.rows[0]);
+		else this.foot = document.id(this.thead.rows[0]);
+		return data;
+	},
+
+	setHeaders: function(headers){
+		this.set('headers', headers);
+		return this;
+	},
+
+	setFooters: function(footers){
+		this.set('footers', footers);
+		return this;
+	},
+
+	push: function(row, rowProperties, target, tag){
+		var tds = row.map(function(data){
+			var td = new Element(tag || 'td', data.properties),
+				type = data.content || data || '',
+				element = document.id(type);
+			if($type(type) != 'string' && element) td.adopt(element);
+			else td.set('html', type);
+
+			return td;
+		});
+
+		return {
+			tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
+			tds: tds
+		};
+	}
+
+});
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- /HtmlTable
+- /Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		classZebra: 'table-tr-odd',
+		zebra: true
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		if (this.options.zebra) this.updateZebras();
+	},
+
+	updateZebras: function(){
+		Array.each(this.body.rows, this.zebra, this);
+	},
+
+	zebra: function(row, i){
+		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+	},
+
+	push: function(){
+		var pushed = this.previous.apply(this, arguments);
+		if (this.options.zebra) this.updateZebras();
+		return pushed;
+	}
+
+});/*
+---
+
+script: HtmlTable.Sort.js
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- core:1.2.4/Hash
+- /HtmlTable
+- /Class.refactor
+- /Element.Delegation
+- /Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {/*
+		onSort: $empty, */
+		sortIndex: 0,
+		sortReverse: false,
+		parsers: [],
+		defaultParser: 'string',
+		classSortable: 'table-sortable',
+		classHeadSort: 'table-th-sort',
+		classHeadSortRev: 'table-th-sort-rev',
+		classNoSort: 'table-th-nosort',
+		classGroupHead: 'table-tr-group-head',
+		classGroup: 'table-tr-group',
+		classCellSort: 'table-td-sort',
+		classSortSpan: 'table-th-sort-span',
+		sortable: false
+	},
+
+	initialize: function () {
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.sorted = {index: null, dir: 1};
+		this.bound = {
+			headClick: this.headClick.bind(this)
+		};
+		this.sortSpans = new Elements();
+		if (this.options.sortable) {
+			this.enableSort();
+			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+		}
+	},
+
+	attachSorts: function(attach){
+		this.element.removeEvents('click:relay(th)');
+		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
+	},
+
+	setHeaders: function(){
+		this.previous.apply(this, arguments);
+		if (this.sortEnabled) this.detectParsers();
+	},
+	
+	detectParsers: function(force){
+		if (!this.head) return;
+		var parsers = this.options.parsers, 
+				rows = this.body.rows;
+
+		// auto-detect
+		this.parsers = $$(this.head.cells).map(function(cell, index) {
+			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
+			var thDiv = new Element('div');
+			$each(cell.childNodes, function(node) {
+				thDiv.adopt(node);
+			});
+			thDiv.inject(cell);
+			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(thDiv, 'top');
+			
+			this.sortSpans.push(sortSpan);
+
+			var parser = parsers[index], 
+					cancel;
+			switch ($type(parser)) {
+				case 'function': parser = {convert: parser}; cancel = true; break;
+				case 'string': parser = parser; cancel = true; break;
+			}
+			if (!cancel) {
+				HtmlTable.Parsers.some(function(current) {
+					var match = current.match;
+					if (!match) return false;
+					for (var i = 0, j = rows.length; i < j; i++) {
+						var text = $(rows[i].cells[index]).get('html').clean();
+						if (text && match.test(text)) {
+							parser = current;
+							return true;
+						}
+					}
+				});
+			}
+
+			if (!parser) parser = this.options.defaultParser;
+			cell.store('htmltable-parser', parser);
+			return parser;
+		}, this);
+	},
+
+	headClick: function(event, el) {
+		console.log(el);
+		if (!this.head || el.hasClass(this.options.classNoSort)) return;
+		var index = Array.indexOf(this.head.cells, el);
+		this.sort(index);
+		return false;
+	},
+
+	sort: function(index, reverse, pre) {
+		if (!this.head) return;
+		pre = !!(pre);
+		var classCellSort = this.options.classCellSort;
+		var classGroup = this.options.classGroup, 
+				classGroupHead = this.options.classGroupHead;
+
+		if (!pre) {
+			if (index != null) {
+				if (this.sorted.index == index) {
+					this.sorted.reverse = !(this.sorted.reverse);
+				} else {
+					if (this.sorted.index != null) {
+						this.sorted.reverse = false;
+						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+					} else {
+						this.sorted.reverse = true;
+					}
+					this.sorted.index = index;
+				}
+			} else {
+				index = this.sorted.index;
+			}
+
+			if (reverse != null) this.sorted.reverse = reverse;
+
+			var head = document.id(this.head.cells[index]);
+			if (head) {
+				head.addClass(this.options.classHeadSort);
+				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+				else head.removeClass(this.options.classHeadSortRev);
+			}
+
+			this.body.getElements('td').removeClass(this.options.classCellSort);
+		}
+
+		var parser = this.parsers[index];
+		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
+		if (!parser) return;
+
+		if (!Browser.Engine.trident) {
+			var rel = this.body.getParent();
+			this.body.dispose();
+		}
+
+		var data = Array.map(this.body.rows, function(row, i) {
+			var value = parser.convert.call(document.id(row.cells[index]));
+
+			return {
+				position: i,
+				value: value,
+				toString:  function() {
+					return value.toString();
+				}
+			};
+		}, this);
+		data.reverse(true);
+
+		data.sort(function(a, b){
+			if (a.value === b.value) return 0;
+			return a.value > b.value ? 1 : -1;
+		});
+
+		if (!this.sorted.reverse) data.reverse(true);
+
+		var i = data.length, body = this.body;
+		var j, position, entry, group;
+
+		while (i) {
+			var item = data[--i];
+			position = item.position;
+			var row = body.rows[position];
+			if (row.disabled) continue;
+
+			if (!pre) {
+				if (group === item.value) {
+					row.removeClass(classGroupHead).addClass(classGroup);
+				} else {
+					group = item.value;
+					row.removeClass(classGroup).addClass(classGroupHead);
+				}
+				if (this.zebra) this.zebra(row, i);
+
+				row.cells[index].addClass(classCellSort);
+			}
+
+			body.appendChild(row);
+			for (j = 0; j < i; j++) {
+				if (data[j].position > position) data[j].position--;
+			}
+		};
+		data = null;
+		if (rel) rel.grab(body);
+
+		return this.fireEvent('sort', [body, index]);
+	},
+
+	reSort: function(){
+		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+		return this;
+	},
+
+	enableSort: function(){
+		this.element.addClass(this.options.classSortable);
+		this.attachSorts(true);
+		this.detectParsers();
+		this.sortEnabled = true;
+		return this;
+	},
+
+	disableSort: function(){
+		this.element.removeClass(this.options.classSortable);
+		this.attachSorts(false);
+		this.sortSpans.each(function(span) { span.destroy(); });
+		this.sortSpans.empty();
+		this.sortEnabled = false;
+		return this;
+	}
+
+});
+
+HtmlTable.Parsers = new Hash({
+
+	'date': {
+		match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+		convert: function() {
+			return Date.parse(this.get('text')).format('db');
+		},
+		type: 'date'
+	},
+	'input-checked': {
+		match: / type="(radio|checkbox)" /,
+		convert: function() {
+			return this.getElement('input').checked;
+		}
+	},
+	'input-value': {
+		match: /<input/,
+		convert: function() {
+			return this.getElement('input').value;
+		}
+	},
+	'number': {
+		match: /^\d+[^\d.,]*$/,
+		convert: function() {
+			return this.get('text').toInt();
+		},
+		number: true
+	},
+	'numberLax': {
+		match: /^[^\d]+\d+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^0-9]/, '').toInt();
+		},
+		number: true
+	},
+	'float': {
+		match: /^[\d]+\.[\d]+/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '').toFloat();
+		},
+		number: true
+	},
+	'floatLax': {
+		match: /^[^\d]+[\d]+\.[\d]+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '');
+		},
+		number: true
+	},
+	'string': {
+		match: null,
+		convert: function() {
+			return this.get('text');
+		}
+	},
+	'title': {
+		match: null,
+		convert: function() {
+			return this.title;
+		}
+	}
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+- Perrin Westrich
+- Aaron Newton
+- Scott Kyle
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- /Log
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+	
+	var Keyboard = this.Keyboard = new Class({
+
+		Extends: Events,
+
+		Implements: [Options, Log],
+
+		options: {
+			/*
+			onActivate: $empty,
+			onDeactivate: $empty,
+			*/
+			defaultEventType: 'keydown',
+			active: false,
+			events: {},
+			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this.setup();
+		}, 
+		setup: function(){
+			this.addEvents(this.options.events);
+			//if this is the root manager, nothing manages it
+			if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
+			if (this.options.active) this.activate();
+		},
+
+		handle: function(event, type){
+			//Keyboard.stop(event) prevents key propagation
+			if (event.preventKeyboardPropagation) return;
+			
+			var bubbles = !!this.manager;
+			if (bubbles && this.activeKB){
+				this.activeKB.handle(event, type);
+				if (event.preventKeyboardPropagation) return;
+			}
+			this.fireEvent(type, event);
+			
+			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
+		},
+
+		addEvent: function(type, fn, internal){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+		},
+
+		removeEvent: function(type, fn){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+		},
+
+		toggleActive: function(){
+			return this[this.active ? 'deactivate' : 'activate']();
+		},
+
+		activate: function(instance){
+			if (instance) {
+				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
+				if (instance != this.activeKB) this.previous = this.activeKB;
+				//if we're enabling a child, assign it so that events are now passed to it
+				this.activeKB = instance.fireEvent('activate');
+				Keyboard.manager.fireEvent('changed');
+			} else if (this.manager) {
+				//else we're enabling ourselves, we must ask our parent to do it for us
+				this.manager.activate(this);
+			}
+			return this;
+		},
+
+		deactivate: function(instance){
+			if (instance) {
+				if(instance === this.activeKB) {
+					this.activeKB = null;
+					instance.fireEvent('deactivate');
+					Keyboard.manager.fireEvent('changed');
+				}
+			}
+			else if (this.manager) {
+				this.manager.deactivate(this);
+			}
+			return this;
+		},
+
+		relenquish: function(){
+			if (this.previous) this.activate(this.previous);
+		},
+
+		//management logic
+		manage: function(instance){
+			if (instance.manager) instance.manager.drop(instance);
+			this.instances.push(instance);
+			instance.manager = this;
+			if (!this.activeKB) this.activate(instance);
+			else this._disable(instance);
+		},
+
+		_disable: function(instance){
+			if (this.activeKB == instance) this.activeKB = null;
+		},
+
+		drop: function(instance){
+			this._disable(instance);
+			this.instances.erase(instance);
+		},
+
+		instances: [],
+
+		trace: function(){
+			Keyboard.trace(this);
+		},
+
+		each: function(fn){
+			Keyboard.each(this, fn);
+		}
+
+	});
+	
+	var parsed = {};
+	var modifiers = ['shift', 'control', 'alt', 'meta'];
+	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+	
+	Keyboard.parse = function(type, eventType, ignore){
+		if (ignore && ignore.contains(type.toLowerCase())) return type;
+		
+		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+			eventType = $1;
+			return '';
+		});
+
+		if (!parsed[type]){
+			var key, mods = {};
+			type.split('+').each(function(part){
+				if (regex.test(part)) mods[part] = true;
+				else key = part;
+			});
+
+			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+			
+			var keys = [];
+			modifiers.each(function(mod){
+				if (mods[mod]) keys.push(mod);
+			});
+			
+			if (key) keys.push(key);
+			parsed[type] = keys.join('+');
+		}
+
+		return eventType + ':' + parsed[type];
+	};
+
+	Keyboard.each = function(keyboard, fn){
+		var current = keyboard || Keyboard.manager;
+		while (current){
+			fn.run(current);
+			current = current.activeKB;
+		}
+	};
+
+	Keyboard.stop = function(event){
+		event.preventKeyboardPropagation = true;
+	};
+
+	Keyboard.manager = new Keyboard({
+		active: true
+	});
+	
+	Keyboard.trace = function(keyboard){
+		keyboard = keyboard || Keyboard.manager;
+		keyboard.enableLog();
+		keyboard.log('the following items have focus: ');
+		Keyboard.each(keyboard, function(current){
+			keyboard.log(document.id(current.widget) || current.wiget || current);
+		});
+	};
+	
+	var handler = function(event){
+		var keys = [];
+		modifiers.each(function(mod){
+			if (event[mod]) keys.push(mod);
+		});
+		
+		if (!regex.test(event.key)) keys.push(event.key);
+		Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
+	};
+	
+	document.addEvents({
+		'keyup': handler,
+		'keydown': handler
+	});
+
+	Event.Keys.extend({
+		'shift': 16,
+		'control': 17,
+		'alt': 18,
+		'capslock': 20,
+		'pageup': 33,
+		'pagedown': 34,
+		'end': 35,
+		'home': 36,
+		'numlock': 144,
+		'scrolllock': 145,
+		';': 186,
+		'=': 187,
+		',': 188,
+		'-': Browser.Engine.Gecko ? 109 : 189,
+		'.': 190,
+		'/': 191,
+		'`': 192,
+		'[': 219,
+		'\\': 220,
+		']': 221,
+		"'": 222
+	});
+
+})();
+/*
+---
+
+script: HtmlTable.Select.js
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+- Harald Kirschner
+- Aaron Newton
+
+requires:
+- /Keyboard
+- /HtmlTable
+- /Class.refactor
+- /Element.Delegation
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		/*onRowFocus: $empty,
+		onRowUnfocus: $empty,*/
+		useKeyboard: true,
+		classRowSelected: 'table-tr-selected',
+		classRowHovered: 'table-tr-hovered',
+		classSelectable: 'table-selectable',
+		allowMultiSelect: true,
+		selectable: false
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.selectedRows = new Elements();
+		this.bound = {
+			mouseleave: this.mouseleave.bind(this),
+			focusRow: this.focusRow.bind(this)
+		};
+		if (this.options.selectable) this.enableSelect();
+	},
+
+	enableSelect: function(){
+		this.selectEnabled = true;
+		this.attachSelects();
+		this.element.addClass(this.options.classSelectable);
+	},
+
+	disableSelect: function(){
+		this.selectEnabled = false;
+		this.attach(false);
+		this.element.removeClass(this.options.classSelectable);
+	},
+
+	attachSelects: function(attach){
+		attach = $pick(attach, true);
+		var method = attach ? 'addEvents' : 'removeEvents';
+		this.element[method]({
+			mouseleave: this.bound.mouseleave
+		});
+		this.body[method]({
+			'click:relay(tr)': this.bound.focusRow
+		});
+		if (this.options.useKeyboard || this.keyboard){
+			if (!this.keyboard) this.keyboard = new Keyboard({
+				events: {
+					down: function(e) {
+						e.preventDefault();
+						this.shiftFocus(1);
+					}.bind(this),
+					up: function(e) {
+						e.preventDefault();
+						this.shiftFocus(-1);
+					}.bind(this),
+					enter: function(e) {
+						e.preventDefault();
+						if (this.hover) this.focusRow(this.hover);
+					}.bind(this)
+				},
+				active: true
+			});
+			this.keyboard[attach ? 'activate' : 'deactivate']();
+		}
+		this.updateSelects();
+	},
+
+	mouseleave: function(){
+		if (this.hover) this.leaveRow(this.hover);
+	},
+
+	focus: function(){
+		if (this.keyboard) this.keyboard.activate();
+	},
+
+	blur: function(){
+		if (this.keyboard) this.keyboard.deactivate();
+	},
+
+	push: function(){
+		var ret = this.previous.apply(this, arguments);
+		this.updateSelects();
+		return ret;
+	},
+
+	updateSelects: function(){
+		Array.each(this.body.rows, function(row){
+			var binders = row.retrieve('binders');
+			if ((binders && this.selectEnabled) || (!binders && !this.selectEnabled)) return;
+			if (!binders){
+				binders = {
+					mouseenter: this.enterRow.bind(this, [row]),
+					mouseleave: this.leaveRow.bind(this, [row])
+				};
+				row.store('binders', binders).addEvents(binders);
+			} else {
+				row.removeEvents(binders);
+			}
+		}, this);
+	},
+
+	enterRow: function(row){
+		if (this.hover) this.hover = this.leaveRow(this.hover);
+		this.hover = row.addClass(this.options.classRowHovered);
+	},
+
+	shiftFocus: function(offset){
+		if (!this.hover) return this.enterRow(this.body.rows[0]);
+		var to = Array.indexOf(this.body.rows, this.hover) + offset;
+		if (to < 0) to = 0;
+		if (to >= this.body.rows.length) to = this.body.rows.length - 1;
+		if (this.hover == this.body.rows[to]) return this;
+		this.enterRow(this.body.rows[to]);
+	},
+
+	leaveRow: function(row){
+		row.removeClass(this.options.classRowHovered);
+	},
+
+	focusRow: function(){
+		var row = arguments[1] || arguments[0]; //delegation passes the event first
+		if (!this.body.getChildren().contains(row)) return;
+		var unfocus = function(row){
+			this.selectedRows.erase(row);
+			row.removeClass(this.options.classRowSelected);
+			this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+		}.bind(this);
+		if (!this.options.allowMultiSelect) this.selectedRows.each(unfocus);
+		if (!this.selectedRows.contains(row)) {
+			this.selectedRows.push(row);
+			row.addClass(this.options.classRowSelected);
+			this.fireEvent('rowFocus', [row, this.selectedRows]);
+		} else {
+			unfocus(row);
+		}
+		return false;
+	},
+
+	selectAll: function(status){
+		status = $pick(status, true);
+		if (!this.options.allowMultiSelect && status) return;
+		if (!status) this.selectedRows.removeClass(this.options.classRowSelected).empty();
+		else this.selectedRows.combine(this.body.rows).addClass(this.options.classRowSelected);
+		return this;
+	},
+
+	selectNone: function(){
+		return this.selectAll(false);
+	}
+
+});
+/*
+---
+
+script: Keyboard.js
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+- Perrin Westrich
+
+requires:
+- core:1.2.4/Function
+- /Keyboard.Extras
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+	/*
+		shortcut should be in the format of:
+		{
+			'keys': 'shift+s', // the default to add as an event.
+			'description': 'blah blah blah', // a brief description of the functionality.
+			'handler': function(){} // the event handler to run when keys are pressed.
+		}
+	*/
+	addShortcut: function(name, shortcut) {
+		this.shortcuts = this.shortcuts || [];
+		this.shortcutIndex = this.shortcutIndex || {};
+		
+		shortcut.getKeyboard = $lambda(this);
+		shortcut.name = name;
+		this.shortcutIndex[name] = shortcut;
+		this.shortcuts.push(shortcut);
+		if(shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+		return this;
+	},
+
+	addShortcuts: function(obj){
+		for(var name in obj) this.addShortcut(name, obj[name]);
+		return this;
+	},
+
+	getShortcuts: function(){
+		return this.shortcuts || [];
+	},
+
+	getShortcut: function(name){
+		return (this.shortcutIndex || {})[name];
+	}
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+	$splat(shortcuts).each(function(shortcut){
+		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+		shortcut.keys = newKeys;
+		shortcut.getKeyboard().fireEvent('rebound');
+	});
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard) {
+	var activeKBS = [], activeSCS = [];
+	Keyboard.each(keyboard, [].push.bind(activeKBS));
+	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+	return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+	opts = opts || {};
+	var shortcuts = opts.many ? [] : null,
+		set = opts.many ? function(kb){
+				var shortcut = kb.getShortcut(name);
+				if(shortcut) shortcuts.push(shortcut);
+			} : function(kb) { 
+				if(!shortcuts) shortcuts = kb.getShortcut(name);
+			};
+	Keyboard.each(keyboard, set);
+	return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard) {
+	return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+/*
+---
+
+script: Scroller.js
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+
+requires:
+- core:1.2.4/Events
+- core:1.2.4/Options
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Dimensions
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		area: 20,
+		velocity: 1,
+		onChange: function(x, y){
+			this.element.scrollTo(x, y);
+		},
+		fps: 50
+	},
+
+	initialize: function(element, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.docBody = document.id(this.element.getDocument().body);
+		this.listener = ($type(this.element) != 'element') ?  this.docBody : this.element;
+		this.timer = null;
+		this.bound = {
+			attach: this.attach.bind(this),
+			detach: this.detach.bind(this),
+			getCoords: this.getCoords.bind(this)
+		};
+	},
+
+	start: function(){
+		this.listener.addEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+	},
+
+	stop: function(){
+		this.listener.removeEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+		this.detach();
+		this.timer = $clear(this.timer);
+	},
+
+	attach: function(){
+		this.listener.addEvent('mousemove', this.bound.getCoords);
+	},
+
+	detach: function(){
+		this.listener.removeEvent('mousemove', this.bound.getCoords);
+		this.timer = $clear(this.timer);
+	},
+
+	getCoords: function(event){
+		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+	},
+
+	scroll: function(){
+		var size = this.element.getSize(), 
+			scroll = this.element.getScroll(), 
+			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0}, 
+			scrollSize = this.element.getScrollSize(), 
+			change = {x: 0, y: 0};
+		for (var z in this.page){
+			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0) {
+				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
+			} else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
+				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
+			}
+		}
+		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+	}
+
+});/*
+---
+
+script: Tips.js
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+- Valerio Proietti
+- Christoph Pojer
+
+requires:
+- core:1.2.4/Options
+- core:1.2.4/Events
+- core:1.2.4/Element.Event
+- core:1.2.4/Element.Style
+- core:1.2.4/Element.Dimensions
+- /MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		/*
+		onAttach: $empty(element),
+		onDetach: $empty(element),
+		*/
+		onShow: function(){
+			this.tip.setStyle('display', 'block');
+		},
+		onHide: function(){
+			this.tip.setStyle('display', 'none');
+		},
+		title: 'title',
+		text: function(element){
+			return element.get('rel') || element.get('href');
+		},
+		showDelay: 100,
+		hideDelay: 100,
+		className: 'tip-wrap',
+		offset: {x: 16, y: 16},
+		windowPadding: {x:0, y:0},
+		fixed: false
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, elements: $defined});
+		this.setOptions(params.options);
+		if (params.elements) this.attach(params.elements);
+		this.container = new Element('div', {'class': 'tip'});
+	},
+
+	toElement: function(){
+		if (this.tip) return this.tip;
+
+		return this.tip = new Element('div', {
+			'class': this.options.className,
+			styles: {
+				position: 'absolute',
+				top: 0,
+				left: 0
+			}
+		}).adopt(
+			new Element('div', {'class': 'tip-top'}),
+			this.container,
+			new Element('div', {'class': 'tip-bottom'})
+		).inject(document.body);
+	},
+
+	attach: function(elements){
+		$$(elements).each(function(element){
+			var title = read(this.options.title, element),
+				text = read(this.options.text, element);
+			
+			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
+			element.retrieve('tip:text', text);
+			this.fireEvent('attach', [element]);
+			
+			var events = ['enter', 'leave'];
+			if (!this.options.fixed) events.push('move');
+			
+			events.each(function(value){
+				var event = element.retrieve('tip:' + value);
+				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
+				
+				element.store('tip:' + value, event).addEvent('mouse' + value, event);
+			}, this);
+		}, this);
+		
+		return this;
+	},
+
+	detach: function(elements){
+		$$(elements).each(function(element){
+			['enter', 'leave', 'move'].each(function(value){
+				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+			});
+			
+			this.fireEvent('detach', [element]);
+			
+			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+				var original = element.retrieve('tip:native');
+				if (original) element.set('title', original);
+			}
+		}, this);
+		
+		return this;
+	},
+
+	elementEnter: function(event, element){
+		this.container.empty();
+		
+		['title', 'text'].each(function(value){
+			var content = element.retrieve('tip:' + value);
+			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
+		}, this);
+		
+		$clear(this.timer);
+		this.timer = (function(){
+			this.show(this, element);
+			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+		}).delay(this.options.showDelay, this);
+	},
+
+	elementLeave: function(event, element){
+		$clear(this.timer);
+		this.timer = this.hide.delay(this.options.hideDelay, this, element);
+		this.fireForParent(event, element);
+	},
+
+	fireForParent: function(event, element){
+		element = element.getParent();
+		if (!element || element == document.body) return;
+		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+		else this.fireForParent(event, element);
+	},
+
+	elementMove: function(event, element){
+		this.position(event);
+	},
+
+	position: function(event){
+		if (!this.tip) document.id(this);
+
+		var size = window.getSize(), scroll = window.getScroll(),
+			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+			props = {x: 'left', y: 'top'},
+			obj = {};
+		
+		for (var z in props){
+			obj[props[z]] = event.page[z] + this.options.offset[z];
+			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+		}
+		
+		this.tip.setStyles(obj);
+	},
+
+	fill: function(element, contents){
+		if(typeof contents == 'string') element.set('html', contents);
+		else element.adopt(contents);
+	},
+
+	show: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('show', [this.tip, element]);
+	},
+
+	hide: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('hide', [this.tip, element]);
+	}
+
+});
+
+})();/*
+---
+
+script: Date.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+- Alfons Sanchez
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Date', {
+
+	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'fa menys d`un minut',
+	minuteAgo: 'fa un minut',
+	minutesAgo: 'fa {delta} minuts',
+	hourAgo: 'fa un hora',
+	hoursAgo: 'fa unes {delta} hores',
+	dayAgo: 'fa un dia',
+	daysAgo: 'fa {delta} dies',
+	lessThanMinuteUntil: 'menys d`un minut des d`ara',
+	minuteUntil: 'un minut des d`ara',
+	minutesUntil: '{delta} minuts des d`ara',
+	hourUntil: 'un hora des d`ara',
+	hoursUntil: 'unes {delta} hores des d`ara',
+	dayUntil: '1 dia des d`ara',
+	daysUntil: '{delta} dies des d`ara'
+
+});/*
+---
+
+script: Date.Czech.js
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+- Jan Černý chemiX
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Date', {
+
+	months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+	days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+	AM: 'dop.',
+	PM: 'odp.',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return '.';
+	},
+
+    // TODO : in examples use and fix it
+	lessThanMinuteAgo: 'méně než minutou',
+	minuteAgo: 'přibližně před minutou',
+	minutesAgo: 'před {delta} minutami',
+	hourAgo: 'přibližně před hodinou',
+	hoursAgo: 'před {delta} hodinami',
+	dayAgo: 'před dnem',
+	daysAgo: 'před {delta} dni',
+	lessThanMinuteUntil: 'před méně než minutou',
+	minuteUntil: 'asi před minutou',
+	minutesUntil: ' asi před {delta} minutami',
+	hourUntil: 'asi před hodinou',
+	hoursUntil: 'před {delta} hodinami',
+	dayUntil: 'před dnem',
+	daysUntil: 'před {delta} dni',
+	weekUntil: 'před týdnem',
+	weeksUntil: 'před {delta} týdny',
+	monthUntil: 'před měsícem',
+	monthsUntil: 'před {delta} měsíci',
+	yearUntil: 'před rokem',
+	yearsUntil: 'před {delta} lety'
+
+});
+/*
+---
+
+script: Date.Danish.js
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+- Martin Overgaard
+- Henrik Hansen
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Danish]
+
+...
+*/
+ 
+MooTools.lang.set('da-DK', 'Date', {
+
+	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d-%m-%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+	  //1st, 2nd, 3rd, etc.
+	  return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mindre end et minut siden',
+	minuteAgo: 'omkring et minut siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omkring en time siden',
+	hoursAgo: 'omkring {delta} timer siden',
+	dayAgo: '1 dag siden',
+	daysAgo: '{delta} dage siden',
+	weekAgo: '1 uge siden',
+	weeksAgo: '{delta} uger siden',
+	monthAgo: '1 måned siden',
+	monthsAgo: '{delta} måneder siden',
+	yearthAgo: '1 år siden',
+	yearsAgo: '{delta} år siden',
+	lessThanMinuteUntil: 'mindre end et minut fra nu',
+	minuteUntil: 'omkring et minut fra nu',
+	minutesUntil: '{delta} minutter fra nu',
+	hourUntil: 'omkring en time fra nu',
+	hoursUntil: 'omkring {delta} timer fra nu',
+	dayUntil: '1 dag fra nu',
+	daysUntil: '{delta} dage fra nu',
+	weekUntil: '1 uge fra nu',
+	weeksUntil: '{delta} uger fra nu',
+	monthUntil: '1 måned fra nu',
+	monthsUntil: '{delta} måneder fra nu',
+	yearUntil: '1 år fra nu',
+	yearsUntil: '{delta} år fra nu'
+
+});
+/*
+---
+
+script: Date.Dutch.js
+
+description: Date messages in Dutch.
+
+license: MIT-style license
+
+authors:
+- Lennart Pilon
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Date', {
+
+	months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
+	days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: 'e',
+
+	lessThanMinuteAgo: 'minder dan een minuut geleden',
+	minuteAgo: 'ongeveer een minuut geleden',
+	minutesAgo: 'minuten geleden',
+	hourAgo: 'ongeveer een uur geleden',
+	hoursAgo: 'ongeveer {delta} uur geleden',
+	dayAgo: '{delta} dag geleden',
+	daysAgo: 'dagen geleden',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+	lessThanMinuteUntil: 'minder dan een minuut vanaf nu',
+	minuteUntil: 'ongeveer een minuut vanaf nu',
+	minutesUntil: '{delta} minuten vanaf nu',
+	hourUntil: 'ongeveer een uur vanaf nu',
+	hoursUntil: 'ongeveer {delta} uur vanaf nu',
+	dayUntil: '1 dag vanaf nu',
+	daysUntil: '{delta} dagen vanaf nu',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearthAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+
+	weekUntil: 'over een week',
+	weeksUntil: 'over {delta} weken',
+	monthUntil: 'over een maand',
+	monthsUntil: 'over {delta} maanden',
+	yearUntil: 'over een jaar',
+	yearsUntil: 'over {delta} jaar' 
+
+});/*
+---
+
+script: Date.English.GB.js
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+- Aaron Newton
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.English.GB]
+
+...
+*/
+
+MooTools.lang.set('en-GB', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+	
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M'
+
+}).set('cascade', ['en-US']);/*
+---
+
+script: Date.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+- Kevin Valdek
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Date', {
+
+	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],
+	//culture's date order: MM.DD.YYYY
+	dateOrder: ['month', 'date', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%m.%d.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
+	minuteAgo: 'umbes minut aega tagasi',
+	minutesAgo: '{delta} minutit tagasi',
+	hourAgo: 'umbes tund aega tagasi',
+	hoursAgo: 'umbes {delta} tundi tagasi',
+	dayAgo: '1 päev tagasi',
+	daysAgo: '{delta} päeva tagasi',
+	weekAgo: '1 nädal tagasi',
+	weeksAgo: '{delta} nädalat tagasi',
+	monthAgo: '1 kuu tagasi',
+	monthsAgo: '{delta} kuud tagasi',
+	yearAgo: '1 aasta tagasi',
+	yearsAgo: '{delta} aastat tagasi',
+	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
+	minuteUntil: 'umbes minuti aja pärast',
+	minutesUntil: '{delta} minuti pärast',
+	hourUntil: 'umbes tunni aja pärast',
+	hoursUntil: 'umbes {delta} tunni pärast',
+	dayUntil: '1 päeva pärast',
+	daysUntil: '{delta} päeva pärast',
+	weekUntil: '1 nädala pärast',
+	weeksUntil: '{delta} nädala pärast',
+	monthUntil: '1 kuu pärast',
+	monthsUntil: '{delta} kuu pärast',
+	yearUntil: '1 aasta pärast',
+	yearsUntil: '{delta} aasta pärast'
+
+});/*
+---
+
+script: Date.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+- Christoph Pojer
+- Frank Rossi
+- Ulrich Petri
+- Fabian Beiner
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Date', {
+
+	months: ['Januar', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+	days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: [ 'date', 'month', 'year', '.'],
+
+	AM: 'vormittags',
+	PM: 'nachmittags',
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '.',
+
+	lessThanMinuteAgo: 'Vor weniger als einer Minute',
+	minuteAgo: 'Vor einer Minute',
+	minutesAgo: 'Vor {delta} Minuten',
+	hourAgo: 'Vor einer Stunde',
+	hoursAgo: 'Vor {delta} Stunden',
+	dayAgo: 'Vor einem Tag',
+	daysAgo: 'Vor {delta} Tagen',
+	weekAgo: 'Vor einer Woche',
+	weeksAgo: 'Vor {delta} Wochen',
+	monthAgo: 'Vor einem Monat',
+	monthsAgo: 'Vor {delta} Monaten',
+	yearAgo: 'Vor einem Jahr',
+	yearsAgo: 'Vor {delta} Jahren',
+	lessThanMinuteUntil: 'In weniger als einer Minute',
+	minuteUntil: 'In einer Minute',
+	minutesUntil: 'In {delta} Minuten',
+	hourUntil: 'In ca. einer Stunde',
+	hoursUntil: 'In ca. {delta} Stunden',
+	dayUntil: 'In einem Tag',
+	daysUntil: 'In {delta} Tagen',
+	weekUntil: 'In einer Woche',
+	weeksUntil: 'In {delta} Wochen',
+	monthUntil: 'In einem Monat',
+	monthsUntil: 'In {delta} Monaten',
+	yearUntil: 'In einem Jahr',
+	yearsUntil: 'In {delta} Jahren'
+});/*
+---
+
+script: Date.German.CH.js
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors: 
+- Michael van der Weg
+
+requires:
+- /Lang
+- /Date.German
+
+provides: [Date.German.CH]
+
+...
+*/
+
+MooTools.lang.set('de-CH', 'cascade', ['de-DE']);/*
+---
+
+script: Date.French.js
+
+description: Date messages in French.
+
+license: MIT-style license
+
+authors:
+- Nicolas Sorosac
+- Antoine Abt
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Date', {
+
+	months: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
+	days: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	getOrdinal: function(dayOfMonth){
+	  return (dayOfMonth > 1) ? '' : 'er';
+	},
+
+	lessThanMinuteAgo: 'il y a moins d\'une minute',
+	minuteAgo: 'il y a une minute',
+	minutesAgo: 'il y a {delta} minutes',
+	hourAgo: 'il y a une heure',
+	hoursAgo: 'il y a {delta} heures',
+	dayAgo: 'il y a un jour',
+	daysAgo: 'il y a {delta} jours',
+	weekAgo: 'il y a une semaine',
+	weeksAgo: 'il y a {delta} semaines',
+	monthAgo: 'il y a 1 mois',
+	monthsAgo: 'il y a {delta} mois',
+	yearthAgo: 'il y a 1 an',
+	yearsAgo: 'il y a {delta} ans',
+	lessThanMinuteUntil: 'dans moins d\'une minute',
+	minuteUntil: 'dans une minute',
+	minutesUntil: 'dans {delta} minutes',
+	hourUntil: 'dans une heure',
+	hoursUntil: 'dans {delta} heures',
+	dayUntil: 'dans un jour',
+	daysUntil: 'dans {delta} jours',
+	weekUntil: 'dans 1 semaine',
+	weeksUntil: 'dans {delta} semaines',
+	monthUntil: 'dans 1 mois',
+	monthsUntil: 'dans {delta} mois',
+	yearUntil: 'dans 1 an',
+	yearsUntil: 'dans {delta} ans'
+
+});
+/*
+---
+
+script: Date.Italian.js
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+- Andrea Novero
+- Valerio Proietti
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Date', {
+ 
+	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H.%M',
+
+	/* Date.Extras */
+	ordinal: '&ordm;',
+
+	lessThanMinuteAgo: 'meno di un minuto fa',
+	minuteAgo: 'circa un minuto fa',
+	minutesAgo: 'circa {delta} minuti fa',
+	hourAgo: 'circa un\'ora fa',
+	hoursAgo: 'circa {delta} ore fa',
+	dayAgo: 'circa 1 giorno fa',
+	daysAgo: 'circa {delta} giorni fa',
+	lessThanMinuteUntil: 'tra meno di un minuto',
+	minuteUntil: 'tra circa un minuto',
+	minutesUntil: 'tra circa {delta} minuti',
+	hourUntil: 'tra circa un\'ora',
+	hoursUntil: 'tra circa {delta} ore',
+	dayUntil: 'tra circa un giorno',
+	daysUntil: 'tra circa {delta} giorni'
+
+});/*
+---
+
+script: Date.Norwegian.js
+
+description: Date messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+- Espen 'Rexxars' Hovlandsdal
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	lessThanMinuteAgo: 'kortere enn et minutt siden',
+	minuteAgo: 'omtrent et minutt siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omtrent en time siden',
+	hoursAgo: 'omtrent {delta} timer siden',
+	dayAgo: '{delta} dag siden',
+	daysAgo: '{delta} dager siden'
+
+});/*
+---
+
+script: Date.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+- Oskar Krawczyk
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Date', {
+	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+	dateOrder: ['year', 'month', 'date'],
+	AM: 'nad ranem',
+	PM: 'po południu',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mniej niż minute temu',
+	minuteAgo: 'około minutę temu',
+	minutesAgo: '{delta} minut temu',
+	hourAgo: 'około godzinę temu',
+	hoursAgo: 'około {delta} godzin temu',
+	dayAgo: 'Wczoraj',
+	daysAgo: '{delta} dni temu',
+	lessThanMinuteUntil: 'za niecałą minutę',
+	minuteUntil: 'za około minutę',
+	minutesUntil: 'za {delta} minut',
+	hourUntil: 'za około godzinę',
+	hoursUntil: 'za około {delta} godzin',
+	dayUntil: 'za 1 dzień',
+	daysUntil: 'za {delta} dni'
+});/*
+---
+
+script: Date.Portuguese.BR.js
+
+description: Date messages in Portuguese-BR (Brazil).
+
+license: MIT-style license
+
+authors:
+- Fabio Miranda Costa
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Date', {
+
+	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1º, 2º, 3º, etc.
+    	return '&ordm;';
+	},
+
+	lessThanMinuteAgo: 'há menos de um minuto',
+	minuteAgo: 'há cerca de um minuto',
+	minutesAgo: 'há {delta} minutos',
+	hourAgo: 'há cerca de uma hora',
+	hoursAgo: 'há cerca de {delta} horas',
+	dayAgo: 'há um dia',
+	daysAgo: 'há {delta} dias',
+    weekAgo: 'há uma semana',
+	weeksAgo: 'há {delta} semanas',
+	monthAgo: 'há um mês',
+	monthsAgo: 'há {delta} meses',
+	yearAgo: 'há um ano',
+	yearsAgo: 'há {delta} anos',
+	lessThanMinuteUntil: 'em menos de um minuto',
+	minuteUntil: 'em um minuto',
+	minutesUntil: 'em {delta} minutos',
+	hourUntil: 'em uma hora',
+	hoursUntil: 'em {delta} horas',
+	dayUntil: 'em um dia',
+	daysUntil: 'em {delta} dias',
+	weekUntil: 'em uma semana',
+	weeksUntil: 'em {delta} semanas',
+	monthUntil: 'em um mês',
+	monthsUntil: 'em {delta} meses',
+	yearUntil: 'em um ano',
+	yearsUntil: 'em {delta} anos'
+
+});/*
+Script: Date.Russian.js
+	Date messages for Russian.
+
+	License:
+		MIT-style license.
+
+	Authors:
+		Evstigneev Pavel
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Date', {
+
+	months: ['Январь', 'Февраль', 'Март', 'Ð?прель', 'Май', 'Июнь', 'Июль', 'Ð?вгуÑ?Ñ‚', 'СентÑ?брь', 'ОктÑ?брь', 'Ð?оÑ?брь', 'Декабрь'],
+	days: ['ВоÑ?креÑ?енье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'ПÑ?тница', 'Суббота'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+
+  /*
+   *  Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+   *
+   *  one -> n mod 10 is 1 and n mod 100 is not 11;
+   *  few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+   *  many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+   *  other -> everything else (example 3.14)
+   */
+
+  pluralize: function (n, one, few, many, other) {
+    var modulo10 = n % 10
+    var modulo100 = n % 100
+
+    if (modulo10 == 1 && modulo100 != 11) {
+      return one;
+    } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return few;
+    } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return many;
+    } else {
+      return other;
+    }
+  },
+
+	/* Date.Extras */
+	ordinal: '',
+	lessThanMinuteAgo: 'меньше минуты назад',
+	minuteAgo: 'минута назад',
+	minutesAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'минута', 'минуты', 'минут') + ' назад'},
+	hourAgo: 'чаÑ? назад',
+	hoursAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ' назад'},
+	dayAgo: 'вчера',
+	daysAgo: function (delta) { return '{delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + ' назад' },
+	lessThanMinuteUntil: 'меньше минуты назад',
+	minuteUntil: 'через минуту',
+	minutesUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	hourUntil: 'через чаÑ?',
+	hoursUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	dayUntil: 'завтра',
+	daysUntil: function (delta) { return 'через {delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + '' }
+
+});/*
+---
+
+script: Date.Spanish.US.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+- Ãlfons Sanchez
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Date', {
+
+	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+	days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'hace menos de un minuto',
+	minuteAgo: 'hace un minuto',
+	minutesAgo: 'hace {delta} minutos',
+	hourAgo: 'hace una hora',
+	hoursAgo: 'hace unas {delta} horas',
+	dayAgo: 'hace un día',
+	daysAgo: 'hace {delta} días',
+	weekAgo: 'hace una semana',
+	weeksAgo: 'hace unas {delta} semanas',
+	monthAgo: 'hace un mes',
+	monthsAgo: 'hace {delta} meses',
+	yearAgo: 'hace un año',
+	yearsAgo: 'hace {delta} años',
+	lessThanMinuteUntil: 'menos de un minuto desde ahora',
+	minuteUntil: 'un minuto desde ahora',
+	minutesUntil: '{delta} minutos desde ahora',
+	hourUntil: 'una hora desde ahora',
+	hoursUntil: 'unas {delta} horas desde ahora',
+	dayUntil: 'un día desde ahora',
+	daysUntil: '{delta} días desde ahora',
+	weekUntil: 'una semana desde ahora',
+	weeksUntil: 'unas {delta} semanas desde ahora',
+	monthUntil: 'un mes desde ahora',
+	monthsUntil: '{delta} meses desde ahora',
+	yearUntil: 'un año desde ahora',
+	yearsUntil: '{delta} años desde ahora'
+
+});/*
+---
+
+script: Date.Swedish.js
+
+description: Date messages for Swedish (SE).
+
+license: MIT-style license
+
+authors:
+- Martin Lundgren
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Date', {
+
+	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+	// culture's date order: YYYY-MM-DD
+	dateOrder: ['year', 'month', 'date'],
+	AM: '',
+	PM: '',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		// Not used in Swedish
+		return '';
+	},
+
+	lessThanMinuteAgo: 'mindre än en minut sedan',
+	minuteAgo: 'ungefär en minut sedan',
+	minutesAgo: '{delta} minuter sedan',
+	hourAgo: 'ungefär en timme sedan',
+	hoursAgo: 'ungefär {delta} timmar sedan',
+	dayAgo: '1 dag sedan',
+	daysAgo: '{delta} dagar sedan',
+	lessThanMinuteUntil: 'mindre än en minut sedan',
+	minuteUntil: 'ungefär en minut sedan',
+	minutesUntil: '{delta} minuter sedan',
+	hourUntil: 'ungefär en timme sedan',
+	hoursUntil: 'ungefär {delta} timmar sedan',
+	dayUntil: '1 dag sedan',
+	daysUntil: '{delta} dagar sedan'
+
+});/*
+---
+
+script: Date.Ukrainian.js
+
+description: Date messages for Ukrainian.
+
+license: MIT-style license
+
+authors:
+- Slik
+
+requires:
+- /Lang
+- /Date
+
+provides: [Date.Ukrainian]
+
+...
+*/
+
+(function(){
+	var pluralize = function(n, one, few, many, other){
+		var d = (n / 10).toInt();
+		var z = n % 10;
+		var s = (n / 100).toInt();
+
+		if(d == 1 && n > 10) return many;
+		if(z == 1) return one;
+		if(z > 0 && z < 5) return few;
+		return many;
+	};
+
+	MooTools.lang.set('uk-UA', 'Date', {
+			months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'ВереÑ?ень', 'Жовтень', 'ЛиÑ?топад', 'Грудень'],
+			days: ['Ð?еділÑ?', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'Ñ?тницÑ?', 'Субота'],
+			//culture's date order: DD/MM/YYYY
+			dateOrder: ['date', 'month', 'year'],
+			AM: 'до полуднÑ?',
+			PM: 'по полудню',
+
+			shortDate: '%d/%m/%Y',
+			shortTime: '%H:%M',
+
+			/* Date.Extras */
+			ordinal: '',
+			lessThanMinuteAgo: 'меньше хвилини тому',
+			minuteAgo: 'хвилину тому',
+			minutesAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому';
+			},
+			hourAgo: 'годину тому',
+			hoursAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому';
+			},
+			dayAgo: 'вчора',
+			daysAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів') + ' тому';
+			},
+			weekAgo: 'тиждень тому',
+			weeksAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому';
+			},
+			monthAgo: 'міÑ?Ñ?ць тому',
+			monthsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців') + ' тому';
+			},
+			yearAgo: 'рік тому',
+			yearsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому';
+			},
+			lessThanMinuteUntil: 'за мить',
+			minuteUntil: 'через хвилину',
+			minutesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин');
+			},
+			hourUntil: 'через годину',
+			hoursUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин');
+			},
+			dayUntil: 'завтра',
+			daysUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів');
+			},
+			weekUntil: 'через тиждень',
+			weeksUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів');
+			},
+			monthUntil: 'через міÑ?Ñ?ць',
+			monthesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців');
+			},
+			yearUntil: 'через рік',
+			yearsUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років');
+			}
+	});
+
+})();/*
+---
+
+script: Form.Validator.Arabic.js
+
+description: Form.Validator messages in Arabic.
+
+license: MIT-style license
+
+authors:
+- Chafik Barbar
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Arabic]
+
+...
+*/
+
+MooTools.lang.set('ar', 'Form.Validator', {
+	required:'هذا الحقل مطلوب.',
+	minLength:'رجاءً إدخال {minLength}  أحرÙ? على الأقل (تم إدخال {length} أحرÙ?).',
+	maxLength:'الرجاء عدم إدخال أكثر من {maxLength} أحرÙ? (تم إدخال {length} أحرÙ?).',
+	integer:'الرجاء إدخال عدد صحيح Ù?ÙŠ هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
+	numeric:'الرجاء إدخال قيم رقمية Ù?ÙŠ هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+	digits:'الرجاء أستخدام قيم رقمية وعلامات ترقيمية Ù?قط Ù?ÙŠ هذا الحقل (مثال, رقم هاتÙ? مع نقطة أو شحطة)',
+	alpha:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	alphanum:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) أو أرقام (0-9) Ù?قط Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	dateSuchAs:'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+	dateInFormatMDY:'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+	email:'الرجاء إدخال بريد إلكتروني صحيح.',
+	url:'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
+	currencyDollar:'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
+	oneRequired:'الرجاء إدخال قيمة Ù?ÙŠ أحد هذه الحقول على الأقل.',
+	errorPrefix: 'خطأ: ',
+	warningPrefix: 'تحذير: '
+}).set('ar', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+- Miquel Hudin
+- Alfons Sanchez
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Form.Validator', {
+
+	required:'Aquest camp es obligatori.',
+	minLength:'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+	maxLength:'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+	integer:'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+	numeric:'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+	alpha:'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	alphanum:'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	dateSuchAs:'Per favor introdueix una data valida com {date}',
+	dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',
+	url:'Per favor introdueix una URL valida com http://www.google.com.',
+	currencyDollar:'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+	oneRequired:'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Avis: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No poden haver espais en aquesta entrada.',
+	reqChkByNode: 'No hi han elements seleccionats.',
+	requiredChk: 'Aquest camp es obligatori.',
+	reqChkByName: 'Per favor selecciona una {label}.',
+	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+	startDate: 'la data de inici',
+	endDate: 'la data de fi',
+	currendDate: 'la data actual',
+	afterDate: 'La data deu ser igual o posterior a {label}.',
+	beforeDate: 'La data deu ser igual o anterior a {label}.',
+	startMonth: 'Per favor selecciona un mes d´orige',
+	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});/*
+---
+
+script: Form.Validator.Czech.js
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+- Jan Černý chemiX
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Form.Validator', {
+
+	required:'Tato položka je povinná.',
+	minLength:'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+	maxLength:'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).',
+	integer:'Zadejte prosím celé Ä?íslo. Desetinná Ä?ísla (napÅ™. 1.25) nejsou povolena.',
+	numeric:'Zadejte jen Ä?íselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+	digits:'Zadejte prosím pouze Ä?ísla a interpunkÄ?ní znaménka(například telefonní Ä?íslo s pomlÄ?kami nebo teÄ?kami je povoleno).',
+	alpha:'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+	alphanum:'Zadejte prosím pouze písmena (a-z) nebo Ä?íslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+	dateSuchAs:'Zadejte prosím platné datum jako {date}',
+	dateInFormatMDY:'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+	email:'Zadejte prosím platnou e-mailovou adresu. Například "fred at domain.com".',
+	url:'Zadejte prosím platnou URL adresu jako http://www.google.com.',
+	currencyDollar:'Zadejte prosím platnou Ä?ástku. Například $100.00.',
+	oneRequired:'Zadejte prosím alespoň jednu hodnotu pro tyto položky.',
+	errorPrefix: 'Chyba: ',
+	warningPrefix: 'Upozornění: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'V této položce nejsou povoleny mezery',
+	reqChkByNode: 'Nejsou vybrány žádné položky.',
+	requiredChk: 'Tato položka je vyžadována.',
+	reqChkByName: 'Prosím vyberte {label}.',
+	match: 'Tato položka se musí shodovat s položkou {matchName}',
+	startDate: 'datum zahájení',
+	endDate: 'datum ukonÄ?ení',
+	currendDate: 'aktuální datum',
+	afterDate: 'Datum by mělo být stejné nebo větší než {label}.',
+	beforeDate: 'Datum by mělo být stejné nebo menší než {label}.',
+	startMonth: 'Vyberte poÄ?áteÄ?ní mÄ›síc.',
+	sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.',
+    creditcard: 'Zadané Ä?íslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} Ä?ísel.'
+
+});
+/*
+---
+
+script: Form.Validator.Chinese.js
+
+description: Form.Validator messages in chinese (both simplified and traditional).
+
+license: MIT-style license
+
+authors:
+- 陈桂军 - guidy <at> ixuer [dot] net
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Chinese]
+
+...
+*/
+
+/*
+In Chinese:
+------------
+需�指出的是:
+简体中文适用于中国大陆,
+�体中文适用于香港�澳门和�湾�。
+简体中文和�体中文在字体和语法上有很多的��之处。
+
+我�以确�简体中文语言包的准确性,
+但对于�体中文,我�以��用户�以准确的�解,但无法��语�符�他们的阅读习惯。
+如果您�能确认的�,�以�使用简体中文语言包,因为它是最通用的。
+
+In English:
+------------
+It should be noted that:
+Simplified  Chinese apply to mainland Chinese,
+Traditional Chinese apply to Hong Kong, Macao and Taiwan Province.
+There are a lot of different from Simplified  Chinese and Traditional Chinese , Contains font and syntax .
+
+I can assure Simplified Chinese language pack accuracy .
+For Traditional Chinese, I can only guarantee that users can understand, but not necessarily in line with their reading habits.
+If you are unsure, you can only use the simplified Chinese language pack, as it is the most common.
+
+*/
+
+// Simplified Chinese
+MooTools.lang.set('zhs-CN', 'Form.Validator', {
+	required:'这是必填项。',
+	minLength:'请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
+	maxLength:'最多�能输入 {maxLength} 个字符 (已输入 {length} 个)。',
+	integer:'请输入一个整数,�能包��数点。例如:"1", "200"。',
+	numeric:'请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'这里�能接�数字和标点的输入,标点�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'请输入 A-Z 的 26 个字�,�能包�空格或任何其他字符。',
+	alphanum:'请输入 A-Z 的 26 个字�或 0-9 的 10 个数字,�能包�空格或任何其他字符。',
+	dateSuchAs:'请输入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'请输入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'请输入�法的电�信箱地�,例如:"fred at domain.com"。',
+	url:'请输入�法的 Url 地�,例如:http://www.google.com。',
+	currencyDollar:'请输入�法的货�符�,例如:¥',
+	oneRequired:'请至少选择一项。',
+	errorPrefix: '错误:',
+	warningPrefix: '警告:'
+});
+
+// Traditional Chinese
+MooTools.lang.set('zht-CN', 'Form.Validator', {
+	required:'這是必填項。',
+	minLength:'請至少�入 {minLength} 個字符(已�入 {length} 個)。',
+	maxLength:'最多�能�入 {maxLength} 個字符(已�入 {length} 個)。',
+	integer:'請�入一個整數,�能包��數點。例如:"1", "200"。',
+	numeric:'請�入一個數字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'這裡�能接�數字和標點的�入,標點�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'請�入 A-Z 的 26 個字�,�能包�空格或任何其他字符。',
+	alphanum:'請�入 A-Z 的 26 個字�或 0-9 的 10 個數字,�能包�空格或任何其他字符。',
+	dateSuchAs:'請�入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'請�入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'請�入�法的電�信箱地�,例如:"fred at domain.com"。',
+	url:'請�入�法的 Url 地�,例如:http://www.google.com。',
+	currencyYuan:'請�入�法的貨幣符號,例如:¥',
+	oneRequired:'請至少�擇一項。',
+	errorPrefix: '錯誤:',
+	warningPrefix: '警告:'
+});
+
+Form.Validator.add('validate-currency-yuan', {
+	errorMsg: function(){
+		return Form.Validator.getMsg('currencyYuan');
+	},
+	test: function(element) {
+		// [ï¿¥]1[##][,###]+[.##]
+		// [ï¿¥]1###+[.##]
+		// [ï¿¥]0.##
+		// [ï¿¥].##
+		return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+	}
+});
+/*
+---
+
+script: Form.Validator.Dutch.js
+
+description: Form.Validator messages in Dutch.
+
+license: MIT-style license
+
+authors:
+- Lennart Pilon
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Form.Validator', {
+	required:'Dit veld is verplicht.',
+	minLength:'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+	maxLength:'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+	integer:'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1,25) zijn niet toegestaan.',
+	numeric:'Vul alleen numerieke waarden in (bijvoorbeeld. "1" of "1.1" of "-1" of "-1.1").',
+	digits:'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met een streepje).',
+	alpha:'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+	alphanum:'Vul alleen letters in (a-z) of nummers (0-9). Spaties en andere karakters zijn niet toegestaan.',
+	dateSuchAs:'Vul een geldige datum in, zoals {date}',
+	dateInFormatMDY:'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+	email:'Vul een geldig e-mailadres in. Bijvoorbeeld "fred at domein.nl".',
+	url:'Vul een geldige URL in, zoals http://www.google.nl.',
+	currencyDollar:'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+	oneRequired:'Vul iets in bij minimaal een van de invoervelden.',
+	warningPrefix: 'Waarschuwing: ',
+	errorPrefix: 'Fout: '
+});/*
+---
+
+script: Form.Validator.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+- Kevin Valdek
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Form.Validator', {
+
+	required:'Väli peab olema täidetud.',
+	minLength:'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
+	maxLength:'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
+	integer:'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
+	numeric:'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
+	digits:'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+	alpha:'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
+	alphanum:'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
+	dateSuchAs:'Palun sisestage kehtiv kuupäev kujul {date}',
+	dateInFormatMDY:'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
+	email:'Palun sisestage kehtiv e-maili aadress (näiteks: "fred at domain.com").',
+	url:'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
+	currencyDollar:'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
+	oneRequired:'Palun sisestage midagi vähemalt ühele antud väljadest.',
+	errorPrefix: 'Viga: ',
+	warningPrefix: 'Hoiatus: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Väli ei tohi sisaldada tühikuid.',
+	reqChkByNode: 'Ükski väljadest pole valitud.',
+	requiredChk: 'Välja täitmine on vajalik.',
+	reqChkByName: 'Palun valige üks {label}.',
+	match: 'Väli peab sobima {matchName} väljaga',
+	startDate: 'algkuupäev',
+	endDate: 'lõppkuupäev',
+	currendDate: 'praegune kuupäev',
+	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
+	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
+	startMonth: 'Palun valige algkuupäev.',
+	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'
+
+});/*
+---
+
+script: Form.Validator.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors: 
+- Frank Rossi
+- Ulrich Petri
+- Fabian Beiner
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Form.Validator', {
+
+	required: 'Dieses Eingabefeld muss ausgef&uuml;llt werden.',
+	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+	maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+	integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.',
+	numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.',
+	digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+	alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+	alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).',
+	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.1999&quot;).',
+	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max at mustermann.de&quot;).',
+	url: 'Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).',
+	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).',
+	oneRequired: 'Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.',
+	errorPrefix: 'Fehler: ',
+	warningPrefix: 'Warnung: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+	requiredChk: 'Dieses Feld muss ausgef&uuml;llt werden.',
+	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+	match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.',
+	startDate: 'Das Anfangsdatum',
+	endDate: 'Das Enddatum',
+	currendDate: 'Das aktuelle Datum',
+	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.',
+	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.',
+	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.',
+	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+});
+/*
+---
+
+script: Form.Validator.German.CH.js
+
+description: Date messages for German (Switzerland).
+ 
+license: MIT-style license
+ 
+authors:
+- Michael van der Weg
+
+requires:
+- /Lang
+- /Form.Validator.German
+
+provides: [Form.Validator.German.CH]
+
+...
+*/
+ 
+MooTools.lang.set('de-CH', 'Form.Validator', {
+ 
+  required:'Dieses Feld ist obligatorisch.',
+  minLength:'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  maxLength:'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  integer:'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+  numeric:'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+  digits:'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+  alpha:'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  alphanum:'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  dateSuchAs:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+  dateInFormatMDY:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+  email:'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria at bernasconi.ch&quot;.',
+  url:'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.',
+  currencyDollar:'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+  oneRequired:'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+  errorPrefix: 'Fehler: ',
+  warningPrefix: 'Warnung: ',
+ 
+  //Form.Validator.Extras
+ 
+  noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+  reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+  requiredChk: 'Dieses Feld ist obligatorisch.',
+  reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+  match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+  startDate: 'Das Anfangsdatum',
+  endDate: 'Das Enddatum',
+  currendDate: 'Das aktuelle Datum',
+  afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+  beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+  startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+  sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.'
+ 
+});/*
+---
+
+script: Form.Validator.French.js
+
+description: Form.Validator messages in French.
+
+license: MIT-style license
+
+authors: 
+- Miquel Hudin
+- Nicolas Sorosac <nicolas <dot> sorosac <at> gmail <dot> com>
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Form.Validator', {
+  required:'Ce champ est obligatoire.',
+  minLength:'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  maxLength:'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  integer:'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+  numeric:'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+  digits:'Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d\'union est autoris&eacute;).',
+  alpha:'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  alphanum:'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  dateSuchAs:'Veuillez saisir une date correcte comme {date}',
+  dateInFormatMDY:'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+  email:'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred at domaine.com".',
+  url:'Veuillez saisir une URL, comme http://www.google.com.',
+  currencyDollar:'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
+  oneRequired:'Veuillez s&eacute;lectionner au moins une de ces options.',
+  errorPrefix: 'Erreur : ',
+  warningPrefix: 'Attention : ',
+  
+  //Form.Validator.Extras
+ 
+  noSpace: 'Ce champ n\'accepte pas les espaces.',
+  reqChkByNode: 'Aucun &eacute;l&eacute;ment n\'est s&eacute;lectionn&eacute;.',
+  requiredChk: 'Ce champ est obligatoire.',
+  reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+  match: 'Ce champ doit correspondre avec le champ {matchName}.',
+  startDate: 'date de d&eacute;but',
+  endDate: 'date de fin',
+  currendDate: 'date actuelle',
+  afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+  beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+  startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+  sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'
+ 
+});/*
+---
+
+script: Form.Validator.Italian.js
+
+description: Form.Validator messages in Italian.
+
+license: MIT-style license
+
+authors:
+- Leonardo Laureti
+- Andrea Novero
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Form.Validator', {
+
+	required:'Il campo &egrave; obbligatorio.',
+	minLength:'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+	maxLength:'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+	integer:'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+	numeric:'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+	digits:'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+	alpha:'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+	alphanum:'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+	dateSuchAs:'Inserire una data valida del tipo {date}',
+	dateInFormatMDY:'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+	email:'Inserire un indirizzo email valido. Per esempio "nome at dominio.com".',
+	url:'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
+	currencyDollar:'Inserire un importo valido. Per esempio "$100.00".',
+	oneRequired:'Completare almeno uno dei campi richiesti.',
+	errorPrefix: 'Errore: ',
+	warningPrefix: 'Attenzione: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Non sono consentiti spazi.',
+	reqChkByNode: 'Nessuna voce selezionata.',
+	requiredChk: 'Il campo &egrave; obbligatorio.',
+	reqChkByName: 'Selezionare un(a) {label}.',
+	match: 'Il valore deve corrispondere al campo {matchName}',
+	startDate: 'data d\'inizio',
+	endDate: 'data di fine',
+	currendDate: 'data attuale',
+	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+	startMonth: 'Selezionare un mese d\'inizio',
+	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});/*
+---
+
+script: Form.Validator.Norwegian.js
+
+description: Form.Validator messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+- Espen 'Rexxars' Hovlandsdal
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Form.Validator', {
+   required:'Dette feltet er påkrevd.',
+   minLength:'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+   maxLength:'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+   integer:'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+   numeric:'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+   digits:'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+   alpha:'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   alphanum:'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   dateSuchAs:'Vennligst skriv inn en gyldig dato, som {date}',
+   dateInFormatMDY:'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+   email:'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen at domene.no".',
+   url:'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
+   currencyDollar:'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .',
+   oneRequired:'Vennligst fyll ut noe i minst ett av disse feltene.',
+   errorPrefix: 'Feil: ',
+   warningPrefix: 'Advarsel: '
+});/*
+---
+
+script: Form.Validator.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+- Oskar Krawczyk
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Form.Validator', {
+
+	required:'To pole jest wymagane.',
+	minLength:'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+	maxLength:'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})',
+	integer:'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+	numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+	digits:'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+	alpha:'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	alphanum:'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	dateSuchAs:'Prosimy podać prawidłową datę w formacie: {date}',
+	dateInFormatMDY:'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+	email:'Prosimy podać prawidłowy adres e-mail, np. "jan at domena.pl".',
+	url:'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
+	currencyDollar:'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+	oneRequired:'Prosimy wypełnić chociaż jedno z pól.',
+	errorPrefix: 'BÅ‚Ä…d: ',
+	warningPrefix: 'Uwaga: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'W tym polu nie mogą znajdować się spacje.',
+	reqChkByNode: 'Brak zaznaczonych elementów.',
+	requiredChk: 'To pole jest wymagane.',
+	reqChkByName: 'Prosimy wybrać z {label}.',
+	match: 'To pole musi być takie samo jak {matchName}',
+	startDate: 'data poczÄ…tkowa',
+	endDate: 'data końcowa',
+	currendDate: 'aktualna data',
+	afterDate: 'Podana data poinna być taka sama lub po {label}.',
+	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+	startMonth: 'Prosimy wybrać początkowy miesiąc.',
+	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});/*
+---
+
+script: Form.Validator.Portuguese.js
+
+description: Form.Validator messages in Portuguese.
+
+license: MIT-style license
+
+authors:
+- Miquel Hudin
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Portuguese]
+
+...
+*/
+
+MooTools.lang.set('pt-PT', 'Form.Validator', {
+	required:'Este campo é necessário.',
+	minLength:'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+	maxLength:'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+	integer:'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+	numeric:'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits:'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+	alpha:'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+	alphanum:'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+	dateSuchAs:'Digite uma data válida, como {date}',
+	dateInFormatMDY:'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+	email:'Digite um endereço de email válido. Por exemplo "fred at domain.com".',
+	url:'Digite uma URL válida, como http://www.google.com.',
+	currencyDollar:'Digite um valor válido $. Por exemplo $ 100,00. ',
+	oneRequired:'Digite algo para pelo menos um desses insumos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: '
+
+}).set('pt-PT', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Portuguese.BR.js
+
+description: Form.Validator messages in Portuguese-BR.
+
+license: MIT-style license
+
+authors:
+- Fábio Miranda Costa
+
+requires:
+- /Lang
+- /Form.Validator.Portuguese
+
+provides: [Form.Validator.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Form.Validator', {
+
+	required: 'Este campo é obrigatório.',
+	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+	dateSuchAs: 'Digite uma data válida, como {date}',
+	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+	email: 'Digite um endereço de email válido. Por exemplo "nome at dominio.com".',
+	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
+	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+	oneRequired: 'Digite algo para pelo menos um desses campos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Não é possível digitar espaços neste campo.',
+	reqChkByNode: 'Não foi selecionado nenhum item.',
+	requiredChk: 'Este campo é obrigatório.',
+	reqChkByName: 'Por favor digite um {label}.',
+	match: 'Este campo deve ser igual ao campo {matchName}.',
+	startDate: 'a data inicial',
+	endDate: 'a data final',
+	currendDate: 'a data atual',
+	afterDate: 'A data deve ser igual ou posterior a {label}.',
+	beforeDate: 'A data deve ser igual ou anterior a {label}.',
+	startMonth: 'Por favor selecione uma data inicial.',
+	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});/*
+---
+
+script: Form.Validator.Russian.js
+
+description: Form.Validator messages in Russian (utf-8 and cp1251).
+
+license: MIT-style license
+
+authors:
+- Chernodarov Egor
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Russian]
+
+...
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {
+	required:'Это поле обÑ?зательно к заполнению.',
+	minLength:'ПожалуйÑ?та, введите хотÑ? бы {minLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	maxLength:'ПожалуйÑ?та, введите не больше {maxLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	integer:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло. Дробные чиÑ?ла (например 1.25) тут не разрешены.',
+	numeric:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло (например "1" или "1.1", или "-1", или "-1.1").',
+	digits:'Ð’ Ñ?том поле Ð’Ñ‹ можете иÑ?пользовать только цифры и знаки пунктуации (например, телефонный номер Ñ?о знаками дефиÑ?а или Ñ? точками).',
+	alpha:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z). Пробелы и другие Ñ?имволы запрещены.',
+	alphanum:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z) и цифры (0-9). Пробелы и другие Ñ?имволы запрещены.',
+	dateSuchAs:'ПожалуйÑ?та, введите корректную дату {date}',
+	dateInFormatMDY:'ПожалуйÑ?та, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
+	email:'ПожалуйÑ?та, введите корректный емейл-адреÑ?. ДлÑ? примера "fred at domain.com".',
+	url:'ПожалуйÑ?та, введите правильную Ñ?Ñ?ылку вида http://www.google.com.',
+	currencyDollar:'ПожалуйÑ?та, введите Ñ?умму в долларах. Ð?апример: $100.00 .',
+	oneRequired:'ПожалуйÑ?та, выберите хоть что-нибудь в одном из Ñ?тих полей.',
+	errorPrefix: 'Ошибка: ',
+	warningPrefix: 'Внимание: '
+});
+
+//translation in windows-1251 codepage
+MooTools.lang.set('ru-RU', 'Form.Validator', {
+	required:'�òî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
+	minLength:'�îæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	maxLength:'�îæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	integer:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
+	numeric:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
+	digits:' ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
+	alpha:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	alphanum:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	dateSuchAs:'�îæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
+	dateInFormatMDY:'�îæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃà (íàïðèìåð "12/31/1999")',
+	email:'�îæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred at domain.com".',
+	url:'�îæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
+	currencyDollar:'�îæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. �àïðèìåð: $100.00 .',
+	oneRequired:'�îæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
+	errorPrefix: 'Îøèáêà: ',
+	warningPrefix: 'Âíèìàíèå: '
+});/*
+---
+
+script: Form.Validator.Spanish.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+- Ãlfons Sanchez
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Form.Validator', {
+
+	required:'Este campo es obligatorio.',
+	minLength:'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+	maxLength:'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+	integer:'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+	numeric:'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+	alpha:'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+	alphanum:'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+	dateSuchAs:'Por favor introduce una fecha v&aacute;lida como {date}',
+	dateInFormatMDY:'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred at domain.com".',
+	url:'Por favor introduce una URL v&aacute;lida como http://www.google.com.',
+	currencyDollar:'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+	oneRequired:'Por favor introduce algo para por lo menos una de estas entradas.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No pueden haber espacios en esta entrada.',
+	reqChkByNode: 'No hay elementos seleccionados.',
+	requiredChk: 'Este campo es obligatorio.',
+	reqChkByName: 'Por favor selecciona una {label}.',
+	match: 'Este campo necesita coincidir con el campo {matchName}',
+	startDate: 'la fecha de inicio',
+	endDate: 'la fecha de fin',
+	currendDate: 'la fecha actual',
+	afterDate: 'La fecha debe ser igual o posterior a {label}.',
+	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+	startMonth: 'Por favor selecciona un mes de origen',
+	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+/*
+---
+
+script: Form.Validator.Swedish.js
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+- Martin Lundgren
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Form.Validator', {
+
+	required:'Fältet är obligatoriskt.',
+	minLength:'Ange minst {minLength} tecken (du angav {length} tecken).',
+	maxLength:'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+	integer:'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
+	numeric:'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+	digits:'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
+	alpha:'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	alphanum:'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	dateSuchAs:'Ange ett giltigt datum som t.ex. {date}',
+	dateInFormatMDY:'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+	email:'Ange en giltig e-postadress. Till exempel "erik at domain.com".',
+	url:'Ange en giltig webbadress som http://www.google.com.',
+	currencyDollar:'Ange en giltig belopp. Exempelvis 100,00.',
+	oneRequired:'Vänligen ange minst ett av dessa alternativ.',
+	errorPrefix: 'Fel: ',
+	warningPrefix: 'Varning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
+	reqChkByNode: 'Inga objekt är valda.',
+	requiredChk: 'Detta är ett obligatoriskt fält.',
+	reqChkByName: 'Välj en {label}.',
+	match: 'Detta fält måste matcha {matchName}',
+	startDate: 'startdatumet',
+	endDate: 'slutdatum',
+	currendDate: 'dagens datum',
+	afterDate: 'Datumet bör vara samma eller senare än {label}.',
+	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
+	startMonth: 'Välj en start månad',
+	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'
+
+});/*
+---
+
+script: Form.Validator.Ukrainian.js
+
+description: Form.Validator messages in Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+- Slik
+
+requires:
+- /Lang
+- /Form.Validator
+
+provides: [Form.Validator.Ukrainian]
+
+...
+*/
+
+MooTools.lang.set('uk-UA', 'Form.Validator', {
+	required:'Це поле повинне бути заповненим.',
+	minLength:'Введіть хоча б {minLength} Ñ?имволів (Ви ввели {length}).',
+	maxLength:'КількіÑ?Ñ‚ÑŒ Ñ?имволів не може бути більше {maxLength} (Ви ввели {length}).',
+	integer:'Введіть в це поле чиÑ?ло. Дробові чиÑ?ла (наприклад 1.25) не дозволені.',
+	numeric:'Введіть в це поле чиÑ?ло (наприклад "1" або "1.1", або "-1", або "-1.1").',
+	digits:'Ð’ цьому полі ви можете викориÑ?товувати лише цифри Ñ– знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).',
+	alpha:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	alphanum:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z) Ñ– цифри (0-9). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	dateSuchAs:'Введіть коректну дату {date}.',
+	dateInFormatMDY:'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',
+	email:'Введіть коректну адреÑ?у електронної пошти (наприклад "name at domain.com").',
+	url:'Введіть коректне інтернет-поÑ?иланнÑ? (наприклад http://www.google.com).',
+	currencyDollar:'Введіть Ñ?уму в доларах (наприклад "$100.00").',
+	oneRequired:'Заповніть одне з полів.',
+	errorPrefix: 'Помилка: ',
+	warningPrefix: 'Увага: '
+});
+// $Id: common.js 845 2010-04-15 18:47:08Z pagameba $
+/**
+ * Function: $jx
+ * dereferences a DOM Element to a JxLib object if possible and returns
+ * a reference to the object, or null if not defined.
+ */
+function $jx(id) {
+  id = document.id(id);
+  if (id) {
+    var widget = id.retrieve('jxWidget');
+    if (!widget && id != document.body) {
+      return $jx(id.getParent());
+    } else {
+      return widget;
+    }
+  }
+  return null;
+}
+
+/**
+ * Class: Jx
+ * Jx is a global singleton object that contains the entire Jx library
+ * within it.  All Jx functions, attributes and classes are accessed
+ * through the global Jx object.  Jx should not create any other
+ * global variables, if you discover that it does then please report
+ * it as a bug
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+/* firebug console supressor for IE/Safari/Opera */
+window.addEvent('load',
+function() {
+    if (! ("console" in window)) {
+        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+        "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
+
+        window.console = {};
+        for (var i = 0; i < names.length; ++i) {
+            window.console[names[i]] = function() {};
+        }
+    }
+});
+
+// add mutator that sets jxFamily when creating a class so we can check
+// its type
+Class.Mutators.Family = function(self, name) {
+    if ($defined(name)) {
+        self.jxFamily = name;
+        return self;
+    }
+    else if(!$defined(this.prototype.jxFamily)) {
+        this.implement({
+            'jxFamily': self
+        });
+    }
+};
+
+// this replaces the mootools $unlink method with our own version that
+// avoids infinite recursion on Jx objects.
+function $unlink(object) {
+    if (object && object.jxFamily) {
+        return object;
+    }
+    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.  It is possible to set the global namespace
+ * prior to including jxlib.  This would typically be required only if
+ * the auto-detection of the jxlib base URL would fail.  For instance,
+ * if you combine jxlib with other javascript libraries into a single file
+ * build and call it something without jxlib in the file name, then the
+ * detection of baseURL would fail.  If this happens to you, try adding
+ * Jx = { baseURL: '/path/to/jxlib/'; }
+ * where the path to jxlib contains a file called a_pixel.png (it doesn't
+ * have to include jxlib, just the a_pixel.png file).
+ */
+if (typeof Jx === 'undefined') {
+  var Jx = {};
+}
+
+/**
+ * APIProperty: {String} baseURL
+ * This is the URL that Jx was loaded from, it is 
+ * automatically calculated from the script tag
+ * src property that included Jx.
+ *
+ * Note that this assumes that you are loading Jx
+ * from a js/ or lib/ folder in parallel to the
+ * images/ folder that contains the various images
+ * needed by Jx components.  If you have a different
+ * folder structure, you can define Jx's base
+ * by including the following before including
+ * the jxlib javascript file:
+ *
+ * (code)
+ * Jx = {
+ *    baseURL: 'some/path'
+ * }
+ * (end)
+ */
+if (!$defined(Jx.baseURL)) {
+  (function() {
+    var aScripts = document.getElementsByTagName('SCRIPT');
+    for (var i = 0; i < aScripts.length; i++) {
+      var s = aScripts[i].src;
+      var n = s.lastIndexOf('/');
+      var file = s.slice(n+1,s.length-1);
+      if (file.contains('jxlib')) {
+        Jx.baseURL = s.slice(0,n);
+        break;
+      }
+    }
+  })();
+}
+/** 
+ * APIProperty: {Image} aPixel
+ * aPixel is a single transparent pixel and is the only image we actually
+ * use directly in JxLib code.  If you want to use your own transparent pixel
+ * image or use it from a different location than the install of jxlib
+ * javascript files, you can manually declare it before including jxlib code
+ * (code)
+ * Jx = {
+ *   aPixel: new Element('img', {
+ *     alt: '',
+ *     title: '',
+ *     width: 1,
+ *     height: 1,
+ *     src: 'path/to/a/transparent.png'
+ *   });
+ * }
+ * (end)
+ */
+if (!$defined(Jx.aPixel)) {
+  Jx.aPixel = new Element('img', {
+    alt:'',
+    title:'',
+    src: Jx.baseURL +'/a_pixel.png'
+  });
+}
+
+/** 
+ * APIProperty: {Boolean} isAir
+ * indicates if JxLib is running in an Adobe Air environment.  This is
+ * normally auto-detected but you can manually set it by declaring the Jx
+ * namespace before including jxlib:
+ * (code)
+ * Jx = {
+ *   isAir: true
+ * }
+ * (end)
+ */
+if (!$defined(Jx.isAir)) {
+	(function() {
+		/**
+		 * Determine if we're running in Adobe AIR.
+		 */
+		var aScripts = document.getElementsByTagName('SCRIPT');
+		var src = aScripts[0].src;
+		if (src.contains('app:')) {
+			Jx.isAir = true;
+		} else {
+			Jx.isAir = false;
+		}
+	})();
+}
+
+/**
+ * APIMethod: setLanguage
+ * set the current language to be used by Jx widgets.  This uses the MooTools
+ * lang module.  If an invalid or missing language is requested, the default
+ * rules of MooTools.lang will be used (revert to en-US at time of writing).
+ *
+ * Parameters:
+ * {String} language identifier, the language to set.
+ */
+Jx.setLanguage = function(lang) {
+	Jx.lang = lang;
+	MooTools.lang.setLanguage(Jx.lang);
+};
+
+/**
+ * APIProperty: {String} lang
+ * Checks to see if Jx.lang is already set. If not, it sets it to the default
+ * 'en-US'. We will then set the Motools.lang language to this setting 
+ * automatically.
+ * 
+ * The language can be changed on the fly at anytime by calling
+ * Jx.setLanguage().
+ * By default all Jx.Widget subclasses will listen for the langChange event of
+ * the Mootools.lang class. It will then call a method, changeText(), if it
+ * exists on the particular widget. You will be able to disable listening for
+ * these changes by setting the Jx.Widget option useLang to false.
+ */
+if (!$defined(Jx.lang)) {
+	Jx.lang = 'en-US';
+}
+
+Jx.setLanguage(Jx.lang);
+
+/**
+ * APIMethod: applyPNGFilter
+ *
+ * Static method that applies the PNG Filter Hack for IE browsers
+ * when showing 24bit PNG's.  Used automatically for img tags with
+ * a class of png24.
+ *
+ * The filter is applied using a nifty feature of IE that allows javascript to
+ * be executed as part of a CSS style rule - this ensures that the hack only
+ * gets applied on IE browsers.
+ *
+ * The CSS that triggers this hack is only in the ie6.css files of the various
+ * themes.
+ *
+ * Parameters:
+ * object {Object} the object (img) to which the filter needs to be applied.
+ */
+Jx.applyPNGFilter = function(o) {
+    var t = Jx.aPixel.src;
+    if (o.src != t) {
+        var s = o.src;
+        o.src = t;
+        o.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + s + "',sizingMethod='scale')";
+    }
+};
+
+/**
+ * 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
+/**
+ * APIMethod: addToImgQueue
+ * Request that an image be set to a DOM IMG element src attribute.  This puts 
+ * the image into a queue and there are private methods to manage that queue
+ * and limit image loading to 2 at a time.
+ *
+ * Parameters:
+ * obj - {Object} an object containing an element and src
+ * property, where element is the element to update and src
+ * is the url to the image.
+ */
+Jx.addToImgQueue = function(obj) {
+    if (Jx.imgLoaded[obj.src]) {
+        //if this image was already requested (i.e. it's in cache) just set it directly
+        obj.element.src = obj.src;
+    } else {
+        //otherwise stick it in the queue
+        Jx.imgQueue.push(obj);
+        Jx.imgLoaded[obj.src] = true;
+    }
+    //start the queue management process
+    Jx.checkImgQueue();
+};
+
+/**
+ * APIMethod: checkImgQueue
+ *
+ * An internal method that ensures no more than 2 images are loading at a
+ * time.
+ */
+Jx.checkImgQueue = function() {
+    while (Jx.imagesLoading < 2 && Jx.imgQueue.length > 0) {
+        Jx.loadNextImg();
+    }
+};
+
+/**
+ * Method: loadNextImg
+ *
+ * An internal method actually populate the DOM element with the image source.
+ */
+Jx.loadNextImg = function() {
+    var obj = Jx.imgQueue.shift();
+    if (obj) {
+        ++Jx.imagesLoading;
+        obj.element.onload = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.onerror = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.src = obj.src;
+    }
+};
+
+/**
+ * APIMethod: getNumber
+ * safely parse a number and return its integer value.  A NaN value 
+ * returns 0.  CSS size values are also parsed correctly.
+ *
+ * Parameters: 
+ * n - {Mixed} the string or object to parse.
+ *
+ * Returns:
+ * {Integer} the integer value that the parameter represents
+ */
+Jx.getNumber = function(n, def) {
+    var result = n === null || isNaN(parseInt(n, 10)) ? (def || 0) : parseInt(n, 10);
+    return result;
+};
+
+/**
+ * APIMethod: getPageDimensions
+ * return the dimensions of the browser client area.
+ *
+ * Returns:
+ * {Object} an object containing a width and height property 
+ * that represent the width and height of the browser client area.
+ */
+Jx.getPageDimensions = function() {
+    return {
+        width: window.getWidth(),
+        height: window.getHeight()
+    };
+};
+
+/**
+ * APIMethod: type
+ * safely return the type of an object using the mootools type system
+ *
+ * Returns:
+ * {Object} an object containing a width and height property 
+ * that represent the width and height of the browser client area.
+ */
+Jx.type = function(obj) {
+    if (typeof obj == 'undefined') {
+        return false;
+    }
+    return obj.jxFamily || $type(obj);
+};
+
+(function($) {
+    // Wrapper for document.id
+
+    /**
+     * Class: Element
+     *
+     * Element is a global object provided by the mootools library.  The
+     * functions documented here are extensions to the Element object provided
+     * by Jx to make cross-browser compatibility easier to achieve.  Most of
+     * the methods are measurement related.
+     *
+     * While the code in these methods has been converted to use MooTools
+     * methods, there may be better MooTools methods to use to accomplish
+     * these things.
+     * Ultimately, it would be nice to eliminate most or all of these and find
+     * the MooTools equivalent or convince MooTools to add them.
+     * 
+     * NOTE: Many of these methods can be replaced with mootools-more's 
+     * Element.Measure
+     */
+    Element.implement({
+        /**
+         * APIMethod: getBoxSizing
+         * return the box sizing of an element, one of 'content-box' or 
+         *'border-box'.
+         *
+         * Parameters: 
+         * elem - {Object} the element to get the box sizing of.
+         *
+         * Returns:
+         * {String} the box sizing of the element.
+         */
+        getBoxSizing: function() {
+            var result = 'content-box';
+            if (Browser.Engine.trident || Browser.Engine.presto) {
+                var cm = document["compatMode"];
+                if (cm == "BackCompat" || cm == "QuirksMode") {
+                    result = 'border-box';
+                } else {
+                    result = 'content-box';
+                }
+            } else {
+                if (arguments.length === 0) {
+                    node = document.documentElement;
+                }
+                var sizing = this.getStyle("-moz-box-sizing");
+                if (!sizing) {
+                    sizing = this.getStyle("box-sizing");
+                }
+                result = (sizing ? sizing: 'content-box');
+            }
+            return result;
+        },
+        /**
+         * APIMethod: getContentBoxSize
+         * return the size of the content area of an element.  This is the
+         * size of the element less margins, padding, and borders.
+         *
+         * Parameters: 
+         * elem - {Object} the element to get the content size of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the content area of the measured element.
+         */
+        getContentBoxSize: function() {
+            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
+            };
+        },
+        /**
+         * APIMethod: getBorderBoxSize
+         * return the size of the border area of an element.  This is the size
+         * of the element less margins.
+         *
+         * Parameters: 
+         * elem - {Object} the element to get the border sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * 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
+            };
+        },
+
+        /**
+         * APIMethod: getMarginBoxSize
+         * return the size of the margin area of an element.  This is the size
+         * of the element plus margins.
+         *
+         * Parameters: 
+         * elem - {Object} the element to get the margin sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the margin area of the measured element.
+         */
+        getMarginBoxSize: function() {
+            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
+            };
+        },
+        /**
+         * APIMethod: 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;
+        },
+        /**
+         * APIMethod: setContentBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the content
+         * area of the element is the requested size and the resulting
+         * size of the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters: 
+         * elem - {Object} the element to set the content area of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setContentBoxSize: function(size) {
+            if (this.getBoxSizing() == 'border-box') {
+                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 ($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 ($defined(size.width) && size.width >= 0) {
+                    this.style.width = size.width + 'px';
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                    this.style.height = size.height + 'px';
+                }
+            }
+        },
+        /**
+         * APIMethod: setBorderBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the border
+         * size of the element is the requested size and the resulting
+         * content areaof the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters: 
+         * elem - {Object} the element to set the border size of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setBorderBoxSize: function(size) {
+            if (this.getBoxSizing() == 'content-box') {
+                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 ($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 ($defined(size.width) && size.width >= 0) {
+                    this.style.width = size.width + 'px';
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                    this.style.height = size.height + 'px';
+                }
+            }
+        },
+
+        /**
+         * APIMethod: descendantOf
+         * determines if the element is a descendent of the reference node.
+         *
+         * Parameters:
+         * node - {HTMLElement} the reference node
+         *
+         * Returns:
+         * {Boolean} true if the element is a descendent, false otherwise.
+         */
+        descendantOf: function(node) {
+            var parent = document.id(this.parentNode);
+            while (parent != node && parent && parent.parentNode && parent.parentNode != parent) {
+                parent = document.id(parent.parentNode);
+            }
+            return parent == node;
+        },
+
+        /**
+         * APIMethod: findElement
+         * search the parentage of the element to find an element of the given
+         * tag name.
+         *
+         * Parameters:
+         * type - {String} the tag name of the element type to search for
+         *
+         * Returns:
+         * {HTMLElement} the first node (this one or first parent) with the
+         * requested tag name or false if none are found.
+         */
+        findElement: function(type) {
+            var o = this;
+            var tagName = o.tagName;
+            while (o.tagName != type && o && o.parentNode && o.parentNode != o) {
+                o = document.id(o.parentNode);
+            }
+            return o.tagName == type ? o: false;
+        }
+    });
+    /**
+     * Class: Array
+     * Extensions to the javascript array object
+     */
+    Array.implement({
+        /**
+         * APIMethod: 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 = this[a];
+            this[a] = this[b];
+            this[b] = temp;
+        }
+    });
+})(document.id || $);
+// End Wrapper for document.id
+/**
+ * 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
+ * 
+ * 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(),
+    /**
+     * APIMethod: getCssRule
+     * retrieve a reference to a CSS rule in a specific style sheet based on
+     * its selector.  If the rule does not exist, create it.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * 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;
+    },
+    /**
+     * APIMethod: insertCssRule
+     * insert a new dynamic rule into the given stylesheet.  If no name is
+     * given for the stylesheet then the default stylesheet is used.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * declaration - <String> CSS-formatted rules to include.  May be empty,
+     * in which case you may want to use the returned rule object to
+     * manipulate styles
+     * 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') {
+            if (declaration == '') {
+                //IE requires SOME text for the declaration. Passing '{}' will
+                //create an empty rule.
+                declaration = '{}';
+            }
+            var index = ss.styleSheet.addRule(selector,declaration);
+            rule = ss.styleSheet.rules[index];
+        } else {
+            ss.sheet.insertRule(text, ss.indicies.length);
+            rule = ss.sheet.cssRules[ss.indicies.length];
+        }
+        ss.indicies.push(selector);
+        return rule;
+    },
+    /**
+     * APIMethod: removeCssRule
+     * removes a CSS rule from the named stylesheet.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * styleSheetName - <String> the name of the sheet to remove the rule
+     * from,  or empty to remove them from the default sheet.
+     *
+     * 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;
+    },
+    /**
+     * APIMethod: 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').inject(document.head);
+            sheet.indicies = [];
+            this.dynamicStyleMap.set(name, sheet);
+        }
+        return this.dynamicStyleMap.get(name);
+    },
+    /**
+     * APIMethod: enableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to enable
+     */
+    enableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = false;
+    },
+    /**
+     * APIMethod: disableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to disable
+     */
+    disableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = true;
+    },
+    /**
+     * APIMethod: removeStyleSheet
+     * Removes a style sheet
+     * 
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    removeStyleSheet: function (name) {
+      this.disableStyleSheet(name);
+      this.getDynamicStyleSheet(name).dispose();
+      this.dynamicStyleMap.erase(name);
+    },
+    /**
+     * APIMethod: isStyleSheetDefined
+     * Determined if the passed in name is a defined dynamic style sheet.
+     * 
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    isStyleSheetDefined: function (name) {
+      return this.dynamicStyleMap.has(name);
+    }
+}))();// $Id: object.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * 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.
+ *
+ * The Initialization Pipeline:
+ * Jx.Object provides a default initialize method to construct new instances
+ * of objects that inherit from it.  No sub-class should override initialize
+ * unless you know exactly what you're doing.  Instead, the initialization
+ * pipeline provides an init() method that is intended to be overridden in
+ * sub-classes to provide class-specific initialization as part of the
+ * initialization pipeline.
+ *
+ * The basic initialization pipeline for a Jx.Object is to parse the
+ * parameters provided to initialize(), separate out options from other formal
+ * parameters based on the parameters property of the class, call init() and
+ * initialize plugins.  
+ *
+ * Parsing Parameters:
+ * Because each sub-class no longer has an initialize method, it no longer has
+ * direct access to parameters passed to the constructor.  Instead, a 
+ * sub-class is expected to provide a parameters attribute with an array of
+ * parameter names in the order expected.  Jx.Object will enumerate the 
+ * attributes passed to its initialize method and automatically place them
+ * in the options object under the appropriate key (the value from the 
+ * array).  Parameters not found will not be present or will be null.
+ *
+ * The default parameters are a single options object which is merged with
+ * the options attribute of the class.
+ *
+ * Calling Init:
+ * Jx.Object fires the event 'preInit' before calling the init() method,
+ * calls the init() method, then fires the 'postInit' event.  It is expected
+ * that most sub-class specific initialization will happen in the init()
+ * method.  A sub-class may hook preInit and postInit events to perform tasks
+ * in one of two ways. 
+ * 
+ * First, simply send onPreInit and onPostInit functions via the options
+ * object as follows (they could be standalone functions or functions of
+ * another object setup using .bind())
+ * 
+ * (code)
+ * var preInit = function () {}
+ * var postInit = function () {}
+ * 
+ * var options = {
+ *   onPreInit: preInit,
+ *   onPostInit: postInit,
+ *   ...other options...
+ * };
+ * 
+ * var dialog = new Jx.Dialog(options);
+ * (end)
+ * 
+ * The second method you can use is to override the initialize method
+ *
+ * (code)
+ * var MyClass = new Class({
+ *   Family: 'MyClass',
+ *   initialize: function() {
+ *     this.addEvent('preInit', this.preInit.bind(this));
+ *     this.addEvent('postInit', this.postInit.bind(this));
+ *     this.parent.apply(this, arguments);
+ *   },
+ *   preInit: function() {
+ *     // something just before init() is called
+ *   },
+ *   postInit: function() {
+ *     // something just after init() is called
+ *   },
+ *   init: function() {
+ *     this.parent();
+ *     // initialization code here
+ *   }
+ * });
+ * (end)
+ * 
+ * When the object finishes initializing itself (including the plugin
+ * initialization) it will fire off the initializeDone event. You can hook
+ * into this event in the same way as the events mentioned above.
+ *
+ * Plugins:
+ * Plugins provide pieces of additional, optional, functionality. They are not 
+ * necessary for the proper function of an object. All plugins should be
+ * located in the Jx.Plugin namespace and they should be further segregated by
+ * applicable object. While all objects can support plugins, not all of them
+ * have the automatic instantiation of applicable plugins turned on. In order
+ * to turn this feature on for an object you need to set the pluginNamespace
+ * property of the object. The following is an example of setting the
+ * property:
+ * 
+ * (code)
+ * var MyClass = new Class({
+ *   Extends: Jx.Object,
+ *   pluginNamespace: 'MyClass'
+ * };
+ * (end)
+ * 
+ * The absence of this property does not mean you cannot attach a plugin to an 
+ * object. It simply means that you can't have Jx.Object create the
+ * plugin for you.
+ * 
+ * There are four ways to attach a plugin to an object. First, simply
+ * instantiate the plugin yourself and call its attach() method (other class
+ * options left out for the sake of simplicity):
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid();
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * APlugin.attach(MyGrid);
+ * (end)
+ * 
+ * Second, you can instantiate the plugin first and pass it to the object
+ * through the plugins array in the options object.
+ * 
+ * (code)
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * var MyGrid = new Jx.Grid({plugins: [APlugin]});
+ * (end)
+ * 
+ * The third way is to pass the information needed to instantiate the plugin
+ * in the plugins array of the options object:
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: [{
+ *      name: 'Selector',
+ *      options: {}    //options needed to create this plugin
+ *   },{
+ *      name: 'Sorter',
+ *      options: {}
+ *   }]
+ * });
+ * (end)
+ * 
+ * The final way, if the plugin has no options, is to pass the name of the
+ * plugin as a simple string in the plugins array.
+ * 
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: ['Selector','Sorter']
+ * });
+ * (end)
+ * 
+ * Part of the process of initializing plugins is to call prePluginInit() and
+ * postPluginInit(). These events provide you access to the object just before 
+ * and after the plugins are initialized and/or attached to the object using
+ * methods 2 and 3 above. You can hook into these in the same way that you
+ * hook into the preInit() and postInit() events.  
+ * 
+ * Destroying Jx.Object Instances:
+ * Jx.Object provides a destroy method that cleans up potential memory leaks
+ * when you no longer need an object.  Sub-classes are expected to implement
+ * a cleanup() method that provides specific cleanup code for each
+ * sub-class.  Remember to call this.parent() when providing a cleanup()
+ * method. Destroy will also fire off 2 events: preDestroy and postDestroy.
+ * You can hook into these methods in the same way as the init or plugin
+ * events. 
+ *
+ * The Family Attribute:
+ * the Family attribute of a class is used internally by JxLib to identify Jx
+ * objects within mootools.  The actual value of Family is unimportant to Jx.
+ * If you do not provide a Family, a class will inherit it's base class family
+ * up to Jx.Object.  Family is useful when debugging as you will be able to
+ * identify the family in the firebug inspector, but is not as useful for
+ * coding purposes as it does not allow for inheritance.
+ *
+ * Events:
+ *
+ * preInit
+ * postInit
+ * prePluginInit
+ * postPluginInit
+ * initializeDone
+ * preDestroy
+ * postDestroy
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Object = new Class({
+    Family: "Jx.Object",
+    Implements: [Options, Events],
+    plugins: null,
+    pluginNamespace: 'Other',
+    /**
+     * Constructor: Jx.Object
+     * create a new instance of Jx.Object
+     *
+     * Parameters:
+     * options - {Object} optional parameters for creating an object.
+     */
+    parameters: ['options'],
+    
+    options: {
+      /**
+       * Option: useLang
+       * Turns on this widget's ability to react to changes in
+       * the default language. Handy for changing text out on the fly.
+       * 
+       * TODO: Should this be enabled or disabled by default? 
+       */
+      useLang: true,
+      /**
+       * Option: plugins
+       * {Array} an array of plugins to add to the object.
+       */
+      plugins: null
+    },
+    
+    bound: null,
+
+    initialize: function(){
+        this.plugins = new Hash();
+        this.bound = {};
+        //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.bound.changeText = this.changeText.bind(this);
+        if (this.options.useLang) {
+            MooTools.lang.addEvent('langChange', this.bound.changeText);
+        }
+
+        this.fireEvent('preInit');
+        this.init();
+        this.fireEvent('postInit');
+        this.fireEvent('prePluginInit');
+        this.initPlugins();
+        this.fireEvent('postPluginInit');
+        this.fireEvent('initializeDone');
+    },
+
+    /**
+     * Method: initPlugins
+     * internal function to initialize plugins on object creation
+     */
+    initPlugins: function () {
+        // pluginNamespace must be defined in order to pass plugins to the
+        // object
+        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;
+                    	if ($defined(Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()])) {
+                    		p = new Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                    	} else {
+                    		p = new Jx.Adaptor[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                    	}
+                        p.attach(this);
+                    } else if (Jx.type(plugin) === 'string') {
+                        //this is a name for a plugin.
+                    	var p;
+                    	if ($defined(Jx.Plugin[this.pluginNamespace][plugin.capitalize()])) {
+                    		p = new Jx.Plugin[this.pluginNamespace][plugin.capitalize()]();
+                    	} else {
+                    		p = new Jx.Adaptor[this.pluginNamespace][plugin.capitalize()]();
+                    	}
+                        p.attach(this);
+                    }
+                }, this);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     * destroy a Jx.Object, safely cleaning up any potential memory
+     * leaks along the way.  Uses the cleanup method of an object to
+     * actually do the cleanup.
+     * Emits the preDestroy event before cleanup and the postDestroy event
+     * after cleanup.
+     */
+    destroy: function () {
+        this.fireEvent('preDestroy');
+        this.cleanup();
+        this.fireEvent('postDestroy');
+    },
+
+    /**
+     * Method: cleanup
+     * to be implemented by subclasses to do the actual work of destroying
+     * an object. 
+     */
+    cleanup: function () {
+        //detach plugins
+        if (this.plugins.getLength > 0) {
+            this.plugins.each(function (plugin) {
+                plugin.detach();
+                plugin.destroy();
+            }, this);
+        }
+        this.plugins.empty();
+        MooTools.lang.removeEvent('langChange', this.bound.changeText);
+        this.bound = null;
+    },
+
+    /**
+     * Method: init
+     * virtual initialization method to be implemented by sub-classes
+     */
+    init: $empty,
+    
+    /**
+     * APIMethod: registerPlugin
+     * This method is called by a plugin that has its attach method
+     * called.
+     * 
+     * Parameters:
+     * plugin - the plugin to register with this object
+     */
+    registerPlugin: function (plugin) {
+        if (!this.plugins.has(plugin.name)) {
+            this.plugins.set(plugin.name,  plugin);
+        }
+    },
+    /**
+     * APIMethod: deregisterPlugin
+     * his method is called by a plugin that has its detach method
+     * called.
+     * 
+     * Parameters:
+     * plugin - the plugin to deregister.
+     */
+    deregisterPlugin: function (plugin) {
+        if (this.plugins.has(plugin.name)) {
+            this.plugins.erase(plugin.name);
+        }
+    },
+    
+    /**
+     * APIMethod: getPlugin
+     * Allows a developer to get a reference to a plugin with only the
+     * name of the plugin.
+     * 
+     * Parameters:
+     * name - the name of the plugin as defined in the plugin's name property
+     */
+    getPlugin: function (name) {
+        if (this.plugins.has(name)) {
+            return this.plugins.get(name);
+        }
+    },
+
+    /**
+     * APIMethod: getText
+     * 
+     * returns the text for a jx.widget used in a label. 
+     * 
+     * Parameters:
+     * val - <String> || <Function> || <Object> = { set: '', key: ''[, value: ''] } for a MooTools.lang object
+     */
+    getText: function(val) {
+      if (Jx.type(val) == 'string') {
+        return val;
+      } else if (Jx.type(val) == 'function') {
+        return val();
+      } else if (Jx.type(val) == 'object' && $defined(val.set) && $defined(val.key)) {
+        // COMMENT: just an idea how a localization object could be stored to the instance if needed somewhere else and options change?
+        this.i18n = val;
+        if($defined(val.value)) {
+          return MooTools.lang.get(val.set, val.key)[val.value];
+        }else{
+          return MooTools.lang.get(val.set, val.key);
+        }
+      }
+      return '';
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText : $empty
+});
+
+MooTools.lang.set('en-US', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Working ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'close this notice'
+	},
+	progressbar: {
+		messageText: 'Loading...',
+		progressText: '{progress} of {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Browse...'
+	},
+	'formatter.boolean': {
+		'true': 'Yes',
+		'false': 'No'
+	},
+	'formatter.currency': {
+		sign: '$'
+	},
+	'formatter.number': {
+		decimalSeparator: '.',
+    thousandsSeparator: ','
+	},
+	splitter: {
+		barToolTip: 'drag this bar to resize'
+	},
+  panelset: {
+    barToolTip: 'drag this bar to resize'
+  },
+	panel: {
+		collapseTooltip: 'Collapse/Expand Panel',
+    collapseLabel: 'Collapse',
+    expandLabel: 'Expand',
+    maximizeTooltip: 'Maximize Panel',
+    maximizeLabel: 'Maximize',
+    restoreTooltip: 'Restore Panel',
+    restoreLabel: 'Restore',
+    closeTooltip: 'Close Panel',
+    closeLabel: 'Close'
+	},
+	confirm: {
+		affirmativeLabel: 'Yes',
+    negativeLabel: 'No'
+	},
+	dialog: {
+		resizeToolTip: 'Resize dialog'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Cancel'
+	},
+	upload: {
+		buttonText: 'Upload Files'
+	},
+	'plugin.resize': {
+	  tooltip: 'Drag to resize, double click to auto-size.'
+	},
+  'plugin.editor': {
+    submitButton: 'Save',
+    cancelButton: 'Cancel'
+  }
+});// $Id: widget.js 924 2010-05-26 16:03:06Z conrad.barthelmes $
+/**
+ * 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
+ *
+ * o using an existing element, by object reference
+ *
+ * o using an HTML string
+ *
+ * o using a URL to get the content remotely
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Busy States:
+ * 
+ * Any widget can be set as temporarily busy by calling the setBusy(true)
+ * method and then as idle by calling setBusy(false).  By default, busy 
+ * widgets display an event mask that prevents them from being clicked and
+ * a spinner image with a message.  By default, there are two configurations
+ * for the spinner image and message, one for 'small' widgets like buttons
+ * and inputs, and one for larger widgets like panels and dialogs.  The
+ * framework automatically chooses the most appropriate configuration so you
+ * don't need to worry about it unless you want to customize it.
+ *
+ * You can disable this behaviour entirely by setting busyMask: false in the
+ * widget options when creating the widget.
+ *
+ * The mask and spinner functionality is provided by the MooTools Spinner
+ * class.  You can use any options documented for Spinner or Mask by setting
+ * the maskOptions option when creating a widget.
+ *
+ * Events:
+ * Jx.Widget has several events called during it's lifetime (in addition to
+ * the ones for its base class <Jx.Object>).
+ *
+ * preRender - called before rendering begins
+ * postRender - called after rendering is done
+ * deferRender - called when the deferRender option is set to true. The first
+ *      two events (pre- and post- render will NOT be called if deferRender is
+ *      set to true).
+ * contentLoaded - called after content has been loaded successfully
+ * contentLoadFailed - called if content can not be loaded for some reason
+ * addTo - called when a widget is added to another element or widget
+ * busy - called just before the busy mask is rendered/removed
+ * 
+ * MooTools.Lang Keys:
+ * widget.busyMessage - sets the message of the waiter component when used
+ */
+Jx.Widget = new Class({
+    Family: "Jx.Widget",
+    Extends: Jx.Object,
+    
+    options: {
+        /* Option: id
+         * (optional) {String} an HTML ID to assign to the widget
+         */
+        id: null,
+        /**
+         * 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,
+        /**
+         * Option: loadOnDemand
+         * {boolean} ajax content will only be loaded if the action is requested
+         * (like loading the content into a tab when activated)
+         */
+        loadOnDemand : false,
+        /**
+         * Option: cacheContent
+         * {boolean} determine whether the content should be loaded every time
+         * or if it's being cached
+         */
+        cacheContent : true,
+        /**
+         * Option: template
+         * the default HTML structure of this widget.  The default template
+         * is just a div with a class of jxWidget in the base class
+         */
+        template: '<div class="jxWidget"></div>',
+        /**
+         * Option: busyClass
+         * {String} a CSS class name to apply to busy mask when a widget is
+         * set as busy.  The default is 'jxBusy'.
+         */
+        busyClass: 'jxBusy',
+        /**
+         * Option: busyMask
+         * {Object} an object of options to pass to the MooTools Spinner
+         * when masking a busy object.  Set to false if you do not want
+         * to use the busy mask.
+         */
+        busyMask: {
+          'class': 'jxSpinner jxSpinnerLarge',
+          img: {'class':'jxSpinnerImage'},
+          content: {'class':'jxSpinnerContent'},
+          messageContainer: {'class':'jxSpinnerMessage'},
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          },
+          fx: true
+        },
+        /**
+         * Option: deferRender
+         * Used to defer rendering of a widget to a later time. Useful when
+         * we need data or other information not at hand at the moment
+         * of Widget instantiation. If set to true, the user will need to call
+         * render() at some later time. The only drawback to doing so will be
+         * the loss of preRender and postRender events.
+         */
+        deferRender: false
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxWidget'
+    }),
+    
+    /**
+     * Property: busy
+     * {Boolean} is the widget currently busy?  This should be considered
+     * an internal property, use the API methods <Jx.Widget::setBusy> and
+     * <Jx.Widget::isBusy> to manage the busy state of a widget.
+     */
+    busy: false,
+
+    /**
+     * Property: domObj
+     * The HTMLElement that represents this widget.
+     */
+    domObj: null,
+
+    /**
+     * Property: contentIsLoaded
+     * {Boolean} 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,
+
+    /**
+     * Method: init
+     * sets up the base widget code and runs the render function.  Called
+     * by the Jx.Object framework for object initialization, should not be
+     * called directly.
+     */
+    init: function(){
+        if (!this.options.deferRender) {
+            this.fireEvent('preRender');
+            this.render();
+            this.fireEvent('postRender');
+        } else {
+            this.fireEvent('deferRender');
+        }
+    },
+
+    /**
+     * APIMethod: loadContent
+     *
+     * triggers loading of content based on options set for the current
+     * object.
+     *
+     * Parameters:
+     * element - {Object} the element to insert the content into
+     *
+     * Events:
+     *
+     * ContentLoader adds the following events to an object.  You can
+     * register for these events using the addEvent method or by providing
+     * callback functions via the on{EventName} properties in the options
+     * object
+     *
+     * contentLoaded - called when the content has been loaded.  If the
+     *     content is not asynchronous then this is called before loadContent
+     *     returns.
+     * contentLoadFailed - called if the content fails to load, primarily
+     *     useful when using the contentURL method of loading content.
+     */
+    loadContent: function(element) {
+        element = document.id(element);
+        if (this.options.content) {
+            var c;
+            if (this.options.content.domObj) {
+                c = document.id(this.options.content.domObj);
+            } else {
+                c = document.id(this.options.content);
+            }
+            if (c) {
+                if (this.options.content.addTo) {
+                    this.options.content.addTo(element);
+                } else {
+                    element.appendChild(c);
+                }
+                this.contentIsLoaded = true;
+            } else {
+                element.innerHTML = this.options.content;
+                this.contentIsLoaded = true;
+            }
+        } else if (this.options.contentURL) {
+            this.contentIsLoaded = false;
+            this.req = new Request({
+                url: this.options.contentURL,
+                method:'get',
+                evalScripts:true,
+                onRequest:(function() {
+                  if(this.options.loadOnDemand) {
+                      this.setBusy(true);
+                  }
+                }).bind(this),
+                onSuccess:(function(html) {
+                    element.innerHTML = html;
+                    this.contentIsLoaded = true;
+                    if (Jx.isAir){
+                        $clear(this.reqTimeout);
+                    }
+                    this.setBusy(false);
+                    this.fireEvent('contentLoaded', this);
+                }).bind(this),
+                onFailure: (function(){
+                    this.contentIsLoaded = true;
+                    this.fireEvent('contentLoadFailed', this);
+                    this.setBusy(false);
+                }).bind(this),
+                headers: {'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'}
+            });
+            this.req.send();
+            if (Jx.isAir) {
+                var timeout = $defined(this.options.timeout) ? this.options.timeout : 10000;
+                this.reqTimeout = this.checkRequest.delay(timeout, this);
+            }
+        } else {
+            this.contentIsLoaded = true;
+        }
+        if (this.options.contentId) {
+            element.id = this.options.contentId;
+        }
+        if (this.contentIsLoaded) {
+            this.fireEvent('contentLoaded', this);
+        }
+    },
+
+    /**
+     * APIMethod: position
+     * positions an element relative to another element
+     * based on the provided options.  Positioning rules are
+     * a string with two space-separated values.  The first value
+     * references the parent element and the second value references
+     * the thing being positioned.  In general, multiple rules can be
+     * considered by passing an array of rules to the horizontal and
+     * vertical options.  The position method will attempt to position
+     * the element in relation to the relative element using the rules
+     * specified in the options.  If the element does not fit in the
+     * viewport using the rule, then the next rule is attempted.  If
+     * all rules fail, the last rule is used and element may extend
+     * outside the viewport.  Horizontal and vertical rules are
+     * processed independently.
+     *
+     * Horizontal Positioning:
+     * Horizontal values are 'left', 'center', 'right', and numeric values.
+     * Some common rules are:
+     * o 'left left' is interpreted as aligning the left
+     * edge of the element to be positioned with the left edge of the
+     * reference element.
+     * o 'right right' aligns the two right edges.
+     * o 'right left' aligns the left edge of the element to the right of
+     * the reference element.
+     * o 'left right' aligns the right edge of the element to the left
+     * edge of the reference element.
+     *
+     * Vertical Positioning:
+     * Vertical values are 'top', 'center', 'bottom', and numeric values.
+     * Some common rules are:
+     * o 'top top' is interpreted as aligning the top
+     * edge of the element to be positioned with the top edge of the
+     * reference element.
+     * o 'bottom bottom' aligns the two bottom edges.
+     * o 'bottom top' aligns the top edge of the element to the bottom of
+     * the reference element.
+     * o 'top bottom' aligns the bottom edge of the element to the top
+     * edge of the reference element.
+     *
+     * Parameters:
+     * element - the element to position
+     * relative - the element to position relative to
+     * options - the positioning options, see list below.
+     *
+     * Options:
+     * horizontal - the horizontal positioning rule to use to position the
+     *    element.  Valid values are 'left', 'center', 'right', and a numeric
+     *    value.  The default value is 'center center'.
+     * vertical - the vertical positioning rule to use to position the
+     *    element.  Valid values are 'top', 'center', 'bottom', and a numeric
+     *    value.  The default value is 'center center'.
+     * offsets - an object containing numeric pixel offset values for the
+     *    object being positioned as top, right, bottom and left properties.
+     */
+    position: function(element, relative, options) {
+        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 || {});
+
+        var coords = relative.getCoordinates(); //top, left, width, height
+        var page, scroll;
+        if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
+            page = Jx.getPageDimensions();
+            scroll = document.id(document.body).getScroll();
+        } else {
+            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
+            // appear in the right part of the page.
+            coords.left += scroll.x;
+            coords.top += scroll.y;
+        } else if (element.parentNode == relative) {
+            // if the element is opening *inside* its relative, we want
+            // it to position correctly within it so top/left becomes
+            // the reference system.
+            coords.left = 0;
+            coords.top = 0;
+        }
+        var size = element.getMarginBoxSize(); //width, height
+        var left, right, top, bottom, n;
+        if (!hor.some(function(opt) {
+            var parts = opt.split(' ');
+            if (parts.length != 2) {
+                return false;
+            }
+            if (!isNaN(parseInt(parts[0],10))) {
+                n = parseInt(parts[0],10);
+                if (n>=0) {
+                    left = n;
+                } else {
+                    left = coords.left + coords.width + n;
+                }
+            } else {
+                switch(parts[0]) {
+                    case 'right':
+                        left = coords.left + coords.width;
+                        break;
+                    case 'center':
+                        left = coords.left + Math.round(coords.width/2);
+                        break;
+                    case 'left':
+                    default:
+                        left = coords.left;
+                        break;
+                }
+            }
+            if (!isNaN(parseInt(parts[1],10))) {
+                n = parseInt(parts[1],10);
+                if (n<0) {
+                    right = left + n;
+                    left = right - size.width;
+                } else {
+                    left += n;
+                    right = left + size.width;
+                }
+                right = coords.left + coords.width + parseInt(parts[1],10);
+                left = right - size.width;
+            } else {
+                switch(parts[1]) {
+                    case 'left':
+                        left -= offsets.left;
+                        right = left + size.width;
+                        break;
+                    case 'right':
+                        left += offsets.right;
+                        right = left;
+                        left = left - size.width;
+                        break;
+                    case 'center':
+                    default:
+                        left = left - Math.round(size.width/2);
+                        right = left + size.width;
+                        break;
+                }
+            }
+            return (left >= scroll.x && right <= scroll.x + page.width);
+        })) {
+            // all failed, snap the last position onto the page as best
+            // we can - can't do anything if the element is wider than the
+            // space available.
+            if (right > page.width) {
+                left = scroll.x + page.width - size.width;
+            }
+            if (left < 0) {
+                left = 0;
+            }
+        }
+        element.setStyle('left', left);
+
+        if (!ver.some(function(opt) {
+          var parts = opt.split(' ');
+          if (parts.length != 2) {
+            return false;
+          }
+          if (!isNaN(parseInt(parts[0],10))) {
+            top = parseInt(parts[0],10);
+          } else {
+            switch(parts[0]) {
+              case 'bottom':
+                top = coords.top + coords.height;
+                break;
+              case 'center':
+                top = coords.top + Math.round(coords.height/2);
+                break;
+              case 'top':
+              default:
+                top = coords.top;
+                break;
+            }
+          }
+          if (!isNaN(parseInt(parts[1],10))) {
+              var n = parseInt(parts[1],10);
+              if (n>=0) {
+                  top += n;
+                  bottom = top + size.height;
+              } else {
+                  bottom = top + n;
+                  top = bottom - size.height;
+              }
+          } else {
+              switch(parts[1]) {
+                  case 'top':
+                      top -= offsets.top;
+                      bottom = top + size.height;
+                      break;
+                  case 'bottom':
+                      top += offsets.bottom;
+                      bottom = top;
+                      top = top - size.height;
+                      break;
+                  case 'center':
+                  default:
+                      top = top - Math.round(size.height/2);
+                      bottom = top + size.height;
+                      break;
+              }
+          }
+          return (top >= scroll.y && bottom <= scroll.y + page.height);
+      })) {
+          // all failed, snap the last position onto the page as best
+          // we can - can't do anything if the element is higher than the
+          // space available.
+          if (bottom > page.height) {
+              top = scroll.y + page.height - size.height;
+          }
+          if (top < 0) {
+              top = 0;
+          }
+      }
+      element.setStyle('top', top);
+
+      /* update the jx layout if necessary */
+      var jxl = element.retrieve('jxLayout');
+      if (jxl) {
+          jxl.options.left = left;
+          jxl.options.top = top;
+      }
+    },
+
+    /**
+     * Method: makeChrome
+     * create chrome on an element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to put the chrome on.
+     */
+    makeChrome: function(element) {
+        var c = new Element('div', {
+            'class':'jxChrome',
+            events: {
+                contextmenu: function(e) { e.stop(); }
+            }
+        });
+
+        /* add to element so we can get the background image style */
+        element.adopt(c);
+
+        /* pick up any offset because of chrome, set
+         * through padding on the chrome object.  Other code can then
+         * make use of these offset values to fix positioning.
+         */
+        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 */
+        /* the app: protocol check is for adobe air support */
+        var src = c.getStyle('backgroundImage');
+        if (src != null) {
+          if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
+              src = null;
+          } else {
+              src = src.slice(4,-1);
+              /* this only seems to be IE and Opera, but they add quotes
+               * around the url - yuck
+               */
+              if (src.charAt(0) == '"') {
+                  src = src.slice(1,-1);
+              }
+
+              /* and remove the background image */
+              c.setStyle('backgroundImage', 'none');
+
+              /* make chrome */
+              ['TR','TL','BL','BR'].each(function(s){
+                  c.adopt(
+                      new Element('div',{
+                          'class':'jxChrome'+s
+                      }).adopt(
+                      new Element('img',{
+                          'class':'png24',
+                          src:src,
+                          alt: '',
+                          title: ''
+                      })));
+              }, this);
+          }
+        }
+        /* create a shim so selects don't show through the chrome */
+        if ($defined(window.IframeShim)) {
+          this.shim = new IframeShim(c, {className: 'jxIframeShim'});
+        }
+
+        /* remove from DOM so the other resizing logic works as expected */
+        c.dispose();
+        this.chrome = c;
+    },
+
+    /**
+     * APIMethod: showChrome
+     * show the chrome on an element.  This creates the chrome if necessary.
+     * If the chrome has been previously created and not removed, you can
+     * call this without an element and it will just resize the chrome within
+     * its existing element.  You can also pass in a different element from
+     * which the chrome was previously attached to and it will move the chrome
+     * to the new element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to show the chrome on.
+     */
+    showChrome: function(element) {
+        element = document.id(element) || document.id(this);
+        if (element) {
+            if (!this.chrome) {
+                this.makeChrome(element);
+                element.addClass('jxHasChrome');
+            }
+            this.resizeChrome(element);
+            if (this.shim) {
+              this.shim.show();
+            }
+            if (element && this.chrome.parentNode !== element) {
+                element.adopt(this.chrome);
+                this.chrome.setStyle('z-index',-1);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: hideChrome
+     * removes the chrome from the DOM.  If you do this, you can't
+     * call showChrome with no arguments.
+     */
+    hideChrome: function() {
+        if (this.chrome) {
+            if (this.shim) { 
+              this.shim.hide(); 
+            }
+            this.chrome.parentNode.removeClass('jxHasChrome');
+            this.chrome.dispose();
+        }
+    },
+
+    /**
+     * APIMethod: resizeChrome
+     * manually resize the chrome on an element.
+     *
+     * Parameters:
+     * element: {DOMElement} the element to resize the chrome for
+     */
+    resizeChrome: function(o) {
+        if (this.chrome && Browser.Engine.trident4) {
+            this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
+            if (this.shim) {
+              this.shim.position();
+            }
+        }
+    },
+
+    /**
+     * APIMethod: addTo
+     * adds the object to the DOM relative to another element.  If you use
+     * 'top' or 'bottom' then the element is added to the relative
+     * element (becomes a child node).  If you use 'before' or 'after'
+     * then the element is inserted adjacent to the reference node.
+     *
+     * Parameters:
+     * reference - {Object} the DOM element or id of a DOM element
+     * to append the object relative to
+     * where - {String} where to append the element in relation to the
+     * reference node.  Can be 'top', 'bottom', 'before' or 'after'.
+     * The default is 'bottom'.
+     *
+     * Returns:
+     * the object itself, which is useful for chaining calls together
+     */
+    addTo: function(reference, where) {
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            if (reference instanceof Jx.Widget && $defined(reference.add)) {
+                reference.add(el);
+            } else {
+                ref = document.id(reference);
+                el.inject(ref,where);
+            }
+            this.fireEvent('addTo',this);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: toElement
+     * return a DOM element reference for this widget, by default this
+     * returns the local domObj reference.  This is used by the mootools
+     * framework with the document.id() or $() methods allowing you to
+     * manipulate a Jx.Widget sub class as if it were a DOM element.
+     *
+     * (code)
+     * var button = new Jx.Button({label: 'test'});
+     * $(button).inject('someElement');
+     * (end)
+     */
+    toElement: function() {
+        return this.domObj;
+    },
+
+    /**
+     * 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});
+        }
+        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;
+    },
+
+    /**
+     * APIMethod: dispose
+     * remove the widget from the DOM
+     */
+    dispose: function(){
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            el.dispose();
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the widget and clean up any potential memory leaks
+     */
+    cleanup: function(){
+        if ($defined(this.domObj)) {
+            this.domObj.eliminate('jxWidget');
+            this.domObj.destroy();
+        }
+        if ($defined(this.addable)) {
+            this.addable.destroy();
+        }
+        if ($defined(this.domA)) {
+            this.domA.destroy();
+        }
+        if ($defined(this.classes)) {
+          this.classes.each(function(v, k) {
+            this[k] = null;
+          }, this);
+        }
+        this.elements.empty();
+        this.elements = null;
+        this.parent();
+    },
+
+    /**
+     * Method: render
+     * render the widget, internal function called by the framework.
+     */
+    render: function() {
+        this.elements = this.processElements(this.options.template,
+            this.classes);
+        if ($defined(this.domObj)) {
+          if ( $defined(this.options.id)) {
+            this.domObj.set('id', this.options.id);
+          }
+          //TODO: Should we autogenerate an id when one is not provided? like so...
+          // this.domObj.set('id',this.generateId());
+          this.domObj.store('jxWidget', this);
+        }
+    },
+
+    /**
+     * Property: elements
+     * a hash of elements extracted by processing the widget template
+     */
+    elements: null,
+
+    /**
+     * Method: processElements
+     * process the template of the widget and populate the elements hash
+     * with any objects.  Also set any object references based on the classes
+     * hash.
+     */
+    processElements: function(template, classes) {
+        var keys = classes.getValues();
+        elements = this.processTemplate(template, keys);
+        classes.each(function(value, key) {
+            if (key != 'elements' && elements.get(value)) {
+                this[key] = elements.get(value);
+            }
+        }, this);
+        return elements;
+    },
+    
+    /**
+     * APIMethod: isBusy
+     * indicate if the widget is currently busy or not
+     *
+     * Returns:
+     * {Boolean} true if busy, false otherwise.
+     */
+    isBusy: function() {
+      return this.busy;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        if (this.options.busyClass) {
+          this.domObj.addClass(this.options.busyClass);
+        }
+        if (this.options.busyMask && this.domObj.spin) {
+          /* put the spinner above the element in the z-index */
+          var z = Jx.getNumber(this.domObj.getStyle('z-index'));
+          var opts = {
+            style: {
+              'z-index': z+1
+            }
+          };
+          /* switch to the small size if the element is less than
+           * 60 pixels high
+           */
+          var size = this.domObj.getBorderBoxSize();
+          if (size.height < 60) {
+            opts['class'] = 'jxSpinner jxSpinnerSmall';
+            opts.img = null;
+            opts.message = new Element('p',{
+              'class':'jxSpinnerMessage',
+              html: '<span class="jxSpinnerImage"></span>'+this.getText({set:'Jx',key:'widget',value:'busyMessage'})
+            });
+          }
+          opts = $merge(this.options.busyMask, opts);
+          
+          this.domObj.get('spinner', opts).show(!this.options.busyMask.fx);
+  		
+        }
+      } else {
+        if (this.options.busyClass) {
+          this.domObj.removeClass(this.options.busyClass);
+        }
+        if (this.options.busyMask && this.domObj.unspin) {
+          this.domObj.get('spinner').hide(!this.options.busyMask.fx);
+    	
+        }
+      }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - {string} the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    },
+    
+    /**
+     * APIMethod: stack
+     * stack this widget in the z-index of the DOM relative to other stacked
+     * objects.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to stack.  By default, the
+     * element to stack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    stack: function(el) {
+      el = el || document.id(this);
+      Jx.Stack.stack(el);
+    },
+    
+    /**
+     * APIMethod: unstack
+     * remove this widget from the stack.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to unstack.  By default, the
+     * element to unstack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    unstack: function(el) {
+      el = el || document.id(this);
+      Jx.Stack.unstack(el);
+    }
+});
+
+
+/**
+ * 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);
+            }
+        }
+    });
+}// $Id: selection.js 927 2010-05-27 13:32:00Z pagameba $
+/**
+ * Class: Jx.Selection
+ *
+ * Manage selection of objects.
+ *
+ * Example:
+ * (code)
+ * var selection = new Jx.Selection();
+ * (end)
+ *
+ * Events:
+ * select - fired when an item is added to the selection.  This event may be
+ *    changed by passing the eventToFire option when creating the selection
+ *    object.
+ * unselect - fired when an item is removed from the selection.  This event
+ *    may be changed by passing the eventToFire option when creating the
+ *    selection object.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+
+Jx.Selection = new Class({
+    Family: 'Jx.Selection',
+    Extends: Jx.Object,
+    options: {
+        /**
+         * Option: eventToFire
+         * Allows the developer to change the event that is fired in case one
+         * object is using multiple selectionManager instances.  The default
+         * is to use 'select' and 'unselect'.  To modify the event names,
+         * pass different values:
+         * (code)
+         * new Jx.Selection({
+         *   eventToFire: {
+         *     select: 'newSelect',
+         *     unselect: 'newUnselect'
+         *   }
+         * });
+         * (end)
+         */
+        eventToFire: {
+            select: 'select',
+            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
+    },
+
+    /**
+     * Property: selection
+     * {Array} an array holding the current selection
+     */
+    selection: null,
+
+    /**
+     * Constructor: Jx.Selection
+     * create a new instance of Jx.Selection
+     *
+     * Parameters:
+     * options - {Object} options for the new instance
+     */
+    init: function () {
+        this.selection = [];
+        this.parent();
+    },
+
+    cleanup: function() {
+      this.selection = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: defaultSelect
+     * select an item if the selection does not yet contain the minimum
+     * number of selected items.  Uses <Jx.Selection::select> to select
+     * the item, so the same criteria is applied to the item if it is
+     * to be selected.
+     */
+    defaultSelect: function(item) {
+        if (this.selection.length < this.options.minimumSelection) {
+            this.select(item);
+        }
+    },
+
+    /**
+     * APIMethod: select
+     * select an item.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    select: function (item) {
+        item = document.id(item);
+        if (this.options.selectMode === 'multiple') {
+            if (this.selection.contains(item)) {
+                this.unselect(item);
+            } else {
+                document.id(item).addClass(this.options.selectClass);
+                this.selection.push(item);
+                this.fireEvent(this.options.eventToFire.select, item);
+            }
+        } 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]);
+                }
+                this.fireEvent(this.options.eventToFire.select, item);
+            } else {
+                if (this.options.selectToggle) {
+                  this.unselect(item);
+                }
+            }
+        }
+    },
+
+    /**
+     * APIMethod: unselect
+     * remove an item from the selection.  The item must already be in the
+     * selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    unselect: function (item) {
+        if (this.selection.contains(item) &&
+            this.selection.length > this.options.minimumSelection) {
+            document.id(item).removeClass(this.options.selectClass);
+            this.selection.erase(item);
+            this.fireEvent(this.options.eventToFire.unselect, [item, this]);
+        }
+    },
+
+    /**
+     * APIMethod: selected
+     * returns the items in the current selection.
+     *
+     * Returns:
+     * {Array} an array of DOM elements in the current selection
+     */
+    selected: function () {
+        return this.selection;
+    },
+
+    /**
+     * APIMethod: isSelected
+     * test if an item is in the current selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     *
+     * Returns:
+     * {Boolean} true if the current selection contains the item, false
+     * otherwise
+     */
+    isSelected: function(item) {
+        return this.selection.contains(item);
+    }
+});// $Id: list.js 927 2010-05-27 13:32:00Z pagameba $
+/**
+ * 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)
+ * var list = new Jx.List('container',{
+ *   hover: true,
+ *   select: true,
+ *   onSelect: function(el) {
+ *     alert(el.get('html'));
+ *   }
+ * });
+ * list.add(new Element('li', {html:'1'}));
+ * list.add(new Element('li', {html:'2'}));
+ * list.add(new Element('li', {html:'3'}));
+ *
+ * (end)
+ *
+ * Events:
+ * 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,
+    /**
+     * Constructor: Jx.List
+     * create a new instance of Jx.List
+     *
+     * Parameters:
+     * container - {Mixed} an element reference or id of an element that will
+     * contain the items in the list
+     * options - {Object} an object containing optional parameters
+     * selection - {<Jx.Selection>} null or a Jx.Selection object. If the
+     * select option is set to true, then list will use this selection object
+     * to track selections or create its own if no selection object is
+     * supplied.
+     */
+    parameters: ['container', 'options', 'selection'],
+    /* does this object own the selection object (and should clean it up) */
+    ownsSelection: false,
+    /**
+     * APIProperty: container
+     * 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: {
+        /**
+         * Option: items
+         * an array of items to add to the list right away
+         */
+        items: null,
+        /**
+         * Option: hover
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined hoverClass if set and mouseenter/mouseleave
+         * events will be emitted when the user hovers over and out of elements
+         */
+        hover: false,
+        /**
+         * Option: hoverClass
+         * the CSS class name to add to the wrapper element when the mouse is
+         * over an item
+         */
+        hoverClass: 'jxHover',
+
+        /**
+         * Option: press
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined pressClass if set and mousedown/mouseup
+         * events will be emitted when the user clicks on elements
+         */
+        press: false,
+        /**
+         * Option: 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 false.  If set to true, the wrapper element will
+         * obtain the defined selectClass if set and select/unselect events
+         * will be emitted when items are selected and unselected.  For other
+         * selection objects, see <Jx.Selection>
+         */
+        select: false
+    },
+
+    /**
+     * Method: init
+     * internal method to initialize this object
+     */
+    init: function() {
+        this.container = document.id(this.options.container);
+        this.container.store('jxList', this);
+
+        var target = this;
+        var isEnabled = function(el) {
+            var item = el.retrieve('jxListTargetItem') || el;
+            return !item.hasClass('jxDisabled');
+        };
+        var isSelectable = function(el) {
+            var item = el.retrieve('jxListTargetItem') || el;
+            return !item.hasClass('jxUnselectable');
+        };
+        this.bound = $merge(this.bound, {
+            mousedown: function() {
+                if (isEnabled(this)) {
+                    this.addClass(target.options.pressClass);
+                    target.fireEvent('mousedown', this, target);
+                }
+            },
+            mouseup: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(target.options.pressClass);
+                    target.fireEvent('mouseup', this, target);
+                }
+            },
+            mouseenter: function() {
+                if (isEnabled(this)) {
+                    this.addClass(target.options.hoverClass);
+                    target.fireEvent('mouseenter', this, target);
+                }
+            },
+            mouseleave: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(target.options.hoverClass);
+                    target.fireEvent('mouseleave', this, target);
+                }
+            },
+            keydown: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.addClass('jxPressed');
+                }
+            },
+            keyup: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.removeClass('jxPressed');
+                }
+            },
+            click: function (e) {
+                if (target.selection &&
+                    isEnabled(this) &&
+                    isSelectable(this)) {
+                    target.selection.select(this, target);
+                }
+            },
+            select: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('select', itemTarget);
+                }
+            },
+            unselect: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('unselect', itemTarget);
+                }
+            },
+            contextmenu: function(e) {
+              var cm = this.retrieve('jxContextMenu');
+              if (cm) {
+                cm.show(e);
+                this.removeClass(target.options.pressClass);
+              }
+              e.stop();
+            }
+        });
+
+        if (this.options.selection) {
+            this.setSelection(this.options.selection);
+            this.options.select = true;
+        } else if (this.options.select) {
+            this.selection = new Jx.Selection(this.options);
+            this.ownsSelection = true;
+        }
+
+        if ($defined(this.options.items)) {
+            this.add(this.options.items);
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the list and release anything it references
+     */
+    cleanup: function() {
+        this.container.getChildren().each(function(item){
+            this.remove(item);
+        }, this);
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents();
+            this.selection.destroy();
+        }
+        this.setSelection(null);
+        this.container.eliminate('jxList');
+        this.bound.mousedown=null;
+        this.bound.mouseup=null;
+        this.bound.mouseenter=null;
+        this.bound.mouseleave=null;
+        this.bound.keydown=null;
+        this.bound.keyup=null;
+        this.bound.click=null;
+        this.bound.select=null;
+        this.bound.unselect=null;
+        this.bound.contextmenu=null;
+        this.parent();
+    },
+
+    /**
+     * 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 (Jx.type(item) == 'array') {
+            item.each(function(what){
+              this.add(what, position);
+            }.bind(this) );
+            return;
+        }
+        /* the element being wrapped */
+        var el = document.id(item);
+        var target = el.retrieve('jxListTarget') || el;
+        if (target) {
+            target.store('jxListTargetItem', el);
+            target.addEvents({
+              contextmenu: this.bound.contextmenu
+            });
+            if (this.options.press && this.options.pressClass) {
+                target.addEvents({
+                    mousedown: this.bound.mousedown,
+                    mouseup: this.bound.mouseup,
+                    keyup: this.bound.keyup,
+                    keydown: this.bound.keydown
+                });
+            }
+            if (this.options.hover && this.options.hoverClass) {
+                target.addEvents({
+                    mouseenter: this.bound.mouseenter,
+                    mouseleave: this.bound.mouseleave
+                });
+            }
+            if (this.selection) {
+                target.addEvents({
+                    click: this.bound.click
+                });
+            }
+            if ($defined(position)) {
+                if ($type(position) == 'number') {
+                    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);
+            }
+        }
+    },
+    /**
+     * APIMethod: 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) {
+        var el = document.id(item);
+        if (el && this.container.hasChild(el)) {
+            this.unselect(el, true);
+            el.dispose();
+            var target = el.retrieve('jxListTarget') || el;
+            target.removeEvents(this.bound);
+            this.fireEvent('remove', item, this);
+            return item;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: 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
+     * context - {object} the context to execute the function in, null by
+     * default.
+     */
+    each: function(f, context) {
+        $A(this.container.childNodes).each(f, context);
+    },
+    /**
+     * 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);
+    },
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object that this list will use for selection
+     * events.
+     *
+     * Parameters:
+     * {<Jx.Selection>} the selection object, or null to remove it.
+     */
+    setSelection: function(selection) {
+        if (this.selection == selection) return;
+
+        if (this.selection) {
+            this.selection.removeEvents(this.bound);
+            if (this.ownsSelection) {
+                this.selection.destroy();
+                this.ownsSelection = false;
+            }
+        }
+
+        this.selection = selection;
+        if (this.selection) {
+            this.selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+    }
+
+});/**
+ * Class: Jx.Stack
+ * Manage the zIndex of widgets
+ *
+ * This is a singleton and should be called directly, like so:
+ *
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010 Paul Spencer
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Stack = new(new Class({
+  /**
+   * Property: els
+   * {Array} the elements in the stack
+   */
+  els: [],
+  
+  /**
+   * Property: base
+   * {Integer} the base z-index value of the first element in the stack
+   */
+  base: 1000,
+  
+  /**
+   * Property: increment
+   * {Integer} the amount to increment the z-index between elements of the
+   * stack
+   */
+  increment: 100,
+  
+  /**
+   * APIMethod: stack
+   * push an element onto the stack and set its z-index appropriately
+   *
+   * Parameters:
+   * el - {DOMElement} a DOM element to push on the stack
+   */
+  stack: function(el) {
+    this.unstack(el);
+    this.els.push(el);
+    this.setZIndex(el, this.els.length-1);
+  },
+
+  /**
+   * APIMethod: unstack
+   * pull an element off the stack and reflow the z-index of the remaining
+   * elements in the stack if necessary
+   *
+   * Parameters:
+   * el - {DOMElement} the DOM element to pull off the stack
+   */
+  unstack: function(el) {
+    if (this.els.contains(el)) {
+      el.setStyle('z-index', '');
+      var idx = this.els.indexOf(el);
+      this.els.erase(el);
+      for (var i=idx; i<this.els.length; i++) {
+        this.setZIndex(this.els[i], i);
+      }
+    }
+  },
+
+  /**
+   * Method: setZIndex
+   * set the z-index of an element based on its position in the stack
+   *
+   * Parameters:
+   * el - {DOMElement} the element to set the z-index for
+   * idx - {Integer} optional, the index to assume for this object
+   */
+  setZIndex: function(obj, idx) {
+    idx = idx || this.els.indexOf(obj);
+    if (idx !== false) {
+      document.id(obj).setStyle('z-index', this.base + (idx*this.increment));
+    }
+  }
+  
+}))();MooTools.lang.set('de-DE', 'Date', {
+  // need to overwrite 'M&auml;rz' to 'März' for jx.select fields
+  months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
+});
+
+MooTools.lang.set('de-DE', 'Jx', {
+
+	'widget': {
+		busyMessage: 'Arbeite ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'Notiz schließen'
+	},
+	progressbar: {
+		messageText: 'Lade...',
+		progressText: '{progress} von {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Durchsuchen...'
+	},
+	'formatter.boolean': {
+		'true': 'Ja',
+		'false': 'Nein'
+	},
+	'formatter.currency': {
+		sign: '€'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: '.'
+	},
+	splitter: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panelset: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panel: {
+        collapseTooltip: 'Panel ein-/ausklappen', //colB
+        collapseLabel: 'Einklappen',  //colM
+        expandLabel: 'Ausklappen', //colM
+        maximizeTooltip: 'Panel maximieren',
+        maximizeLabel: 'maximieren',
+        restoreTooltip: 'Panel wieder herstellen', //maxB
+        restoreLabel: 'wieder herstellen', //maxM
+        closeTooltip: 'Panel schließen', //closeB
+        closeLabel: 'Schließen' //closeM
+	},
+	confirm: {
+		affirmativeLabel: 'Ja',
+    negativeLabel: 'Nein'
+	},
+	dialog: {
+		label: 'Neues Fenster'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Abbrechen'
+	},
+	upload: {
+		buttonText: 'Dateien hochladen'
+	},
+	'plugin.resize': {
+	  tooltip: 'Klicken um Größe zu verändern. Doppelklick für automatische Anpassung.'
+	},
+  'plugin.editor': {
+    submitButton: 'Speichern',
+    cancelButton: 'Abbrechen'
+  }
+});MooTools.lang.set('ru-RU-unicode', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Обработка...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'закрыть Ñ?то Ñ?ообщение'
+	},
+	progressbar: {
+		messageText: 'Загрузка...',
+		progressText: '{progress} из {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Выбрать...'
+	},
+	'formatter.boolean': {
+		'true': 'Да',
+		'false': 'Ð?ет'
+	},
+	'formatter.currency': {
+		sign: 'Ñ€.'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: ' '
+	},
+	splitter: {
+		barToolTip: 'потÑ?ни, чтобы изменить размер'
+	},
+	panelset: {
+		barToolTip: 'потÑ?ни, чтобы изменить размер'
+	},
+	panel: {
+		collapseTooltip: 'Свернуть/Развернуть Панель',
+    collapseLabel: 'Свернуть',
+    expandLabel: 'Развернуть',
+    maximizeTooltip: 'Увеличить Панель',
+    maximizeLabel: 'Увеличить',
+    restoreTooltip: 'ВоÑ?Ñ?тановить Панель',
+    restoreLabel: 'ВоÑ?Ñ?тановить',
+    closeTooltip: 'Закрыть Панель',
+    closeLabel: 'Закрыть'
+	},
+	confirm: {
+		affirmativeLabel: 'Да',
+    negativeLabel: 'Ð?ет'
+	},
+	dialog: {
+		resizeToolTip: 'Изменить размер'
+	},
+	message: {
+		okButton: 'Ок'
+	},
+	prompt: {
+		okButton: 'Ок',
+		cancelButton: 'Отмена'
+	},
+	upload: {
+		buttonText: 'Загрузка файла'
+	},
+	'plugin.resize': {
+	  tooltip: 'ПотÑ?ни, чтобы изменить, двойной щелчок длÑ? авто размера.'
+	},
+  'plugin.editor': {
+    submitButton: 'Сохранить',
+    cancelButton: 'Отмена'
+  }
+});// $Id: record.js 834 2010-04-05 18:17:01Z jonlb at comcast.net $
+/**
+ * Class: Jx.Record
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is used as a representation (or container) for a single row
+ * of data in a <Jx.Store>. It is not usually directly instantiated by the 
+ * developer but rather by the store itself.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Record = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Record',
+    
+    options: {
+        /**
+         * Option: separator
+         * The separator to pass to the comparator
+         * constructor (<Jx.Compare>) - defaults to '.'
+         */
+        separator : '.',
+        
+        primaryKey: null
+    },
+    /**
+     * Property: data
+     * The data for this record
+     */
+    data: null,
+    /**
+     * Property: state
+     * used to determine the state of this record. When not null (meaning no 
+     * changes were made) this should be one of
+     * 
+     * - Jx.Record.UPDATE
+     * - Jx.Record.DELETE
+     * - Jx.Record.INSERT
+     */
+    state: null,
+    /**
+     * Property: columns
+     * Holds a reference to the columns for this record. These are usually
+     * passed to the record from the store. This should be an array of objects
+     * where the objects represent the columns. The object should take the form:
+     * 
+     * (code)
+     * {
+     *     name: <column name>,
+     *     type: <column type>,
+     *     ..additional options required by the record implementation...
+     * }
+     * (end)
+     * 
+     * The type of the column should be one of alphanumeric, numeric, date, 
+     * boolean, or currency.
+     */
+    columns: null,
+    
+    parameters: ['store', 'columns', 'data', 'options'],
+    
+    init: function () {
+        this.parent();
+        if ($defined(this.options.columns)) {
+            this.columns = this.options.columns;
+        }
+        
+        if ($defined(this.options.data)) {
+            this.processData(this.options.data);
+        } else {
+            this.data = new Hash();
+        }
+        
+        if ($defined(this.options.store)) {
+            this.store = this.options.store;
+        }
+            
+    },
+    /**
+     * APIMethod: get
+     * returns the value of the requested column. Can be programmed to handle
+     * pseudo-columns (such as the primaryKey column implemented in this base
+     * record).
+     * 
+     * Parameters:
+     * column - the string, index, or object of the requested column
+     */
+    get: function (column) {
+        var type = Jx.type(column);
+        if (type !== 'object') {
+            if (column === 'primaryKey') {
+                column = this.resolveCol(this.options.primaryKey);
+            } else {
+                column = this.resolveCol(column);
+            }
+        }
+        if (this.data.has(column.name)) {
+            return this.data.get(column.name);
+        } else {
+            return null;
+        }
+    },
+    /**
+     * APIMethod: set
+     * Sets a given value into the requested column. 
+     * 
+     *  Parameters:
+     *  column - the object, index, or string name of the target column
+     *  data - the data to add to the column
+     */
+    set: function (column, data) {
+        var type = Jx.type(column);
+        if (type !== 'object') {
+            column = this.resolveCol(column);
+        }
+        
+        if (!$defined(this.data)) {
+            this.data = new Hash();
+        }
+        
+        var oldValue = this.get(column);
+        this.data.set(column.name, data);
+        this.state = Jx.Record.UPDATE;
+        return [column.name, oldValue, data];
+        //this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);
+            
+    },
+    /**
+     * APIMethod: equals
+     * Compares the value of a particular column with a given value
+     * 
+     * Parameters:
+     * column - the column to compare with (either column name or index)
+     * value - the value to compare to.
+     * 
+     * Returns:
+     * True | False depending on the outcome of the comparison.
+     */
+    equals: function (column, value) {
+        if (column === 'primaryKey') {
+            column = this.resolveCol(this.options.primaryKey);
+        } else {
+            column = this.resolveCol(column);
+        }
+        if (!this.data.has(column.name)) {
+            return null;
+        } else {
+            if (!$defined(this.comparator)) {
+                this.comparator = new Jx.Compare({
+                    separator : this.options.separator
+                });
+            }
+            var fn = this.comparator[column.type].bind(this.comparator);
+            return (fn(this.get(column), value) === 0);
+        }
+    },
+    /**
+     * Method: processData
+     * This method takes the data passed in and puts it into the form the 
+     * record needs it in. This default implementation does nothing but
+     * assign the data to the data property but it can be overridden in
+     * subclasses to massge the data in any way needed.
+     * 
+     * Parameters:
+     * data - the data to process
+     */
+    processData: function (data) {
+        this.data = $H(data);
+    },
+    
+    /**
+     * Method: resolveCol 
+     * Determines which column is being asked for and returns it.
+     * 
+     * Parameters: 
+     * col - a number referencing a column in the store
+     * 
+     * Returns: 
+     * the column object referred to
+     */
+    resolveCol : function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.columns[col];
+        } else if (t === 'string') {
+            this.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;
+    },
+    /**
+     * APIMethod: asHash
+     * Returns the data for this record as a Hash
+     */
+    asHash: function() {
+        return this.data;
+    }
+});
+
+Jx.Record.UPDATE = 1;
+Jx.Record.DELETE = 2;
+Jx.Record.INSERT = 3;// $Id: store.js 898 2010-05-10 04:08:44Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store 
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is the  store. It keeps track of data. It
+ * allows adding, deleting, iterating, sorting etc...
+ * 
+ * For the most part the store is pretty "dumb" meaning it 
+ * starts with very limited functionality. Actually, it can't
+ * even load data by itself. Instead, it needs to have protocols,
+ * strategies, and a record class passed to it that it can use.
+ * 
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store = new Class({
+    
+    Family: 'Jx.Store',
+    Extends: Jx.Object,
+    
+    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: protocol
+         * The protocol to use for communication in this store. The store 
+         * itself doesn't actually use it but it is accessed by the strategies
+         * to do their work. This option is required and the store won't work
+         * without it.
+         */
+        protocol: null,
+        /**
+         * Option: strategies
+         * This is an array of instantiated strategy objects that will work
+         * on this store. They provide many services such as loading data,
+         * paging data, saving, and sorting (and anything else you may need 
+         * can be written). If none are passed in it will use the default 
+         * Jx.Store.Strategy.Full
+         */
+        strategies: null,
+        /**
+         * Option: record
+         * This is a Jx.Store.Record instance or one of its subclasses. This is
+         * the class that will be used to hold each individual record in the
+         * store. Don't pass in a instance of the class but rather the class
+         * name itself. If none is passed in it will default to Jx.Record
+         */
+        record: null,
+        /**
+         * Option: recordOptions
+         * Options to pass to each record as it's created.
+         */
+        recordOptions: {
+            primaryKey: null
+        }
+    },
+    
+    /**
+     * 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,
+    /**
+     * APIProperty: id
+     * The id of this store.
+     */
+    id : null,
+    /**
+     * Property: loaded
+     * Tells whether the store has been loaded or not
+     */
+    loaded: false,
+    /**
+     * Property: ready
+     * Used to determine if the store is completely initialized.
+     */
+    ready: false,
+    
+    /**
+     * Method: init
+     * initialize the store, should be called by sub-classes
+     */
+    init: function () {
+        this.parent();
+        
+        if ($defined(this.options.id)) {
+            this.id = this.options.id;
+        } 
+        
+        if (!$defined(this.options.protocol)) {
+            this.ready = false;
+            return;
+        } else {
+            this.protocol = this.options.protocol;
+        }
+        
+        this.strategies = new Hash();
+        
+        if ($defined(this.options.strategies)) {
+            this.options.strategies.each(function(strategy){
+                this.addStrategy(strategy);
+            },this);
+        } else {
+            var strategy = new Jx.Store.Strategy.Full();
+            this.addStrategy(strategy);
+        }
+        
+        if ($defined(this.options.record)) {
+            this.record = this.options.record;
+        } else {
+            this.record = Jx.Record;
+        }
+        
+        
+    },
+    
+    /**
+     * Method: cleanup
+     * avoid memory leaks when a store is destroyed, should be called
+     * by sub-classes if overridden
+     */
+    cleanup: function () {
+        this.strategies.each(function(strategy){
+            strategy.destroy();
+        },this);
+        this.strategies = null;
+        this.protocol.destroy();
+        this.protocol = null;
+        this.record = null;
+    },
+    /**
+     * APIMethod: getStrategy
+     * returns the named strategy if it is present, null otherwise.
+     * 
+     * Parameters:
+     * name - the name of the strategy we're looking for
+     */
+    getStrategy: function (name) {
+        if (this.strategies.has(name)) {
+            return this.strategies.get(name);
+        }
+        return null;
+    },
+    /**
+     * APIMethod: addStrategy
+     * Allows the addition of strategies after store initialization. Handy to 
+     * have if some other class needs a strategy that is not present.
+     * 
+     * Parameters:
+     * strategy - the strategy to add to the store
+     */
+    addStrategy: function (strategy) {
+        this.strategies.set(strategy.name, strategy);
+        strategy.setStore(this);
+        strategy.activate();
+    },
+    /**
+     * APIMethod: load
+     * used to load the store. It simply fires an event that the strategies
+     * are listening for.
+     * 
+     * Parameters:
+     * params - a hash of parameters passed to the strategy for determining
+     *     what records to load.
+     */
+    load: function (params) {
+        this.fireEvent('storeLoad', params);
+    },
+    /**
+     * APIMethod: empty
+     * Clears the store of data
+     */
+    empty: function () {
+        if ($defined(this.data)) {
+            this.data.empty();
+        }
+    },
+    
+    /**
+     * 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)) {
+            return this.index < this.data.length - 1;
+        }
+        return null;
+    },
+
+    /**
+     * APIMethod: hasPrevious 
+     * Determines if there are records before the current
+     * one.
+     * 
+     * Returns: true | false
+     */
+    hasPrevious : function () {
+        if ($defined(this.data)) {
+            return this.index > 0;
+        }
+        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.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.length) {
+                this.index = this.data.length - 1;
+            }
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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);
+            return true;
+        } 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);
+            return true;
+        } 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.length - 1;
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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.length;
+        }
+        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)) {
+            return this.index;
+        }
+        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.length) {
+            this.index = index;
+            this.fireEvent('storeMove', this);
+            return true;
+        } else if (!$defined(this.data)) {
+            return null;
+        } else {
+            return false;
+        }
+    },
+    /**
+     * APIMethod: each
+     * allows iteration through the store's records. 
+     * NOTE: this function is untested
+     * 
+     * Parameters:
+     * fn - the function to execute for each record
+     * bind - the scope of the function
+     * ignoreDeleted - flag that tells the function whether to ignore records
+     *                  marked as deleted.
+     */
+    each: function (fn, bind, ignoreDeleted) {
+        var data;
+        if (ignoreDeleted) {
+            data = this.data.filter(function (record) {
+                return record.state !== Jx.Record.DELETE;
+            }, this);
+        } else {
+            data = this.data;
+        }
+        data.each(fn, bind);
+    },
+    /**
+     * APIMethod: get
+     * gets the data for the specified column
+     * 
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of 
+     *          the column) or an integer (the index of the column in the 
+     *          record).
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    get: function (column, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        return this.data[index].get(column);
+    },
+    /**
+     * APIMethod: set
+     * Sets the passed data for a particular column on the indicated record.
+     * 
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of 
+     *          the column) or an integer (the index of the column in the 
+     *          record).
+     * data - the data to set in the column of the record
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    set: function (column, data, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var ret = this.data[index].set(column, data);
+        ret.reverse();
+        ret.push(index);
+        ret.reverse();
+        //fire event with array [index, column, oldvalue, newValue]
+        this.fireEvent('storeColumnChanged', ret);
+    },
+    /**
+     * APIMethod: refresh
+     * Simply fires the storeRefresh event for strategies to listen for.
+     */
+    refresh: function () {
+        this.fireEvent('storeRefresh', this);
+    },
+    /**
+     * APIMethod: addRecord
+     * Adds given data to the end of the current store.
+     * 
+     * Parameters:
+     * data - The data to use in creating a record. This should be in whatever
+     *        form Jx.Store.Record, or the current subclass, needs it in.
+     * position - whether the record is added to the 'top' or 'bottom' of the 
+     *      store.
+     * insert - flag whether this is an "insert"
+     */
+    addRecord: function (data, position, insert) {
+        if (!$defined(this.data)) {
+            this.data = [];
+        }
+        
+        position = $defined(position)? position : 'bottom';
+        
+        var record;
+        if (data instanceof Jx.Record) {
+            record = data;
+        } else {
+            record = new (this.record)(this, this.options.columns, data, this.options.recordOptions);
+        }
+        if (insert) {
+            record.state = Jx.Record.INSERT;
+        }
+        if (position === 'top') {
+            //some literature claims that .shift() and .unshift() don't work reliably in IE
+            //so we do it this way.
+            this.data.reverse();
+            this.data.push(record);
+            this.data.reverse();
+        } else {
+            this.data.push(record);
+        }
+        this.fireEvent('storeRecordAdded', [this, record, position]);
+    },
+    /**
+     * APIMethod: addRecords
+     * Used to add multiple records to the store at one time.
+     * 
+     * Parameters:
+     * data - an array of data to add.
+     * position - 'top' or 'bottom'. Indicates whether to add at the top or
+     * the bottom of the store
+     */
+    addRecords: function (data, position) {
+        var def = $defined(data);
+        var type = Jx.type(data);
+        if (def && type === 'array') {
+            this.fireEvent('storeBeginAddRecords', this);
+            //if position is top, reverse the array or we'll add them in the
+            // wrong order.
+            if (position === 'top') {
+                data.reverse();
+            }
+            data.each(function(d){
+                this.addRecord(d, position);
+            },this);
+            this.fireEvent('storeEndAddRecords', this);
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: getRecord
+     * Returns the record at the given index or the current store index
+     * 
+     * Parameters:
+     * index - the index from which to return the record. Optional. Defaults
+     * to the current store index
+     */
+    getRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        
+        if (Jx.type(index) === 'number') {        
+            if ($defined(this.data) && $defined(this.data[index])) {
+                return this.data[index];
+            }
+        } else {
+            //Not sure what the point of this part is. It compares the 
+            //record to the index directly as if we passed in the record which 
+            //means we already have the record... huh???
+            var r;
+            this.data.each(function(record){
+                if (record === index) {
+                    r = record;
+                }
+            },this);
+            return r;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: replaceRecord
+     * Replaces the record at an existing index with a new record containing
+     * the passed in data.
+     * 
+     * Parameters:
+     * data - the data to use in creating the new record
+     * index - the index at which to place the new record. Optional. 
+     *          defaults to the current store index.
+     */
+    replace: function(data, index) {
+        if ($defined(data)) {
+            if (!$defined(index)) {
+                index = this.index;
+            }
+            var record = new this.record(this.options.columns,data);
+            var oldRecord = this.data[index];
+            this.data[index] = record;
+            this.fireEvent('storeRecordReplaced', [oldRecord, record]);
+            return true;
+        } 
+        return false;
+    },
+    /**
+     * APIMethod: deleteRecord
+     * Marks a record for deletion and removes it from the regular array of
+     * records. It adds it to a special holding array so it can be disposed 
+     * of later.
+     * 
+     * Parameters:
+     * index - the index at which to place the new record. Optional. 
+     *          defaults to the current store index.
+     */
+    deleteRecord: function(index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var record = this.data[index];
+        record.state = Jx.Record.DELETE;
+        // Set to Null or slice it out and compact the array???
+        //this.data[index] = null;
+        this.data.splice(index,1);
+        if (!$defined(this.deleted)) {
+            this.deleted = [];
+        }
+        this.deleted.push(record);
+        this.fireEvent('storeRecordDeleted', [record, this]);
+    },
+    /**
+     * APIMethod: insertRecord
+     * Shortcut to addRecord which facilitates marking a record as inserted.
+     * 
+     * Parameters:
+     * data - the data to use in creating this inserted record. Should be in
+     *          whatever form the current implementation of Jx.Record needs
+     * position - where to place the record. Should be either 'top' or
+     *    'bottom'.
+     */
+    insertRecord: function (data, position) {
+        this.addRecord(data, position, true);
+    },
+    
+    /**
+     * 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 (or index) of the column to search by
+     * value - the value to look for
+     */
+    findByColumn: function (column, value) {
+        if (typeof StopIteration === "undefined") {
+            StopIteration = new Error("StopIteration");
+        }
+
+        var index;
+        try {
+            this.data.each(function(record, idx){
+                if (record.equals(column, value)) {
+                    index = idx;
+                    throw StopIteration;
+                }
+            },this);
+        } catch (error) {
+            if (error !== StopIteration) {
+                throw error;
+            }
+            return index;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: removeRecord
+     * removes (but does not mark for deletion) a record at the given index
+     * or the current store index if none is passed in.
+     * 
+     * Parameters: 
+     * index - Optional. The store index of the record to remove.
+     */
+    removeRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        this.data.splice(index,1);
+        this.fireEvent('storeRecordRemoved', [this, index])
+    },
+    /**
+     * APIMethod: removeRecords
+     * Used to remove multiple contiguous records from a store. 
+     * 
+     * Parameters:
+     * first - where to start removing records (zero-based)
+     * last - where to stop removing records (zero-based, inclusive)
+     */
+    removeRecords: function (first, last) {
+        for (var i = first; i <= last; i++) {
+            this.removeRecord(first);
+        }
+        this.fireEvent('storeMultipleRecordsRemoved', [this, first, last]);
+    },
+    
+    /**
+	 * APIMethod: parseTemplate
+	 * parses the provided template to determine which store columns are
+	 * required to complete it.
+	 *
+	 * Parameters:
+	 * template - the template to parse
+	 */
+	parseTemplate: function (template) {
+	    //we parse the template based on the columns in the data store looking
+	    //for the pattern {column-name}. If it's in there we add it to the
+	    //array of ones to look fo
+	    var arr = [];
+	    this.options.columns.each(function (col) {
+	        var s = '{' + col.name + '}';
+	        if (template.contains(s)) {
+	            arr.push(col.name);
+	        }
+	    }, this);
+	    return arr;
+	},
+	
+	/**
+	 * APIMethod: fillTemplate
+	 * Actually does the work of getting the data from the store
+	 * and creating a single item based on the provided template
+	 * 
+	 * Parameters: 
+	 * index - the index of the data in the store to use in populating the
+	 *          template.
+	 * template - the template to fill
+	 * columnsNeeded - the array of columns needed by this template. should be 
+	 * 			obtained by calling parseTemplate().
+     * obj - an object with some prefilled keys to use in substituting.
+     *      Ones that are also in the store will be overwritten.
+	 */
+	fillTemplate: function (index, template, columnsNeeded, obj) {
+        var record = null;
+		if ($defined(index)) {
+            if (index instanceof Jx.Record) {
+                record = index;
+            } else {
+                record = this.getRecord(index);
+            }
+        } else {
+            record = this.getRecord(this.index);
+        }
+		
+	    //create the item
+	    var itemObj = $defined(obj) ? obj : {};
+	    columnsNeeded.each(function (col) {
+	        itemObj[col] = record.get(col);
+	    }, this);
+	    return template.substitute(itemObj);
+	}
+});// $Id: compare.js 855 2010-04-20 06:04:53Z jonlb at comcast.net $
+/**
+ * Class: Jx.Compare
+ *
+ * Extends: <Jx.Object>
+ *
+ * Class that holds functions for doing comparison operations.
+ * This class requires the mootools-more Date() extensions.
+ *
+ * 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({
+    Family: 'Jx.Compare',
+    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') {
+            var neg = false;
+            if (val.substr(0,1) == '-') {
+                neg = true;
+            }
+            val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g, "$1").replace(new RegExp("[^\\\d" + this.options.separator + "]", "g"), '').replace(/,/, '.')) || 0;
+            if (neg) {
+                val = val * -1;
+            }
+        }
+        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: sort.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: mergesort.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Mergesort',
+    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: heapsort.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Heapsort',
+    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: quicksort.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Quicksort',
+    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: nativesort.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Nativesort',
+    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: response.js 898 2010-05-10 04:08:44Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Response
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is used by the protocol to send information back to the calling 
+ * strategy (or other caller).
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Response = new Class({
+
+    Family: 'Jx.Store.Response',
+    Extends: Jx.Object,
+
+    /**
+     * Property: code
+     * This is the success/failure code
+     */
+    code: null,
+    /**
+     * Property: data
+     * The data passed received by the protocol.
+     */
+    data: null,
+    /**
+     * Property: meta
+     * The metadata received by the protocol
+     */
+    meta: null,
+    /**
+     * Property: requestType
+     * one of 'read', 'insert', 'delete', or 'update'
+     */
+    requestType: null,
+    /**
+     * Property: requestParams
+     * The parameters passed to the method that created this response
+     */
+    requestParams: null,
+    /**
+     * Property: request
+     * the mootools Request object used in this operation (if one is actually
+     * used)
+     */
+    request: null,
+    /**
+     * Property: error
+     * the error data received from the called page if any.
+     */
+    error: null,
+    /**
+     * APIMethod: success
+     * determines if this response represents a successful response
+     */
+    success: function () {
+        return this.code > 0;
+    }
+});
+
+Jx.Store.Response.WAITING = 2;
+Jx.Store.Response.SUCCESS = 1;
+Jx.Store.Response.FAILURE = 0;
+// $Id: protocol.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Store.Protocol
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all protocols. Protocols are used for communication, primarily,
+ * in Jx.Store. It may be possible to adapt them to be used in other places but
+ * that is not their intended function.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Protocol',
+    
+    parser: null,
+    
+    options: {},
+    
+    init: function () {
+        this.parent();
+        
+        if ($defined(this.options.parser)) {
+            this.parser = this.options.parser;
+        }
+    },
+    
+    cleanup: function () {
+        this.parser = null;
+        this.parent();
+    },
+    
+    /**
+     * APIMethod: read
+     * Supports reading data from a location. Abstract method that subclasses
+     * should implement.
+     * 
+     * Parameters:
+     * options - optional options for configuring the request
+     */
+    read: $empty,
+    /**
+     * APIMethod: insert
+     * Supports inserting data from a location. Abstract method that subclasses
+     * should implement.
+     * 
+     * Parameters:
+     * data - the data to use in creating the record in the form of one or more
+     *        Jx.Store.Record instances
+     * options - optional options for configuring the request
+     */
+    insert: $empty,
+    /**
+     * APIMethod: update
+     * Supports updating data at a location. Abstract method that subclasses
+     * should implement.
+     * 
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    update: $empty,
+    /**
+     * APIMethod: delete
+     * Supports deleting data from a location. Abstract method that subclasses
+     * should implement.
+     * 
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    "delete": $empty,
+    /**
+     * APIMethod: abort
+     * used to abort any of the above methods (where practical). Abstract method
+     * that subclasses should implement.
+     */
+    abort: $empty
+});// $Id: protocol.local.js 834 2010-04-05 18:17:01Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Protocol.Local
+ * 
+ * Extends: Jx.Store.Protocol
+ * 
+ * Based on the Protocol base class, the local protocol uses data that it is
+ * handed upon instantiation to process requests.
+ * 
+ * Constructor Parameters:
+ * data - The data to use 
+ * options - any options for the base protocol class
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * inspired by the openlayers.org implementation of a similar system
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Local = new Class({
+    
+    Extends: Jx.Store.Protocol,
+    
+    parameters: ['data', 'options'],
+    /**
+     * Property: data
+     * The data passed to the protocol
+     */
+    data: null,
+    
+    init: function () {
+        this.parent();
+        
+        if ($defined(this.options.data)) {
+            this.data = this.parser.parse(this.options.data);
+        }
+    },
+    /**
+     * APIMethod: read
+     * process requests for data and sends the appropriate response via the
+     * dataLoaded event.
+     * 
+     * Parameters: 
+     * options - options to use in processing the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response();
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+        
+        var page = options.data.page;
+        var itemsPerPage = options.data.itemsPerPage;
+        
+        if ($defined(this.data)) {
+            if (page <= 1 && itemsPerPage === -1) {
+                //send them all
+                resp.data = this.data;
+                resp.meta = { count: this.data.length };
+            } else {
+                var start = (page - 1) * itemsPerPage;
+                var end = start + itemsPerPage;
+                resp.data = this.data.slice(start, end);
+                resp.meta = { 
+                    page: page, 
+                    itemsPerPage: itemsPerPage,
+                    totalItems: this.data.length,
+                    totalPages: Math.ceil(this.data.length/itemsPerPage)
+                };
+            }
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        } else {
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        }                        
+    }
+    
+    /**
+     * The following methods are not implemented as they make no sense for a
+     * local protocol:
+     * - create
+     * - update 
+     * - delete
+     * - commit
+     * - abort
+     */
+});// $Id: protocol.ajax.js 898 2010-05-10 04:08:44Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Protocol.Ajax
+ * 
+ * Extends: <Jx.Store.Protocol>
+ * 
+ * This protocol is used to send and receive data via AJAX. It also has the
+ * capability to use a REST-style API.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Ajax = new Class({
+    
+    Extends: Jx.Store.Protocol,
+    
+    options: {
+        /**
+         * Option: requestOptions
+         * Options to pass to the mootools Request class
+         */
+        requestOptions: {
+            method: 'get'
+        },
+        /**
+         * Option: rest
+         * Flag indicating whether this protocol is operating against a RESTful
+         * web service
+         */
+        rest: false,
+        /**
+         * Option: urls
+         * This is a hash of the urls to use for each method. If the rest 
+         * option is set to true the only one needed will be the urls.rest.
+         * These can be overridden if needed by passing an options object into
+         * the various methods with the appropriate urls.
+         */
+        urls: {
+            rest: null,
+            insert: null,
+            read: null,
+            update: null,
+            'delete': null
+        }
+    },
+    
+    init: function() {
+        this.parent();
+    },
+    /**
+     * APIMethod: read
+     * Send a read request via AJAX
+     * 
+     * Parameters:
+     * options - the options to pass to the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response();
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+        
+        var req = new Request({
+            onSuccess: this.handleResponse.bind(this, resp)
+        });
+        
+        resp.request = req;
+        var temp = {};
+        if (this.options.rest) {
+            temp.url = this.options.urls.rest;
+        } else {
+            temp.url = this.options.urls.read;
+        }
+        
+        //set up options
+        var opts = $merge(this.options.requestOptions, temp, options);
+        
+        req.send(opts);
+        
+        resp.code = Jx.Store.Response.WAITING;
+        
+        return resp;
+        
+    },
+    /**
+     * Method: handleResponse
+     * Called as an event handler for a returning request. Parses the request's
+     * response into the actual response object.
+     * 
+     * Parameters: 
+     * response - the response related to teh returning request.
+     */
+    handleResponse: function (response) {
+        var req = response.request;
+        var str = req.xhr.responseText;
+        
+        var data = this.parser.parse(str);
+        if ($defined(data)) {
+            if ($defined(data.success) && data.success) {
+                if ($defined(data.data)) {
+                    response.data = data.data;
+                }
+                if ($defined(data.meta)) {
+                    response.meta = data.meta;
+                }
+                response.code = Jx.Store.Response.SUCCESS;
+            } else {
+                response.code = Jx.Store.Response.FAILURE;
+                response.error = $defined(data.error) ? data.error : null;
+            }
+        } else {
+            response.code = Jx.Store.Response.FAILURE;
+        }
+        this.fireEvent('dataLoaded', response);
+    },
+    /**
+     * APIMethod: insert
+     * Takes a Jx.Record instance and saves it
+     * 
+     * Parameters:
+     * record - a Jx.Store.Record or array of them
+     * options - options to pass to the request
+     */
+    insert: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+        } else {
+            options = $merge({url: this.options.urls.insert},options);
+        }
+        this.options.requestOptions.method = 'POST';
+        return this.run(record, options, "insert");
+    },
+    /**
+     * APIMethod: update
+     * Takes a Jx.Record and updates it via AJAX
+     * 
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    update: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'PUT';
+        } else {
+            options = $merge({url: this.options.urls.update},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "update");
+    },
+    /**
+     * APIMethod: delete
+     * Takes a Jx.Record and deletes it via AJAX
+     * 
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    "delete": function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'DELETE';
+        } else {
+            options = $merge({url: this.options.urls['delete']},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "delete");
+    },
+    /**
+     * APIMethod: abort
+     * aborts the request related to the passed in response.
+     * 
+     * Parameters:
+     * response - the response with the request to abort
+     */
+    abort: function (response) {
+        response.request.cancel();
+        
+    },
+    /**
+     * Method: run
+     * called by update, delete, and insert methods that actually does the work
+     * of kicking off the request.
+     * 
+     * Parameters:
+     * record - The Jx.Record to work with
+     * options - Options to pass to the request
+     * method - The name of the method calling this function
+     */
+    run: function (record, options, method) {
+        
+        this.options.requestOptions.data = {
+            data: this.parser.encode(record)
+        };
+        
+        var resp = new Jx.Store.Response();
+        resp.requestType = method;
+        resp.requestParams = [record, options, method];
+        
+        var req = new Request({
+            onSuccess: this.handleResponse.bind(this, resp)
+        });
+        
+        //set up options
+        var opts = $merge(this.options.requestOptions, options);
+        
+        req.send(opts);
+        
+        resp.code = Jx.Store.Response.WAITING;
+        resp.request = req;
+        
+        return resp;
+    }
+    
+});// $Id: strategy.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Store.Strategy
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all Jx.Store strategies
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Strategy',
+    /**
+     * APIProperty: store
+     * The store this strategy is associated with
+     */
+    store: null,
+    /**
+     * APIProperty: active
+     * whether this strategy has been activated or not.
+     */
+    active: null,
+    
+    /**
+     * Method: init
+     * initialize the strategy, should be called by subclasses
+     */
+    init: function () {
+        this.parent();
+        this.active = false;
+    },
+    /**
+     * APIMethod: setStore
+     * Associates this strategy with a particular store.
+     */
+    setStore: function (store) {
+        if (store instanceof Jx.Store) {
+            this.store = store;
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if (!this.active) {
+            this.active = true;
+            return true;
+        }
+        return false;
+    },
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if (this.active) {
+            this.active = false;
+            return true;
+        }
+        return false;
+    }
+});// $Id: strategy.full.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Full
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * This is a strategy for loading all of the data from a source at one time.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Strategy.Full = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'full',
+    
+    options:{},
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+        
+    },
+    /**
+     * APIMethod: load
+     * Called as the eventhandler for the store load method. Can also
+     * be called independently to load data into the current store.
+     * 
+     * Parameters:
+     * params - a hash of parameters to use in loading the data.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        var opts = {}
+        if ($defined(params)) {
+            opts.data = params;
+        } else {
+            opts.data = {};
+        }
+        opts.data.page = 0;
+        opts.data.itemsPerPage = -1;
+        this.store.protocol.read(opts);
+    },
+    
+    /**
+     * Method: loadStore
+     * Called as the event handler for the protocol's dataLoaded event. Checks
+     * the response for success and loads the data into the store if needed.
+     * 
+     * Parameters:
+     * resp - the response from the protocol
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            this.store.empty();
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.store.addRecords(resp.data);
+            this.store.loaded = true;
+            this.store.fireEvent('storeDataLoaded',this.store);
+        } else {
+            this.store.loaded = false;
+            this.store.fireEvent('storeDataLoadFailed', [this.store, resp]);
+        }
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the meta property of the response object and puts the data 
+     * where it belongs.
+     * 
+     * Parameters:
+     * meta - the meta data object from the response.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+    }
+});// $Id: strategy.paginate.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Paginate
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Store strategy for paginating results in a store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Paginate = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'paginate',
+    
+    options: {
+        /**
+         * Option: getPaginationParams
+         * a function that returns an object that holds the parameters
+         * necessary for getting paginated data from a protocol.
+         */
+        getPaginationParams: function () {
+            return {
+                page: this.page,
+                itemsPerPage: this.itemsPerPage
+            };
+        },
+        /**
+         * Option: startingItemsPerPage
+         * Used to set the intial itemsPerPage for the strategy. the pageSize 
+         * can be changed using the setPageSize() method.
+         */
+        startingItemsPerPage: 25,
+        /**
+         * Option: startingPage
+         * The page to start on. Defaults to 1 but can be set to any other 
+         * page.
+         */
+        startingPage: 1,
+        /**
+         * Option: expirationInterval
+         * The interval, in milliseconds (1000 = 1 sec), to hold a page of
+         * data before it expires. If the page is expired, the next time the
+         * page is accessed it must be retrieved again. Default is 5 minutes
+         * (1000 * 60 * 5)
+         */
+        expirationInterval: (1000 * 60 * 5),
+        /**
+         * Option: ignoreExpiration
+         * Set to TRUE to ignore the expirationInterval setting and never
+         * expire pages.
+         */
+        ignoreExpiration: false
+    },
+    /**
+     * Property: data
+     * holds the pages of data keyed by page number.
+     */
+    data: null,
+    /**
+     * property: cacheTimer
+     * holds one or more cache timer ids - one per page. Each page is set to 
+     * expire after a certain amount of time.
+     */
+    cacheTimer: null,
+    /**
+     * Property: page
+     * Tracks the page the store currently holds.
+     */
+    page: null,
+    /**
+     * Property: itemsPerPage
+     * The number of items on each page
+     */
+    itemsPerPage: null,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.data = new Hash();
+        this.cacheTimer = new Hash();
+        //set up bindings that we need here
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+        this.itemsPerPage = this.options.startingItemsPerPage;
+        this.page = this.options.startingPage;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+    },
+    /**
+     * APIMethod: load
+     * Called to load data into the store
+     * 
+     * Parameters:
+     * params - a Hash of parameters to use in getting data from the protocol.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        this.params = params;
+        var opts = {
+            data: $merge(params, this.options.getPaginationParams.apply(this))
+        };
+        this.store.protocol.read(opts);
+    },
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.data.set(this.page,resp.data);
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.empty();
+        this.store.loaded = false;
+        if (!this.options.ignoreExpiration) {
+            var id = this.expirePage.delay(this.options.expirationInterval, this, this.page);
+            this.cacheTimer.set(this.page,id);
+        }
+        this.store.addRecords(data);
+        this.store.loaded = true;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the metadata returned from the protocol and places it in the
+     * appropriate Vplaces.
+     * 
+     * Parameters:
+     * meta - the meta data object returned from the protocol.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.totalItems)) {
+            this.totalItems = meta.totalItems;
+        }
+        if ($defined(meta.totalPages)) {
+            this.totalPages = meta.totalPages;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+            
+    },
+    /**
+     * Method: expirePage
+     * Is called when a pages cache timer expires. Will expire the page by 
+     * erasing the page and timer. This will force a reload of the data the 
+     * next time the page is accessed.
+     * 
+     * Parameters:
+     * page - the page number to expire.
+     */
+    expirePage: function (page) {
+        this.data.erase(page);
+        this.cacheTimer.erase(page);
+    },
+    /**
+     * APIMethod: setPage
+     * Allows a caller (i.e. a paging toolbar) to move to a specific page.
+     * 
+     * Parameters:
+     * page - the page to move to. Can be any absolute page number, any number
+     *        prefaced with '-' or '+' (i.e. '-1', '+3'), 'first', 'last', 
+     *        'next', or 'previous'
+     */
+    setPage: function (page) {
+        if (Jx.type(page) === 'string') {
+            switch (page) {
+                case 'first':
+                    this.page = 1;
+                    break;
+                case 'last':
+                    this.page = this.totalPages;
+                    break;
+                case 'next':
+                    this.page++;
+                    break;
+                case 'previous':
+                    this.page--;
+                    break;
+                default:
+                    this.page = this.page + Jx.getNumber(page);
+                    break;
+            }
+        } else {
+            this.page = page;
+        }
+        if (this.cacheTimer.has(this.page)) {
+            $clear(this.cacheTimer.get(this.page));
+            this.cacheTimer.erase(this.page);
+        }
+        if (this.data.has(this.page)){
+            this.loadData(this.data.get(this.page));
+        } else {
+            this.load(this.params);
+        }
+    },
+    /**
+     * APIMethod: getPage
+     * returns the current page
+     */
+    getPage: function () {
+        return this.page;
+    },
+    /**
+     * APIMethod: getNumberOfPages
+     * returns the total number of pages.
+     */
+    getNumberOfPages: function () {
+        return this.totalPages;
+    },
+    /**
+     * APIMethod: setPageSize
+     * sets the current size of the pages. Calling this will expire every page 
+     * and force the current one to reload with the new size.
+     */
+    setPageSize: function (size) {
+        //set the page size 
+        this.itemsPerPage = size;
+        //invalidate all pages cached and reload the current one only
+        this.cacheTimer.each(function(val){
+            $clear(val);
+        },this);
+        this.cacheTimer.empty();
+        this.data.empty();
+        this.load();
+    },
+    /**
+     * APIMethod: getPageSize
+     * returns the current page size
+     */
+    getPageSize: function () {
+        return this.itemsPerPage;
+    },
+    /**
+     * APIMethod: getTotalCount
+     * returns the total number of items as received from the protocol.
+     */
+    getTotalCount: function () {
+        return this.totalItems;
+    }
+});/**
+ * Class: Jx.Store.Strategy.Progressive
+ *
+ * Extends: <Jx.Store.Strategy.Paginate>
+ *
+ * Store strategy for progressively obtaining results in a store. You can
+ * continually call nextPage() to get the next page and the store will retain
+ * all current data. You can set a maximum number of records the store should
+ * hold and whether it should dropRecords when that max is hit.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Progressive = new Class({
+    
+    Extends: Jx.Store.Strategy.Paginate,
+    
+    name: 'progressive',
+    
+    options: {
+        /**
+         * Option: maxRecords
+         * The maximum number of records we want in the store at any one time.
+         */
+        maxRecords: 1000,
+        /**
+         * Option: dropRecords
+         * Whether the strategy should drop records when the maxRecords limit 
+         * is reached. if this is false then maxRecords is ignored and data is
+         * always added to the bottom of the store. 
+         */
+        dropRecords: true
+    },
+    /**
+     * Property: startingPage
+     */
+    startingPage: 0,
+    /**
+     * Property: maxPages
+     */
+    maxPages: null,
+    /**
+     * Property: loadedPages
+     */
+    loadedPages: 0,
+    /**
+     * Property: loadAt
+     * Options are 'top' or 'bottom'. Defaults to 'bottom'.
+     */
+    loadAt: 'bottom',
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        if (this.options.dropPages) {
+            this.maxPages = Math.ceil(this.options.maxRecords/this.itemsPerPage);
+        }
+    },
+    
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.loaded = false;
+        this.store.addRecords(data, this.loadAt);
+        this.store.loaded = true;
+        this.loadedPages++;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    
+    /**
+     * APIMethod: nextPage
+     * Allows a caller (i.e. a paging toolbar) to load more data to the end of 
+     * the store
+     * 
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    nextPage: function (params) {
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.options.dropRecords && this.totalPages > this.startingPage + this.loadedPages) {
+            this.loadAt = 'bottom';
+            if (this.loadedPages >= this.maxPages) {
+                //drop records before getting more
+                this.startingPage++;
+                this.store.removeRecords(0,this.itemsPerPage - 1);
+                this.loadedPages--;
+            }
+        }
+        this.page = this.startingPage + this.loadedPages + 1;
+        this.load($merge(this.params, params));
+    },
+    /**
+     * APIMethod: previousPage
+     * Allows a caller to move back to the previous page.
+     *
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    previousPage: function (params) {
+        //if we're not dropping pages there's nothing to do
+        if (!this.options.dropRecords) {
+            return;
+        }
+        
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.startingPage > 0) {
+            this.loadAt = 'top';
+            if (this.loadedPages >= this.maxPages) {
+                //drop off end before loading previous pages
+                this.startingPage--;
+                this.store.removeRecords(this.options.maxRecords - this.itemsPerPage, this.options.maxRecords);
+                this.loadedPages--;
+            }
+            this.page = this.startingPage;
+            this.load($merge(this.params, params));
+        }
+    }
+});// $Id: strategy.save.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Save 
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * A Store strategy class for saving data via protocols
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Save = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'save',
+    
+    options: {
+        /**
+         * Option: autoSave
+         * Whether the strategy should be watching the store to save changes
+         * automatically. Set to True to watch events, set it to a number of 
+         * milliseconds to have the strategy save every so many seconds
+         */
+        autoSave: false
+    },
+    /**
+     * Property: failedChanges
+     * an array holding all failed requests
+     */
+    failedChanges: [],
+    /**
+     * Property: successfulChanges
+     * an array holding all successful requests
+     */
+    successfulChanges: [],
+    /**
+     * Property: totalChanges
+     * The total number of changes being processed. Used to determine
+     * when to fire off the storeChangesCompleted event on the store
+     */
+    totalChanges: 0,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.saveRecord = this.saveRecord.bind(this);
+        this.bound.onComplete = this.onComplete.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        if (Jx.type(this.options.autoSave) === 'number') {
+            this.periodicalId = this.save.periodical(this.options.autoSave, this);
+        } else if (this.options.autoSave) {
+            this.store.addEvent('storeRecordAdded', this.bound.save);
+            this.store.addEvent('storeColumnChanged', this.bound.save);
+            this.store.addEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        if ($defined(this.periodicalId)) {
+            $clear(this.periodicalId);
+        } else if (this.options.autoSave) {
+            this.store.removeEvent('storeRecordAdded', this.bound.save);
+            this.store.removeEvent('storeColumnChanged', this.bound.save);
+            this.store.removeEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: saveRecord
+     * Called by event handlers when store data is changed, updated, or
+     * deleted. If deleted, the record will be removed from the deleted array.
+     * 
+     * Parameters:
+     * record - The Jx.Record instance that was changed
+     * store - The instance of the store
+     */
+    saveRecord: function (store, record) {
+        //determine the status and route based on that
+        if (!this.updating && $defined(record.state)) {
+            if (this.totalChanges === 0) {
+                this.store.protocol.addEvent('dataLoaded', this.bound.completed);
+            }
+            this.totalChanges++;
+            var ret;
+            switch (record.state) {
+                case Jx.Record.UPDATE:
+                    ret = this.store.protocol.update(record);
+                    break;
+                case Jx.Record.DELETE:
+                    ret = this.store.protocol['delete'](record);
+                    break;
+                case Jx.Record.INSERT:
+                    ret = this.store.protocol.insert(record);
+                    break;
+            }
+            return ret;
+        }
+    },
+    /**
+     * APIMethod: save
+     * Called manually when the developer wants to save all data changes 
+     * in one shot. It will empty the deleted array and reset all other status 
+     * flags
+     */
+    save: function () {
+        //go through all of the data and figure out what needs to be acted on
+        if (this.store.loaded) {
+            var records = [];
+            records[Jx.Record.UPDATE] = [];
+            records[Jx.Record.INSERT] = [];
+            
+            this.store.data.each(function (record) {
+                if ($defined(record) && $defined(record.state)) {
+                    records[record.state].push(record);
+                }
+            }, this);
+            records[Jx.Record.DELETE] = this.store.deleted;
+            
+            records.flatten().each(function (record) {
+                this.saveRecord(null, record);
+            }, this);
+        }
+        
+    },
+    /**
+     * Method: onComplete
+     * Handles processing of the response(s) from the protocol. Each 
+     * update/insert/delete will have an individual response. If any responses 
+     * come back failed we will hold that response and send it to the caller
+     * via the fired event. This method is responsible for updating the status
+     * of each record as it returns and on inserts, it updates the primary key
+     * of the record. If it was a delete it will remove it permanently from
+     * the store's deleted array (provided it returns successful - based on
+     * the success attribute of the meta object). When all changes have been 
+     * accounted for the method fires a finished event and passes all of the 
+     * failed responses to the caller so they can be handled appropriately.
+     * 
+     * Parameters:
+     * response - the response returned from the protocol
+     */
+    onComplete: function (response) {
+        if (!response.success() || ($defined(response.meta) && !response.meta.success)) {
+            this.failedChanges.push(response);
+        } else {
+            //process the response
+            var record = response.requestParams[0];
+            if (response.requestType === 'delete') {
+                this.store.deleted.erase(record);
+            } else { 
+                if (response.requestType === 'insert') {
+                    if ($defined(response.data)) {
+                        this.updating = true;
+                        $H(response.data).each(function (val, key) {
+                            record.set(key, val);
+                        }, this);
+                        this.updating = false;
+                    }
+                }
+                record.state = null;
+            } 
+            this.successfulChanges.push(response);
+        }
+        this.totalChanges--;
+        if (this.totalChanges === 0) {
+            this.store.protocol.removeEvent('dataLoaded', this.bound.completed);
+            this.store.fireEvent('storeChangesCompleted', {
+                successful: this.successfulChanges,
+                failed: this.failedChanges
+            });
+        }
+    }
+});// $Id: strategy.sort.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Sort
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Strategy used for sorting stores. It can either be called manually or it
+ * can listen for specific events from the store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Sort = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'sort',
+    
+    options: {
+        /**
+         * Option: sortOnStoreEvents
+         * an array of events this strategy should listen for on the store and
+         * sort when it sees them.
+         */
+        sortOnStoreEvents: ['storeColumnChanged','storeDataLoaded'],
+        /**
+         * 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 : []
+    },
+    
+    /**
+     * Property: sorters
+     * an object listing the different sorters available
+     */
+    sorters : {
+        quick : "Quicksort",
+        merge : "Mergesort",
+        heap : "Heapsort",
+        'native' : "Nativesort"
+    },
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.sort = this.sort.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.addEvent(ev, this.bound.sort);
+            },this);
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.removeEvent(ev, this.bound.sort);
+            },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.store.count()) {
+            this.store.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.store.data = this.doSort(c[0], sort, this.store.data, true);
+        
+            if (c.length > 1) {
+                this.store.data = this.subSort(this.store.data, 0, 1);
+            }
+        
+            if ($defined(dir) && dir === 'desc') {
+                this.store.data.reverse();
+            }
+        
+            this.store.fireEvent('storeSortFinished', 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: resolveCol
+     * resolves the given column identifier and resolves it to the 
+     * actual column object in the store.
+     * 
+     * Parameters:
+     * col - the name or index of the required column.
+     */
+    resolveCol: function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.store.options.columns[col];
+        } else if (t === 'string') {
+            this.store.options.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;   
+    }
+});// $Id: parser.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Store.Parser
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all parsers
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Parser = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Parser',
+    
+    /**
+     * APIMethod: parse
+     * Reads data passed to it by a protocol and parses it into a specific
+     * format needed by the store/record.
+     * 
+     * Parameters:
+     * data - string of data to parse
+     */
+    parse: $empty,
+    /**
+     * APIMethod: encode
+     * Takes an Jx.Record object and encodes it into a format that can be transmitted 
+     * by a protocol.
+     * 
+     * Parameters:
+     * object - an object to encode
+     */
+    encode: $empty
+});// $Id: parser.json.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Store.Parser.JSON
+ * 
+ * Extends: <Jx.Store.Parser>
+ * 
+ * A Parser that handles encoding and decoding JSON strings
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Parser.JSON = new Class({
+    
+    Extends: Jx.Store.Parser,
+    
+    options: {
+        /**
+         * Option: secure
+         * Whether to use secure decoding. When using secure decoding the 
+         * parser will return null if any invalid JSON characters are in the
+         * passed in string. Defaults to false.
+         */
+        secure: false
+    },
+    /**
+     * APIMethod: parse
+     * Turns a string into a JSON object if possible.
+     * 
+     * Parameters:
+     * data - the string representation of the data we're parsing
+     */
+    parse: function (data) {
+        var type = Jx.type(data);
+        
+        if (type === 'string') {
+            return JSON.decode(data, this.options.secure);
+        }
+        //otherwise just return the data object
+        return data;
+    },
+    
+    /**
+     * APIMethod: encode
+     * Takes an object and turns it into JSON.
+     * 
+     * Parameters: 
+     * object - the object to encode
+     */
+    encode: function (object) {
+        var data;
+        if (object instanceof Jx.Record) {
+            data = object.asHash();
+        } else {
+            data = object;
+        }
+            
+        return JSON.encode(data);
+    }
+});// $Id: button.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Button
+ *
+ * Extends: <Jx.Widget>
+ *
+ * Jx.Button creates a clickable element that can be added to a web page.
+ * When the button is clicked, it fires a 'click' event.
+ *
+ * When you construct a new instance of Jx.Button, the button does not
+ * automatically get inserted into the web page.  Typically a button
+ * is used as part of building another capability such as a Jx.Toolbar.
+ * However, if you want to manually insert the button into your application,
+ * you may use the <Jx.Button::addTo> method to append or insert the button into the
+ * page.
+ *
+ * There are two modes for a button, normal and toggle.  A toggle button
+ * has an active state analogous to a checkbox.  A toggle button generates
+ * different events (down and up) from a normal button (click).  To create
+ * a toggle button, pass toggle: true to the Jx.Button constructor.
+ *
+ * To use a Jx.Button in an application, you should to register for the
+ * 'click' event.  You can pass a function in the 'onClick' option when
+ * constructing a button or you can call the addEvent('click', myFunction)
+ * method.  The addEvent method can be called several times, allowing more
+ * than one function to be called when a button is clicked.  You can use the
+ * removeEvent('click', myFunction) method to stop receiving click events.
+ *
+ * Example:
+ *
+ * (code)
+ * var button = new Jx.Button(options);
+ * button.addTo('myListItem'); // the id of an LI in the page.
+ * (end)
+ *
+ * (code)
+ * Example:
+ * var options = {
+ *     imgPath: 'images/mybutton.png',
+ *     tooltip: 'click me!',
+ *     label: 'click me',
+ *     onClick: function() {
+ *         alert('you clicked me');
+ *     }
+ * };
+ * var button = new Jx.Button(options);
+ * button.addEvent('click', anotherFunction);
+ *
+ * function anotherFunction() {
+ *   alert('a second alert for a single click');
+ * }
+ * (end)
+ *
+ * Events:
+ * click - the button was pressed and released (only if type is not 'toggle').
+ * down - the button is down (only if type is 'toggle')
+ * up - the button is up (only if the type is 'toggle').
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button = new Class({
+    Family: 'Jx.Button',
+    Extends: Jx.Widget,
+
+    options: {
+        /* 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
+         * 16.  If not provided, then the button will have no icon.
+         */
+        image: '',
+        /* Option: tooltip
+         * optional.  A string value to use as the alt/title attribute of the
+         * <A> tag that wraps the button, resulting in a tooltip that appears
+         * when the user hovers the mouse over a button in most browsers.  If
+         * not provided, the button will have no tooltip.
+         */
+        tooltip: '',
+        /* Option: label
+         * optional, default is no label.  A string value that is used as a
+         * label on the button. - use an object for localization: { set: 'Examples', key: 'lanKey', value: 'langValue' }
+         * see widget.js for details
+         */
+        label: '',
+        /* Option: toggle
+         * default true, whether the button is a toggle button or not.
+         */
+        toggle: false,
+        /* Option: toggleClass
+         * A class to apply to the button if it is a toggle button,
+         * 'jxButtonToggle' by default.
+         */
+        toggleClass: 'jxButtonToggle',
+        /* Option: pressedClass
+         * A class to apply to the button when it is pressed,
+         * 'jxButtonPressed' by default.
+         */
+        pressedClass: 'jxButtonPressed',
+        /* Option: activeClass
+         * A class to apply to the buttonwhen it is active,
+         * 'jxButtonActive' by default.
+         */
+        activeClass: 'jxButtonActive',
+
+        /* Option: active
+         * optional, default false.  Controls the initial state of toggle
+         * buttons.
+         */
+        active: false,
+        /* Option: enabled
+         * whether the button is enabled or not.
+         */
+        enabled: true,
+        /* Option: href
+         * set an href on the button's action object, typically an <a> tag.
+         * Default is javascript:void(0) and use onClick.
+         */
+        href: 'javascript:void(0);',
+        /* Option: template
+         * the HTML structure of the button.  As a minimum, there must be a
+         * containing element with a class of jxButtonContainer and an
+         * internal element with a class of jxButton.  jxButtonIcon and
+         * jxButtonLabel are used if present to put the image and label into
+         * the button.
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * used to auto-populate this object with element references when
+     * processing templates
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * create a new button.
+     */
+    render: function() {
+        this.parent();
+
+        /* is the button toggle-able? */
+        if (this.options.toggle) {
+            this.domObj.addClass(this.options.toggleClass);
+        }
+
+        // the clickable part of the button
+        if (this.domA) {
+            var hasFocus;
+            var mouseDown;
+            this.domA.set({
+                href: this.options.href,
+                title: this.getText(this.options.tooltip),
+                alt: this.getText(this.options.tooltip)
+            });
+            this.domA.addEvents({
+                click: this.clicked.bindWithEvent(this),
+                drag: (function(e) {e.stop();}).bindWithEvent(this),
+                mousedown: (function(e) {
+                    this.domA.addClass(this.options.pressedClass);
+                    hasFocus = true;
+                    mouseDown = true;
+                    this.focus();
+                }).bindWithEvent(this),
+                mouseup: (function(e) {
+                    this.domA.removeClass(this.options.pressedClass);
+                    mouseDown = false;
+                }).bindWithEvent(this),
+                mouseleave: (function(e) {
+                    this.domA.removeClass(this.options.pressedClass);
+                }).bindWithEvent(this),
+                mouseenter: (function(e) {
+                    if (hasFocus && mouseDown) {
+                        this.domA.addClass(this.options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keydown: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.addClass(this.options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keyup: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.removeClass(this.options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                blur: function() { hasFocus = false; }
+            });
+
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if (this.domImg) {
+            if (this.options.image || !this.options.label) {
+                this.domImg.set({
+                    title: this.getText(this.options.tooltip),
+                    alt: this.getText(this.options.tooltip)
+                });
+                if (this.options.image && this.options.image.indexOf(Jx.aPixel.src) == -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');
+            }
+        }
+
+        if (this.domLabel) {
+            if (this.options.label || this.domA.hasClass('jxDiscloser')) {
+                this.setLabel(this.options.label);
+            } else {
+                //this.domLabel.removeClass('jx'+this.type+'Label');
+                this.domLabel.setStyle('display','none');
+            }
+        }
+
+        if (this.options.id) {
+            this.domObj.set('id', this.options.id);
+        }
+
+        //update the enabled state
+        this.setEnabled(this.options.enabled);
+
+        //update the active state if necessary
+        if (this.options.active) {
+            this.options.active = false;
+            this.setActive(true);
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     *
+     * Parameters:
+     * evt - {Event} the user click event
+     */
+    clicked : function(evt) {
+        if (this.options.enabled && !this.isBusy()) {
+            if (this.options.toggle) {
+                this.setActive(!this.options.active);
+            } else {
+                this.fireEvent('click', {obj: this, event: evt});
+            }
+        }
+        //return false;
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the button is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the button is enabled or not
+     */
+    isEnabled: function() {
+        return this.options.enabled;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the 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');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    /**
+     * APIMethod: isActive
+     * For toggle buttons, this returns true if the toggle button is
+     * currently active and false otherwise.
+     *
+     * Returns:
+     * {Boolean} the active state of a toggle button
+     */
+    isActive: function() {
+        return this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the button
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the button
+     */
+    setActive: function(active) {
+        if (this.options.enabled && !this.isBusy()) {
+          if (this.options.active == active) {
+              return;
+          }
+          this.options.active = active;
+          if (this.domA) {
+              if (this.options.active) {
+                  this.domA.addClass(this.options.activeClass);
+              } else {
+                  this.domA.removeClass(this.options.activeClass);
+              }
+          }
+          this.fireEvent(active ? 'down':'up', this);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this button to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this button
+     */
+    setImage: function(path) {
+        this.options.image = path;
+        if (this.domImg) {
+            this.domImg.setStyle('backgroundImage',
+                                 "url("+this.options.image+")");
+            this.domImg.setStyle('display', path ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     * sets the text of the button.
+     *
+     * Parameters:
+     * label - {String} the new label for the button
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html', this.getText(label));
+            this.domLabel.setStyle('display', label || this.domA.hasClass('jxDiscloser') ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     * returns the text of the button.
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the button
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.domA) {
+            this.domA.set({
+                'title':this.getText(tooltip),
+                'alt':this.getText(tooltip)
+            });
+        }
+        //need to account for the tooltip on the image as well
+        if (this.domImg) {
+            //check if title and alt are set...
+            var t = this.domImg.get('title');
+            if ($defined(t)) {
+                //change it...
+                this.domImg.set({
+                    'title':this.getText(tooltip),
+                    'alt':this.getText(tooltip)
+                });
+            }
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this button
+     */
+    focus: function() {
+        if (this.domA) {
+            this.domA.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this button
+     */
+    blur: function() {
+        if (this.domA) {
+            this.domA.blur();
+        }
+    },
+
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the button on langChange Event for
+     * Internationalization
+     */
+    changeText : function(lang) {
+        this.parent();
+        this.setLabel(this.options.label);
+        this.setTooltip(this.options.tooltip);
+    }
+});
+// $Id: flyout.js 924 2010-05-26 16:03:06Z conrad.barthelmes $
+/**
+ * Class: Jx.Button.Flyout
+ *
+ * Extends: <Jx.Button>
+ *
+ * Flyout buttons expose a panel when the user clicks the button.  The
+ * panel can have arbitrary content.  You must provide any necessary 
+ * code to hook up elements in the panel to your application.
+ *
+ * When the panel is opened, the 'open' event is fired.  When the panel is
+ * closed, the 'close' event is fired.  You can register functions to handle
+ * these events in the options passed to the constructor (onOpen, onClose).
+ * 
+ * The user can close the flyout panel by clicking the button again, by
+ * clicking anywhere outside the panel and other buttons, or by pressing the
+ * 'esc' key.
+ *
+ * Flyout buttons implement <Jx.ContentLoader> which provides the hooks to
+ * insert content into the Flyout element.  Note that the Flyout element
+ * is not appended to the DOM until the first time it is opened, and it is
+ * removed from the DOM when closed.
+ *
+ * It is generally best to specify a width and height for your flyout content
+ * area through CSS to ensure that it works correctly across all browsers.
+ * You can do this for all flyouts using the .jxFlyout CSS selector, or you
+ * can apply specific styles to your content elements.
+ *
+ * A flyout closes other flyouts when it is opened.  It is possible to embed
+ * 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.
+ *
+ * Example:
+ * (code)
+ * var flyout = new Jx.Button.Flyout({
+ *      label: 'flyout',
+ *      content: 'flyoutContent',
+ *      onOpen: function(flyout) {
+ *          console.log('flyout opened');
+ *      },
+ *      onClose: function(flyout) {
+ *          console.log('flyout closed');
+ *      }
+ * });
+ * (end)
+ *
+ * Events:
+ * open - this event is triggered when the flyout is opened.
+ * close - this event is triggered when the flyout is closed.
+ *
+ * License: 
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Flyout = new Class({
+    Family: 'Jx.Button.Flyout',
+    Extends: Jx.Button,
+    Binds: ['keypressHandler', 'clickHandler'],
+    options: {
+        /* Option: template
+         * the HTML structure of the flyout button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',
+        /* Option: contentTemplate
+         * the HTML structure of the flyout content area
+         */
+        contentTemplate: '<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>',
+        /* Option: position
+         * where to position the flyout, see Jx.Widget::position
+         * for details on how to specify this option
+         */
+        position: {
+          horizontal: ['left left', 'right right'],
+          vertical: ['bottom top', 'top bottom']
+        },
+        /* Option: positionElement
+         * the element to position the flyout relative to, by default
+         * it is the domObj of this button and should only be changed
+         * if you really know what you are doing
+         */
+        positionElement: null
+    },
+    
+    /**
+     * Property: contentClasses
+     * the classes array for processing the contentTemplate
+     */
+    contentClasses: new Hash({
+        contentContainer: 'jxFlyout',
+        content: 'jxFlyoutContent'
+    }),
+    
+    /**
+     * Property: content
+     * the HTML element that contains the flyout content
+     */
+    content: null,
+    /**
+     * Method: render
+     * construct a new instance of a flyout button.  
+     */
+    render: function() {
+        if (!Jx.Button.Flyout.Stack) {
+            Jx.Button.Flyout.Stack = [];
+        }
+        this.parent();
+        this.processElements(this.options.contentTemplate, this.contentClasses);
+        
+        if (this.options.contentClass) {
+            this.content.addClass(this.options.contentClass);
+        }
+        
+        this.content.store('jxFlyout', this);
+        if(!this.options.loadOnDemand || this.options.active) {
+          this.loadContent(this.content);
+        }
+    },
+    cleanup: function() {
+      this.content.eliminate('jxFlyout');
+      this.content.destroy();
+      this.contentClasses.each(function(v,k){
+        this[k] = null;
+      }, this);
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * Override <Jx.Button::clicked> to hide/show the content area of the
+     * flyout.
+     *
+     * Parameters:
+     * e - {Event} the user event
+     */ 
+    clicked: function(e) {
+        if (!this.options.enabled) {
+            return;
+        }
+        if(this.options.loadOnDemand || !this.options.cacheContent) {
+          this.loadContent(this.content);
+        }
+        /* find out what we are contained by if we don't already know */
+        if (!this.owner) {
+            this.owner = document.body;
+            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 = document.id(node.parentNode);
+                }
+            }
+        }
+        if (Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1] == this) {
+            this.hide();
+            return;
+        } else if (this.owner != document.body) {
+            /* if we are part of another flyout, close any open flyouts
+             * inside the parent and register this as the current flyout
+             */
+            if (this.owner.currentFlyout == this) {
+                /* if the flyout to close is this flyout,
+                 * hide this and return */
+                this.hide();
+                return;
+            } else if (this.owner.currentFlyout) {
+                this.owner.currentFlyout.hide();
+            }
+            this.owner.currentFlyout = this;                
+        } else {
+            /* if we are at the top level, close the entire stack before
+             * we open
+             */
+            while (Jx.Button.Flyout.Stack.length) {
+                Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
+            }
+        }
+        // now we go on the stack.
+        Jx.Button.Flyout.Stack.push(this);
+        this.fireEvent('beforeOpen');
+
+        this.options.active = true;
+        this.domA.addClass(this.options.activeClass);
+        this.contentContainer.setStyle('visibility','hidden');
+        document.id(document.body).adopt(this.contentContainer);
+        this.content.getChildren().each(function(child) {
+            if (child.resize) { 
+                child.resize(); 
+            }
+        });
+        this.showChrome(this.contentContainer);
+        
+        var rel = this.options.positionElement || this.domObj;
+        var pos = $merge(this.options.position, {
+          offsets: this.chromeOffsets
+        });
+        this.position(this.contentContainer, rel, pos);
+
+        /* 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(document.id(this.content).getMarginBoxSize());
+        
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','');
+
+        document.addEvent('keydown', this.keypressHandler);
+        document.addEvent('click', this.clickHandler);
+        this.fireEvent('open', this);
+    },
+    /**
+     * APIMethod: hide
+     * Closes the flyout if open
+     */
+    hide: function() {
+        if (this.owner != document.body) {
+            this.owner.currentFlyout = null;            
+        }
+        Jx.Button.Flyout.Stack.pop();
+        this.setActive(false);
+        this.contentContainer.dispose();
+        this.unstack(this.contentContainer);
+        document.removeEvent('keydown', this.keypressHandler);    
+        document.removeEvent('click', this.clickHandler);
+        this.fireEvent('close', this);
+    },
+    /**
+     * Method: clickHandler
+     * hide flyout if the user clicks outside of the flyout 
+     */
+    clickHandler: function(e) {
+        e = new Event(e);
+        var elm = document.id(e.target);
+        var flyout = Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1];
+        if (!elm.descendantOf(flyout.content) &&
+            !elm.descendantOf(flyout.domObj)) {
+            flyout.hide();
+        }
+    },
+    /**
+     * Method: keypressHandler
+     * hide flyout if the user presses the ESC key 
+     */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
+        }
+    }
+});// $Id: colorpalette.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.ColorPalette
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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> suJx.Tooltipbclass is provided
+ * (<Jx.Button.Color>) that embeds a colour panel inside a button for easy use
+ * in toolbars.
+ *
+ * Colour changes are propogated via a change event.  To be notified
+ * of changes in a Jx.ColorPalette, use the addEvent method.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - triggered when the color changes.
+ * click - the user clicked on a color swatch (emitted after a change event)
+ *
+ * MooTools.lang keys:
+ * - colorpalette.alphaLabel
+ * 
+ * 
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ColorPalette = new Class({
+    Family: 'Jx.ColorPalette',
+    Extends: Jx.Widget,
+    /**
+     * Property: {HTMLElement} domObj
+     * the HTML element representing the color panel
+     */
+    domObj: null,
+    options: {
+        /* Option: parent
+         * default null, the DOM element to add the palette to.
+         */
+        parent: null,
+        /* Option: color
+         * default #000000, the initially selected color
+         */
+        color: '#000000',
+        /* Option: alpha
+         * default 100, the initial alpha value
+         */
+        alpha: 1,
+        /* Option: hexColors
+         * an array of hex colors for creating the palette, defaults to a
+         * set of web safe colors.
+         */
+        hexColors: ['00', '33', '66', '99', 'CC', 'FF']
+    },
+    /**
+     * Method: render
+     * initialize a new instance of Jx.ColorPalette
+     */
+    render: function() {
+        this.domObj = new Element('div', {
+            id: this.options.id,
+            'class':'jxColorPalette'
+        });
+
+        var top = new Element('div', {'class':'jxColorBar'});
+        var d = new Element('div', {'class':'jxColorPreview'});
+
+        this.selectedSwatch = new Element('div', {'class':'jxColorSelected'});
+        this.previewSwatch = new Element('div', {'class':'jxColorHover'});
+        d.adopt(this.selectedSwatch);
+        d.adopt(this.previewSwatch);
+
+        top.adopt(d);
+
+        this.colorInputLabel = new Element('label', {
+          'class':'jxColorLabel', 
+          html:'#'
+        });
+        top.adopt(this.colorInputLabel);
+
+        var cc = this.changed.bind(this);
+        this.colorInput = new Element('input', {
+            'class':'jxHexInput',
+            'type':'text',
+            'maxLength':6,
+            events: {
+                'keyup':cc,
+                'blur':cc,
+                'change':cc
+            }
+        });
+
+        top.adopt(this.colorInput);
+
+        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}) });
+        top.adopt(this.alphaLabel);
+
+        this.alphaInput = new Element('input', {
+            'class':'jxAlphaInput',
+            'type':'text',
+            'maxLength':3,
+            events: {
+                'keyup': this.alphaChanged.bind(this)
+            }
+        });
+        top.adopt(this.alphaInput);
+
+        this.domObj.adopt(top);
+
+        var swatchClick = this.swatchClick.bindWithEvent(this);
+        var swatchOver = this.swatchOver.bindWithEvent(this);
+
+        var table = new Element('table', {'class':'jxColorGrid'});
+        var tbody = new Element('tbody');
+        table.adopt(tbody);
+        for (var i=0; i<12; i++) {
+            var tr = new Element('tr');
+            for (var j=-3; j<18; j++) {
+                var bSkip = false;
+                var r, g, b;
+                /* hacky approach to building first three columns
+                 * because I couldn't find a good way to do it
+                 * programmatically
+                 */
+
+                if (j < 0) {
+                    if (j == -3 || j == -1) {
+                        r = g = b = 0;
+                        bSkip = true;
+                    } else {
+                        if (i<6) {
+                            r = g = b = i;
+                        } else {
+                            if (i == 6) {
+                                r = 5; g = 0; b = 0;
+                            } else if (i == 7) {
+                                r = 0; g = 5; b = 0;
+                            } else if (i == 8) {
+                                r = 0; g = 0; b = 5;
+                            } else if (i == 9) {
+                                r = 5; g = 5; b = 0;
+                            } else if (i == 10) {
+                                r = 0; g = 5; b = 5;
+                            } else if (i == 11) {
+                                r = 5; g = 0; b = 5;
+                            }
+                        }
+                    }
+                } else {
+                    /* remainder of the columns are built
+                     * based on the current row/column
+                     */
+                    r = parseInt(i/6,10)*3 + parseInt(j/6,10);
+                    g = j%6;
+                    b = i%6;
+                }
+                var bgColor = '#'+this.options.hexColors[r]+
+                                  this.options.hexColors[g]+
+                                  this.options.hexColors[b];
+
+                var td = new Element('td');
+                if (!bSkip) {
+                    td.setStyle('backgroundColor', bgColor);
+
+                    var a = new Element('a', {
+                        'class': 'colorSwatch ' + (((r > 2 && g > 2) || (r > 2 && b > 2) || (g > 2 && b > 2)) ? 'borderBlack': 'borderWhite'),
+                        'href':'javascript:void(0)',
+                        'title':bgColor,
+                        'alt':bgColor,
+                        events: {
+                            'mouseover': swatchOver,
+                            'click': swatchClick
+                        }
+                    });
+                    a.store('swatchColor', bgColor);
+                    td.adopt(a);
+                } else {
+                    var span = new Element('span', {'class':'emptyCell'});
+                    td.adopt(span);
+                }
+                tr.adopt(td);
+            }
+            tbody.adopt(tr);
+        }
+        this.domObj.adopt(table);
+        this.updateSelected();
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: swatchOver
+     * handle the mouse moving over a colour swatch by updating the preview
+     *
+     * Parameters:
+     * e - {Event} the mousemove event object
+     */
+    swatchOver: function(e) {
+        var a = e.target;
+
+        this.previewSwatch.setStyle('backgroundColor', a.retrieve('swatchColor'));
+    },
+
+    /**
+     * Method: swatchClick
+     * handle mouse click on a swatch by updating the color and hiding the
+     * panel.
+     *
+     * Parameters:
+     * e - {Event} the mouseclick event object
+     */
+    swatchClick: function(e) {
+        var a = e.target;
+
+        this.options.color = a.retrieve('swatchColor');
+        this.updateSelected();
+        this.fireEvent('click', this);
+    },
+
+    /**
+     * Method: changed
+     * handle the user entering a new colour value manually by updating the
+     * selected colour if the entered value is valid HEX.
+     */
+    changed: function() {
+        var color = this.colorInput.value;
+        if (color.substring(0,1) == '#') {
+            color = color.substring(1);
+        }
+        if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+            this.options.color = '#' +color.toUpperCase();
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * Method: alphaChanged
+     * handle the user entering a new alpha value manually by updating the
+     * selected alpha if the entered value is valid alpha (0-100).
+     */
+    alphaChanged: function() {
+        var alpha = this.alphaInput.value;
+        if (alpha.match(/^[0-9]{1,3}$/)) {
+            this.options.alpha = parseFloat(alpha/100);
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the colour represented by this colour panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function( color ) {
+        this.colorInput.value = color;
+        this.changed();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this colour panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function( alpha ) {
+        this.alphaInput.value = alpha;
+        this.alphaChanged();
+    },
+
+    /**
+     * Method: updateSelected
+     * update the colour panel user interface based on the current
+     * colour and alpha values
+     */
+    updateSelected: function() {
+        var styles = {'backgroundColor':this.options.color};
+
+        this.colorInput.value = this.options.color.substring(1);
+
+        this.alphaInput.value = parseInt(this.options.alpha*100,10);
+        if (this.options.alpha < 1) {
+            styles.opacity = this.options.alpha;
+            styles.filter = 'Alpha(opacity='+(this.options.alpha*100)+')';
+            
+        } else {
+            styles.opacity = 1;
+            //not sure what the proper way to remove the filter would be since
+            // I don't have IE to test against.
+            styles.filter = '';  
+        }
+        this.selectedSwatch.setStyles(styles);
+        this.previewSwatch.setStyles(styles);
+        
+        this.fireEvent('change', this);
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the
+     * widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	
+    	if ($defined(this.alphaLabel)) {
+    		this.alphaLabel.set('html', this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}));
+    	}
+    }
+});
+
+// $Id: color.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Button.Color
+ *
+ * Extends: <Jx.Button.Flyout>
+ *
+ * A <Jx.ColorPalette> wrapped up in a Jx.Button.  The button includes a
+ * preview of the currently selected color.  Clicking the button opens
+ * the color panel.
+ *
+ * A color button is essentially a <Jx.Button.Flyout> where the content
+ * of the flyout is a <Jx.ColorPalette>.  For performance, all color buttons
+ * share an instance of <Jx.ColorPalette> which means only one button can be
+ * open at a time.  This isn't a huge restriction as flyouts already close
+ * each other when opened.
+ *
+ * Example:
+ * (code)
+ * var colorButton = new Jx.Button.Color({
+ *     onChange: function(button) {
+ *         console.log('color:' + button.options.color + ' alpha: ' +
+ *                     button.options.alpha);
+ *     }
+ * });
+ * (end)
+ *
+ * Events:
+ * change - fired when the color is changed.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Color = new Class({
+    Family: 'Jx.Button.Color',
+    Extends: Jx.Button.Flyout,
+
+    /**
+     * Property: swatch
+     * the color swatch element used to portray the currently selected
+     * color
+     */
+    swatch: null,
+
+    options: {
+        /**
+         * Option: color
+         * a color to initialize the panel with, defaults to #000000
+         * (black) if not specified.
+         */
+        color: '#000000',
+        /**
+         * Option: alpha
+         * an alpha value to initialize the panel with, defaults to 1
+         *  (opaque) if not specified.
+         *
+         */
+        alpha: 100,
+        /*
+         * Option: template
+         * the HTML template for the color button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        swatch: 'jxButtonSwatchColor',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * creates a new color button.
+     */
+    render: function() {
+        if (!Jx.Button.Color.ColorPalette) {
+            Jx.Button.Color.ColorPalette = new Jx.ColorPalette(this.options);
+        }
+
+        /* we need to have an image to replace, but if a label is
+           requested, there wouldn't normally be an image. */
+        this.options.image = Jx.aPixel.src;
+
+        /* now we can safely initialize */
+        this.parent();
+        this.updateSwatch();
+
+        this.bound.changed = this.changed.bind(this);
+        this.bound.hide = this.hide.bind(this);
+    },
+    cleanup: function() {
+      this.bound.changed = false;
+      this.bound.hide = false;
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * override <Jx.Button.Flyout> to use a singleton color palette.
+     */
+    clicked: function() {
+        if (Jx.Button.Color.ColorPalette.currentButton) {
+            Jx.Button.Color.ColorPalette.currentButton.hide();
+        }
+        Jx.Button.Color.ColorPalette.currentButton = this;
+        Jx.Button.Color.ColorPalette.addEvent('change', this.bound.changed);
+        Jx.Button.Color.ColorPalette.addEvent('click', this.bound.hide);
+        this.content.appendChild(Jx.Button.Color.ColorPalette.domObj);
+        Jx.Button.Color.ColorPalette.domObj.setStyle('display', 'block');
+        Jx.Button.Flyout.prototype.clicked.apply(this, arguments);
+        /* setting these before causes an update problem when clicking on
+         * a second color button when another one is open - the color
+         * wasn't updating properly
+         */
+
+        Jx.Button.Color.ColorPalette.options.color = this.options.color;
+        Jx.Button.Color.ColorPalette.options.alpha = this.options.alpha/100;
+        Jx.Button.Color.ColorPalette.updateSelected();
+},
+
+    /**
+     * APIMethod: hide
+     * hide the color panel
+     */
+    hide: function() {
+        this.setActive(false);
+        Jx.Button.Color.ColorPalette.removeEvent('change', this.bound.changed);
+        Jx.Button.Color.ColorPalette.removeEvent('click', this.bound.hide);
+        Jx.Button.Flyout.prototype.hide.apply(this, arguments);
+        Jx.Button.Color.ColorPalette.currentButton = null;
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the color represented by this color panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function(color) {
+        this.options.color = color;
+        this.updateSwatch();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this color panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function(alpha) {
+        this.options.alpha = alpha;
+        this.updateSwatch();
+    },
+
+    /**
+     * Method: changed
+     * handle the color changing in the palette by updating the preview swatch
+     * in the button and firing the change event.
+     *
+     * Parameters:
+     * panel - <Jx.ColorPalette> the palette that changed.
+     */
+    changed: function(panel) {
+        var changed = false;
+        if (this.options.color != panel.options.color) {
+            this.options.color = panel.options.color;
+            changed = true;
+        }
+        if (this.options.alpha != panel.options.alpha * 100) {
+            this.options.alpha = panel.options.alpha * 100;
+            changed = true;
+        }
+        if (changed) {
+            this.updateSwatch();
+            this.fireEvent('change',this);
+        }
+    },
+
+    /**
+     * Method: updateSwatch
+     * Update the swatch color for the current color
+     */
+    updateSwatch: function() {
+        var styles = {'backgroundColor':this.options.color};
+        if (this.options.alpha < 100) {
+            styles.filter = 'Alpha(opacity='+(this.options.alpha)+')';
+            styles.opacity = this.options.alpha / 100;
+
+        } else {
+            styles.opacity = 1;
+            styles.filter = '';
+        }
+        this.swatch.setStyles(styles);
+    }
+});
+// $Id: menu.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.Menu
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A main menu as opposed to a sub menu that lives inside the menu.
+ *
+ * TODO: Jx.Menu
+ * revisit this to see if Jx.Menu and Jx.SubMenu can be merged into
+ * a single implementation.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu = new Class({
+    Family: 'Jx.Menu',
+    Extends: Jx.Widget,
+    // Binds: ['onMouseEnter','onMouseLeave','hide','keypressHandler'],
+    /**
+     * Property: button
+     * {<Jx.Button>} The button that represents this menu in a toolbar and
+     * opens the menu.
+     */
+    button : null,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML element that contains the menu items
+     * within the menu.
+     */
+    subDomObj : null,
+    /**
+     * Property: list
+     * {<Jx.List>} the list of items in the menu
+     */
+    list: null,
+
+    parameters: ['buttonOptions', 'options'],
+
+    options: {
+        /**
+         * Option: exposeOnHover
+         * {Boolean} default false, if set to true the menu will show
+         * when the mouse hovers over it rather than when it is clicked.
+         */
+        exposeOnHover: false,
+        /**
+         * Option: hideDelay
+         * {Integer} default 0, if greater than 0, this is the number of
+         * milliseconds to delay before hiding a menu when the mouse leaves
+         * the menu button or list.
+         */
+        hideDelay: 0,
+        template: "<div class='jxMenuContainer'><ul class='jxMenu'></ul></div>",
+        buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><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: new Hash({
+        contentContainer: 'jxMenuContainer',
+        subDomObj: 'jxMenu'
+    }),
+    
+    init: function() {
+        this.bound.stop = function(e){e.stop();};
+        this.bound.remove = function(item) {item.setOwner(null);};
+        this.bound.show = this.show.bind(this);
+        this.bound.mouseenter = this.onMouseEnter.bind(this);
+        this.bound.mouseleave = this.onMouseLeave.bind(this);
+        this.bound.keypress = this.keypressHandler.bind(this);
+        this.bound.hide = this.hide.bind(this);
+        this.parent();
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.
+     */
+    render : function() {
+        this.parent();
+        if (!Jx.Menu.Menus) {
+            Jx.Menu.Menus = [];
+        }
+
+        
+        this.contentContainer.addEvent('contextmenu', this.bound.stop);
+        this.list = new Jx.List(this.subDomObj, {
+            onRemove: this.bound.remove
+        });
+
+        /* if options are passed, make a button inside an LI so the
+           menu can be embedded inside a toolbar */
+        if (this.options.buttonOptions) {
+            this.button = new Jx.Button($merge(this.options.buttonOptions,{
+                template: this.options.buttonTemplate,
+                onClick:this.bound.show
+            }));
+
+            this.button.domA.addEvent('mouseenter', this.bound.mouseenter);
+            this.button.domA.addEvent('mouseleave', this.bound.mouseleave);
+
+            this.domObj = this.button.domObj;
+            this.domObj.store('jxMenu', this);
+        }
+        
+        this.subDomObj.addEvent('mouseenter', this.bound.mouseenter);
+        this.subDomObj.addEvent('mouseleave', this.bound.mouseleave);
+
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    cleanup: function() {
+      this.list.removeEvent('remove', this.bound.remove);
+      this.list.destroy();
+      this.list = null;
+      if (this.button) {
+        this.domObj.eliminate('jxMenu');
+        this.domObj = null;
+        this.button.removeEvent('click', this.bound.show);
+        this.button.domA.removeEvents({
+          mouseenter: this.bound.mouseenter,
+          mouseleave: this.bound.mouseleave
+        });
+        
+        this.button.destroy();
+        this.button = null;
+      }
+      this.subDomObj.removeEvents({
+        mouseenter: this.bound.mouseenter,
+        mouseleave: this.bound.mouseleave
+      });
+      this.subDomObj.removeEvents();
+      this.contentContainer.removeEvent('contextmenu', this.bound.stop);
+      this.subDomObj.destroy();
+      this.contentContainer.destroy();
+      this.bound.remove = null;
+      this.bound.show = null;
+      this.bound.stop = null;
+      this.bound.mouseenter = null;
+      this.bound.mouseleave = null;
+      this.bound.keypress = null;
+      this.bound.hide = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     *     can be added by passing an array of menu items.
+     * position - the index to add the item at, defaults to the end of the
+     *     menu
+     */
+    add: function(item, position, owner) {
+        if (Jx.type(item) == 'array') {
+            item.each(function(i){
+                i.setOwner(owner||this);
+            }, this);
+        } else {
+            item.setOwner(owner||this);
+        }
+        this.list.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: empty
+     * Empty the menu of items
+     */
+    empty: function() {
+      this.list.each(function(item){
+        if (item.empty) {
+          item.empty();
+        }
+        item.setOwner(null);
+      }, this);
+      this.list.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the menu by hiding it.
+     */
+    deactivate: function() {this.hide();},
+    /**
+     * Method: onMouseOver
+     * Handle the user moving the mouse over the button for this menu
+     * by showing this menu and hiding the other menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseEnter: function(e) {
+        if (this.hideTimer) {
+          $clear(this.hideTimer);
+          this.hideTimer = null;
+        }
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] != this) {
+            this.show.delay(1,this);
+        } else if (this.options.exposeOnHover) {
+          if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+            Jx.Menu.Menus[0] = null;
+          }
+          this.show.delay(1,this);
+        }
+    },
+    /**
+     * Method: onMouseLeave
+     * Handle the user moving the mouse off this button or menu by
+     * starting the hide process if so configured.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseLeave: function(e) {
+      if (this.options.hideDelay > 0) {
+        this.hideTimer = (function(){
+          this.deactivate();
+        }).delay(this.options.hideDelay, this);
+      }
+    },
+    
+    /**
+     * Method: eventInMenu
+     * determine if an event happened inside this menu or a sub menu
+     * of this menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     *
+     * Returns:
+     * {Boolean} true if the event happened in the menu or
+     * a sub menu of this menu, false otherwise
+     */
+    eventInMenu: function(e) {
+        var target = document.id(e.target);
+        if (!target) {
+            return false;
+        }
+        if (target.descendantOf(this.domObj) ||
+            target.descendantOf(this.subDomObj)) {
+            return true;
+        } else {
+            var ul = target.findElement('ul');
+            if (ul) {
+                var sm = ul.retrieve('jxSubMenu');
+                if (sm) {
+                    var owner = sm.owner;
+                    while (owner) {
+                        if (owner == this) {
+                            return true;
+                        }
+                        owner = owner.owner;
+                    }
+                }
+            }
+            return false;
+        }
+    },
+
+    /**
+     * APIMethod: hide
+     * Hide the menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    hide: function(e) {
+        if (e) {
+            if (this.visibleItem && this.visibleItem.eventInMenu) {
+                if (this.visibleItem.eventInMenu(e)) {
+                    return;
+                }
+            } else if (this.eventInMenu(e)) {
+                return;
+            }
+        }
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+            Jx.Menu.Menus[0] = null;
+        }
+        if (this.button && this.button.domA) {
+            this.button.domA.removeClass(this.button.options.activeClass);
+        }
+        this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
+        document.removeEvent('mousedown', this.bound.hide);
+        document.removeEvent('keydown', this.bound.keypress);
+        this.unstack(this.contentContainer);
+        this.contentContainer.dispose();
+        this.visibleItem = null;
+        this.fireEvent('hide', this);
+    },
+    /**
+     * APIMethod: show
+     * Show the menu
+     */
+    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;
+            }
+        }
+        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
+         */
+         this.contentContainer.setStyles({
+           width: null,
+           height: null
+         });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+        this.showChrome(this.contentContainer);
+
+        this.position(this.contentContainer, this.domObj, $merge({
+            offsets: this.chromeOffsets
+        }, this.options.position));
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','visible');
+
+        if (this.button && this.button.domA) {
+            this.button.domA.addClass(this.button.options.activeClass);
+        }
+
+        /* fix bug in IE that closes the menu as it opens 
+         * because of bubbling (I think)
+         */
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keydown', this.bound.keypress);
+        this.fireEvent('show', this);
+    },
+    /**
+     * APIMethod: setVisibleItem
+     * Set the sub menu that is currently open
+     *
+     * Parameters:
+     * obj- {<Jx.SubMenu>} the sub menu that just became visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    },
+
+    /* hide flyout if the user presses the ESC key */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            this.hide();
+        }
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the menu is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the menu is enabled or not
+     */
+    isEnabled: function() {
+        return this.button ? this.button.isEnabled() : this.options.enabled ;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the menu.
+     *
+     * Parameters:
+     * enabled - {Boolean} the new enabled state of the menu
+     */
+    setEnabled: function(enabled) {
+        return this.button ? this.button.setEnabled(enabled) : this.options.enable;
+    },
+    /**
+     * APIMethod: isActive
+     * returns true if the menu is open.
+     *
+     * Returns:
+     * {Boolean} the active state of the menu
+     */
+    isActive: function() {
+        return this.button ? this.button.isActive() : this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the menu
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the menu
+     */
+    setActive: function(active) {
+        if (this.button) {
+          this.button.setActive(active);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this menu to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this menu
+     */
+    setImage: function(path) {
+        if (this.button) {
+          this.button.setImage(path);
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     *
+     * sets the text of the menu.
+     *
+     * Parameters:
+     *
+     * label - {String} the new label for the menu
+     */
+    setLabel: function(label) {
+        if (this.button) {
+          this.button.setLabel(label);
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     *
+     * returns the text of the menu.
+     */
+    getLabel: function() {
+        return this.button ? this.button.getLabel() : '';
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the menu
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.button) {
+          this.button.setTooltip(tooltip);
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this menu
+     */
+    focus: function() {
+        if (this.button) {
+          this.button.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this menu
+     */
+    blur: function() {
+        if (this.button) {
+          this.button.blur();
+        }
+    }
+});
+
+// $Id: set.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.ButtonSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only
+ * one of the buttons is active.  All the buttons need to have been created
+ * with the toggle option set to true for this to work.
+ *
+ * Example:
+ * (code)
+ * var toolbar = new Jx.Toolbar('bar');
+ * var buttonSet = new Jx.ButtonSet();
+ *
+ * var b1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
+ * var b2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
+ * var b3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
+ * var b4 = new Jx.Button({label: 'b4', toggle:true, contentID: 'content4'});
+ *
+ * buttonSet.add(b1,b2,b3,b4);
+ * (end)
+ *
+ * Events:
+ * change - the current button has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ButtonSet = new Class({
+    Family: 'Jx.ButtonSet',
+    Extends: Jx.Object,
+    Binds: ['buttonChanged'],
+    /**
+     * Property: buttons
+     * {Array} array of buttons that are managed by this button set
+     */
+    buttons: [],
+    
+    cleanup: function() {
+      this.buttons.each(function(b){
+        b.removeEvent('down', this.buttonChanged);
+        b.setActive = null;
+      },this);
+      this.activeButton = null;
+      this.buttons = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: add
+     * Add one or more <Jx.Button>s to the ButtonSet.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button
+     * set.  More than one button can be added by passing extra parameters to
+     * this method.
+     */
+    add : function() {
+        $A(arguments).each(function(button) {
+            if (button.domObj.hasClass(button.options.toggleClass)) {
+                button.domObj.removeClass(button.options.toggleClass);
+                button.domObj.addClass(button.options.toggleClass+'Set');
+            }
+            button.addEvent('down',this.buttonChanged);
+            button.setActive = function(active) {
+                if (button.options.active && this.activeButton == button) {
+                    return;
+                } else {
+                    Jx.Button.prototype.setActive.apply(button, [active]);
+                }
+            }.bind(this);
+            if (!this.activeButton || button.options.active) {
+                button.options.active = false;
+                button.setActive(true);
+            }
+            this.buttons.push(button);
+        }, this);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * Remove a button from this Button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove.
+     */
+    remove : function(button) {
+        this.buttons.erase(button);
+        if (this.activeButton == button) {
+            if (this.buttons.length) {
+                this.buttons[0].setActive(true);
+            }
+            button.removeEvent('down',this.buttonChanged);
+            button.setActive = Jx.Button.prototype.setActive;
+        }
+    },
+    /**
+     * APIMethod: empty
+     * empty the button set and clear the active button
+     */
+    empty: function() {
+      this.buttons = [];
+      this.activeButton = null;
+    },
+    /**
+     * APIMethod: setActiveButton
+     * Set the active button to the one passed to this method
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    setActiveButton: function(button) {
+        var b = this.activeButton;
+        this.activeButton = button;
+        if (b && b != button) {
+            b.setActive(false);
+        }
+    },
+    /**
+     * Method: buttonChanged
+     * Handle selection changing on the buttons themselves and activate the
+     * appropriate button in response.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    buttonChanged: function(button) {
+        this.setActiveButton(button);
+        this.fireEvent('change', this);
+    }
+});// $Id: multi.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.Button.Multi
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * that button to replace the active button in the toolbar and performs
+ * the button's regular action.
+ *
+ * Other buttons can be added to the Multi button using the add method.
+ *
+ * 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
+ * added buttons are placed.  The main button content is cloned from the
+ * last button clicked (or first button added).
+ *
+ * The Multi button does not trigger any events itself, only the contained
+ * buttons trigger events.
+ *
+ * Example:
+ * (code)
+ * var b1 = new Jx.Button({
+ *     label: 'b1',
+ *     onClick: function(button) {
+ *         console.log('b1 clicked');
+ *     }
+ * });
+ * var b2 = new Jx.Button({
+ *     label: 'b2',
+ *     onClick: function(button) {
+ *         console.log('b2 clicked');
+ *     }
+ * });
+ * var b3 = new Jx.Button({
+ *     label: 'b3',
+ *     onClick: function(button) {
+ *         console.log('b3 clicked');
+ *     }
+ * });
+ * var multiButton = new Jx.Button.Multi();
+ * multiButton.add(b1, b2, b3);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Multi = new Class({
+    Family: 'Jx.Button.Multi',
+    Extends: Jx.Button,
+
+    /**
+     * Property: {<Jx.Button>} activeButton
+     * the currently selected button
+     */
+    activeButton: null,
+
+    /**
+     * Property: buttons
+     * {Array} the buttons added to this multi button
+     */
+    buttons: null,
+
+    options: {
+        /* Option: template
+         * the button template for a multi button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>',
+        menuOptions: {}
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel',
+        domDisclose: 'jxButtonDisclose'
+    }),
+
+    /**
+     * Method: render
+     * construct a new instance of Jx.Button.Multi.
+     */
+    render: function() {
+        this.parent();
+        this.buttons = [];
+
+        this.menu = new Jx.Menu({}, this.options.menuOptions);
+        this.menu.button = this;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.bound.click = this.clicked.bind(this);
+
+        if (this.domDisclose) {
+            var button = this;
+            var hasFocus;
+            
+            this.bound.disclose = {
+              click: function(e) {
+                  if (this.list.count() === 0) {
+                      return;
+                  }
+                  if (!button.options.enabled) {
+                      return;
+                  }
+                  this.contentContainer.setStyle('visibility','hidden');
+                  this.contentContainer.setStyle('display','block');
+                  document.id(document.body).adopt(this.contentContainer);
+                  /* we have to size the container for IE to render the chrome
+                   * correctly but just in the menu/sub menu case - there is
+                   * some horrible peekaboo bug in IE related to ULs that we
+                   * just couldn't figure out
+                   */
+                  this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+                  this.showChrome(this.contentContainer);
+
+                  this.position(this.contentContainer, this.button.domObj, {
+                      horizontal: ['right right'],
+                      vertical: ['bottom top', 'top bottom'],
+                      offsets: this.chromeOffsets
+                  });
+
+                  this.contentContainer.setStyle('visibility','');
+
+                  document.addEvent('mousedown', this.bound.hide);
+                  document.addEvent('keyup', this.bound.keypress);
+
+                  this.fireEvent('show', this);
+              }.bindWithEvent(this.menu),
+              mouseenter:function(){
+                  document.id(this.domObj.firstChild).addClass('jxButtonHover');
+                  if (hasFocus) {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bind(this),
+              mouseleave:function(){
+                  document.id(this.domObj.firstChild).removeClass('jxButtonHover');
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bind(this),
+              mousedown: function(e) {
+                  this.domDisclose.addClass(this.options.pressedClass);
+                  hasFocus = true;
+                  this.focus();
+              }.bindWithEvent(this),
+              mouseup: function(e) {
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bindWithEvent(this),
+              keydown: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              keyup: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.removeClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              blur: function() { hasFocus = false; }
+            };
+
+            this.domDisclose.addEvents({
+              click: this.bound.disclose.click,
+              mouseenter: this.bound.disclose.mouseenter,
+              mouseleave: this.bound.disclose.mouseleave,
+              mousedown: this.bound.disclose.mousedown,
+              mouseup: this.bound.disclose.mouseup,
+              keydown: this.bound.disclose.keydown,
+              keyup: this.bound.disclose.keyup,
+              blur: this.bound.disclose.blur
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domDisclose, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+        this.bound.show = function() {
+            this.domA.addClass(this.options.activeClass);
+        }.bind(this);
+        this.bound.hide = function() {
+            if (this.options.active) {
+                this.domA.addClass(this.options.activeClass);
+            }
+        }.bind(this);
+
+        this.menu.addEvents({
+            'show': this.bound.show,
+            'hide': this.bound.hide
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+    },
+    cleanup: function() {
+      // clean up the discloser
+      if (this.domDisclose) {
+        this.domDisclose.removeEvents({
+          click: this.bound.disclose.click,
+          mouseenter: this.bound.disclose.mouseenter,
+          mouseleave: this.bound.disclose.mouseleave,
+          mousedown: this.bound.disclose.mousedown,
+          mouseup: this.bound.disclose.mouseup,
+          keydown: this.bound.disclose.keydown,
+          keyup: this.bound.disclose.keyup,
+          blur: this.bound.disclose.blur
+        });
+      }
+      
+      // clean up the button set
+      this.buttonSet.destroy();
+      this.buttonSet = null;
+      
+      // clean up the buttons array
+      this.buttons.each(function(b){
+        b.removeEvents();
+        this.menu.remove(b.multiButton);
+        b.multiButton.destroy();
+        b.multiButton = null;
+        b.destroy();
+      },this);
+      this.buttons.empty();
+      this.buttons = null;
+      
+      // clean up the menu object
+      this.menu.removeEvents({
+        'show': this.bound.show,
+        'hide': this.bound.hide
+      });
+      // unset the menu button because it references this object
+      this.menu.button = null;
+      this.menu.destroy();
+      this.menu = null;
+      
+      // clean up binds and call parent to finish
+      this.bound.show = null;
+      this.bound.hide = null;
+      this.bound.clicked = null;
+      this.bound.disclose = null;
+      this.activeButton = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * adds one or more buttons to the Multi button.  The first button
+     * added becomes the active button initialize.  This function
+     * takes a variable number of arguments, each of which is expected
+     * to be an instance of <Jx.Button>.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance, may be repeated in the parameter list
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(theButton){
+            if (!theButton instanceof Jx.Button) {
+                return;
+            }
+            theButton.domA.addClass('jxDiscloser');
+            theButton.setLabel(theButton.options.label);
+            this.buttons.push(theButton);
+            var f = this.setButton.bind(this, theButton);
+            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;
+            }
+            var button = new Jx.Menu.Item(opts);
+            this.buttonSet.add(button);
+            this.menu.add(button);
+            theButton.multiButton = button;
+            theButton.domA.addClass('jxButtonMulti');
+            if (!this.activeButton) {
+                this.domA.dispose();
+                this.setActiveButton(theButton);
+            }
+        }, this);
+    },
+    /**
+     * APIMethod: remove
+     * remove a button from a multi button
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove
+     */
+    remove: function(button) {
+        if (!button || !button.multiButton) {
+            return;
+        }
+        // the toolbar will only remove the li.toolItem, which is
+        // the parent node of the multiButton's domObj.
+        if (this.menu.remove(button.multiButton)) {
+            button.multiButton = null;
+            if (this.activeButton == button) {
+                // if any buttons are left that are not this button
+                // then set the first one to be the active button
+                // otherwise set the active button to nothing
+                if (!this.buttons.some(function(b) {
+                    if (b != button) {
+                        this.setActiveButton(b);
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }, this)) {
+                    this.setActiveButton(null);
+                }
+            }
+            this.buttons.erase(button);
+        }
+    },
+    /**
+     * APIMethod: empty
+     * remove all buttons from the multi button
+     */
+    empty: function() {
+      this.buttons.each(function(b){this.remove(b);}, this);
+    },
+    /**
+     * APIMethod: setActiveButton
+     * update the menu item to be the requested button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance that was added to this multi button.
+     */
+    setActiveButton: function(button) {
+        if (this.activeButton) {
+            this.activeButton.domA.dispose();
+            this.activeButton.domA.removeEvent('click', this.bound.click);
+        }
+        if (button && button.domA) {
+            this.domObj.grab(button.domA, 'top');
+            this.domA = button.domA;
+            this.domA.addEvent('click', this.bound.click);
+            if (this.options.toggle) {
+                this.options.active = false;
+                this.setActive(true);
+            }
+        }
+        this.activeButton = button;
+    },
+    /**
+     * Method: setButton
+     * update the active button in the menu item, trigger the button's action
+     * and hide the flyout that contains the buttons.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} The button to set as the active button
+     */
+    setButton: function(button) {
+        this.setActiveButton(button);
+        button.clicked();
+    }
+});// $Id: menu.item.js 934 2010-05-28 15:12:44Z pagameba $
+/**
+ * Class: Jx.Menu.Item
+ *
+ * Extends: <Jx.Button>
+ *
+ * A menu item is a single entry in a menu.  It is typically composed of
+ * a label and an optional icon.  Selecting the menu item emits an event.
+ *
+ * Jx.Menu.Item is represented by a <Jx.Button> with type MenuItem and the
+ * associated CSS changes noted in <Jx.Button>.  The container of a MenuItem
+ * is an 'li' element.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - fired when the menu item is clicked.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Item = new Class({
+    Family: 'Jx.Menu.Item',
+    Extends: Jx.Button,
+    // Binds: ['onMouseOver'],
+    /**
+     * Property: owner
+     * {<Jx.SubMenu> or <Jx.Menu>} the menu that contains the menu item.
+     */
+    owner: null,
+    options: {
+        //image: null,
+        label: '&nbsp;',
+        toggleClass: 'jxMenuItemToggle',
+        pressedClass: 'jxMenuItemPressed',
+        activeClass: 'jxMenuItemActive',
+        /* 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: new Hash({
+        domObj:'jxMenuItemContainer',
+        domA: 'jxMenuItem',
+        domImg: 'jxMenuItemIcon',
+        domLabel: 'jxMenuItemLabel'
+    }),
+    init: function() {
+      this.bound.mouseover = this.onMouseOver.bind(this);
+      this.parent();
+    },
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.Item
+     */
+    render: function() {
+        if (!this.options.image) {
+            this.options.image = Jx.aPixel.src;
+        }
+        this.parent();
+        if (this.options.image && this.options.image != Jx.aPixel.src) {
+            this.domObj.removeClass(this.options.toggleClass);
+        }
+        this.domObj.addEvent('mouseover', this.bound.mouseover);
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.domObj.removeEvent('mouseover', this.bound.mouseover);
+      this.bound.mouseover = null;
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: function() {this.blur.delay(1,this);},
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty,
+    /**
+     * Method: clicked
+     * Handle the user clicking on the menu item, overriding the <Jx.Button::clicked>
+     * method to facilitate menu tracking
+     *
+     * Parameters:
+     * obj - {Object} an object containing an event property that was the user
+     * event.
+     */
+    clicked: function(obj) {
+        if (this.options.enabled) {
+            if (this.options.toggle) {
+                this.setActive.delay(1,this,!this.options.active);
+            }
+            this.fireEvent.delay(1, this, ['click', {obj: this}]);
+            this.blur();
+            if (this.owner && this.owner.deactivate) {
+                this.owner.deactivate.delay(1, this.owner, obj.event);
+            }
+        }
+        return false;
+    },
+    /**
+     * Method: onmouseover
+     * handle the mouse moving over the menu item
+     */
+    onMouseOver: function(e) {
+        e.stop();
+        if (this.owner && this.owner.setVisibleItem) {
+            this.owner.setVisibleItem(this);
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the menu item on langChange Event for
+     * Internationalization
+     */
+    changeText: function(lang) {
+        this.parent();
+        if (this.owner && this.owner.deactivate) {
+            this.owner.deactivate();
+        }
+    }
+});
+
+// $Id: layout.js 718 2010-03-02 16:22:06Z pagameba $
+/**
+ * Class: Jx.Layout
+ *
+ * Extends: <Jx.Object>
+ *
+ * Jx.Layout is used to provide more flexible layout options for applications
+ *
+ * Jx.Layout wraps an existing DOM element (typically a div) and provides
+ * extra functionality for sizing that element within its parent and sizing
+ * elements contained within it that have a 'resize' function attached to them.
+ *
+ * To create a Jx.Layout, pass the element or id plus an options object to
+ * the constructor.
+ *
+ * Example:
+ * (code)
+ * var myContainer = new Jx.Layout('myDiv', options);
+ * (end)
+ *
+ * Events:
+ * sizeChange - fired when the size of the container changes
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Layout = new Class({
+    Family: 'Jx.Layout',
+    Extends: Jx.Object,
+
+    options: {
+        /* Option: resizeWithWindow
+         * boolean, automatically resize this layout when the window size
+         * changes, even if the element is not a direct descendant of the
+         * BODY.  False by default.
+         */
+        resizeWithWindow: false,
+        /* Option: propagate
+         * boolean, controls propogation of resize to child nodes.
+         * True by default. If set to false, changes in size will not be
+         * propogated to child nodes.
+         */
+        propagate: true,
+        /* Option: position
+         * how to position the element, either 'absolute' or 'relative'.
+         * The default (if not passed) is 'absolute'.  When using
+         * 'absolute' positioning, both the width and height are
+         * controlled by Jx.Layout.  If 'relative' positioning is used
+         * then only the width is controlled, allowing the height to
+         * be controlled by its content.
+         */
+        position: 'absolute',
+        /* Option: left
+         * the distance (in pixels) to maintain the left edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the left edge can be any distance from its parent
+         * based on other parameters.
+         */
+        left: 0,
+        /* Option: right
+         * the distance (in pixels) to maintain the right edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the right edge can be any distance from its parent
+         * based on other parameters.
+         */
+        right: 0,
+        /* Option: top
+         * the distance (in pixels) to maintain the top edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the top edge can be any distance from its parent
+         * based on other parameters.
+         */
+        top: 0,
+        /* Option: bottom
+         * the distance (in pixels) to maintain the bottom edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the bottom edge can be any distance from its parent
+         * based on other parameters.
+         */
+        bottom: 0,
+        /* Option: width
+         * the width (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the width can be any value based on
+         * other parameters.
+         */
+        width: null,
+        /* Option: height
+         * the height (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the height can be any value based on
+         * other parameters.
+         */
+        height: null,
+        /* Option: minWidth
+         * the minimum width that the element can be sized to.  The default
+         * value is 0.
+         */
+        minWidth: 0,
+        /* Option: minHeight
+         * the minimum height that the element can be sized to.  The
+         * default value is 0.
+         */
+        minHeight: 0,
+        /* Option: maxWidth
+         * the maximum width that the element can be sized to.  The default
+         * value is -1, which means no maximum.
+         */
+        maxWidth: -1,
+        /* Option: maxHeight
+         * the maximum height that the element can be sized to.  The
+         * default value is -1, which means no maximum.
+         */
+        maxHeight: -1
+    },
+
+    /**
+     * Parameters:
+     * domObj - {HTMLElement} element or id to apply the layout to
+     * options - <Jx.Layout.Options>
+     */
+    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);
+
+        if (this.options.resizeWithWindow || document.body == this.domObj.parentNode) {
+            window.addEvent('resize', this.windowResize.bindWithEvent(this));
+            window.addEvent('load', this.windowResize.bind(this));
+        }
+        //this.resize();
+    },
+
+    /**
+     * Method: windowResize
+     * when the window is resized, any Jx.Layout controlled elements that are
+     * direct children of the BODY element are resized
+     */
+     windowResize: function() {
+         this.resize();
+         if (this.resizeTimer) {
+             $clear(this.resizeTimer);
+             this.resizeTimer = null;
+         }
+         this.resizeTimer = this.resize.delay(50, this);
+    },
+
+    /**
+     * Method: resize
+     * resize the element controlled by this Jx.Layout object.
+     *
+     * Parameters:
+     * options - new options to apply, see <Jx.Layout.Options>
+     */
+    resize: function(options) {
+         /* this looks like a really big function but actually not
+          * much code gets executed in the two big if statements
+          */
+        this.resizeTimer = null;
+        var needsResize = false;
+        if (options) {
+            for (var i in options) {
+                //prevent forceResize: false from causing a resize
+                if (i == 'forceResize') {
+                    continue;
+                }
+                if (this.options[i] != options[i]) {
+                    needsResize = true;
+                    this.options[i] = options[i];
+                }
+            }
+            if (options.forceResize) {
+                needsResize = true;
+            }
+        }
+        if (!document.id(this.domObj.parentNode)) {
+            return;
+        }
+
+        var parentSize;
+        if (this.domObj.parentNode.tagName == 'BODY') {
+            parentSize = Jx.getPageDimensions();
+        } else {
+            parentSize = document.id(this.domObj.parentNode).getContentBoxSize();
+        }
+
+        if (this.lastParentSize && !needsResize) {
+            needsResize = (this.lastParentSize.width != parentSize.width ||
+                          this.lastParentSize.height != parentSize.height);
+        } else {
+            needsResize = true;
+        }
+        this.lastParentSize = parentSize;
+
+        if (!needsResize) {
+            return;
+        }
+
+        var l, t, w, h;
+
+        /* calculate left and width */
+        if (this.options.left != null) {
+            /* fixed left */
+            l = this.options.left;
+            if (this.options.right == null) {
+                /* variable right */
+                if (this.options.width == null) {
+                    /* variable right and width
+                     * set right to min, stretch width */
+                    w = parentSize.width - l;
+                    if (w < this.options.minWidth ) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* variable right, fixed width
+                     * use width
+                     */
+                    w = this.options.width;
+                }
+            } else {
+                /* fixed right */
+                if (this.options.width == null) {
+                    /* fixed right, variable width
+                     * stretch width
+                     */
+                    w = parentSize.width - l - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* fixed right, fixed width
+                     * respect left and width, allow right to stretch
+                     */
+                    w = this.options.width;
+                }
+            }
+
+        } else {
+            if (this.options.right == null) {
+                if (this.options.width == null) {
+                    /* variable left, width and right
+                     * set left, right to min, stretch width
+                     */
+                     l = 0;
+                     w = parentSize.width;
+                     if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                         l = l + parseInt(w - this.options.maxWidth,10)/2;
+                         w = this.options.maxWidth;
+                     }
+                } else {
+                    /* variable left, fixed width, variable right
+                     * distribute space between left and right
+                     */
+                    w = this.options.width;
+                    l = parseInt((parentSize.width - w)/2,10);
+                    if (l < 0) {
+                        l = 0;
+                    }
+                }
+            } else {
+                if (this.options.width != null) {
+                    /* variable left, fixed width, fixed right
+                     * left is calculated directly
+                     */
+                    w = this.options.width;
+                    l = parentSize.width - w - this.options.right;
+                    if (l < 0) {
+                        l = 0;
+                    }
+                } else {
+                    /* variable left and width, fixed right
+                     * set left to min value and stretch width
+                     */
+                    l = 0;
+                    w = parentSize.width - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        l = w - this.options.maxWidth - this.options.right;
+                        w = this.options.maxWidth;
+                    }
+                }
+            }
+        }
+
+        /* calculate the top and height */
+        if (this.options.top != null) {
+            /* fixed top */
+            t = this.options.top;
+            if (this.options.bottom == null) {
+                /* variable bottom */
+                if (this.options.height == null) {
+                    /* variable bottom and height
+                     * set bottom to min, stretch height */
+                    h = parentSize.height - t;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* variable bottom, fixed height
+                     * stretch height
+                     */
+                    h = this.options.height;
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = h - this.options.maxHeight;
+                        h = this.options.maxHeight;
+                    }
+                }
+            } else {
+                /* fixed bottom */
+                if (this.options.height == null) {
+                    /* fixed bottom, variable height
+                     * stretch height
+                     */
+                    h = parentSize.height - t - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* fixed bottom, fixed height
+                     * respect top and height, allow bottom to stretch
+                     */
+                    h = this.options.height;
+                }
+            }
+        } else {
+            if (this.options.bottom == null) {
+                if (this.options.height == null) {
+                    /* variable top, height and bottom
+                     * set top, bottom to min, stretch height
+                     */
+                     t = 0;
+                     h = parentSize.height;
+                     if (h < this.options.minHeight) {
+                         h = this.options.minHeight;
+                     }
+                     if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                         t = parseInt((parentSize.height - this.options.maxHeight)/2,10);
+                         h = this.options.maxHeight;
+                     }
+                } else {
+                    /* variable top, fixed height, variable bottom
+                     * distribute space between top and bottom
+                     */
+                    h = this.options.height;
+                    t = parseInt((parentSize.height - h)/2,10);
+                    if (t < 0) {
+                        t = 0;
+                    }
+                }
+            } else {
+                if (this.options.height != null) {
+                    /* variable top, fixed height, fixed bottom
+                     * top is calculated directly
+                     */
+                    h = this.options.height;
+                    t = parentSize.height - h - this.options.bottom;
+                    if (t < 0) {
+                        t = 0;
+                    }
+                } else {
+                    /* variable top and height, fixed bottom
+                     * set top to min value and stretch height
+                     */
+                    t = 0;
+                    h = parentSize.height - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = parentSize.height - this.options.maxHeight - this.options.bottom;
+                        h = this.options.maxHeight;
+                    }
+                }
+            }
+        }
+
+        //TODO: check left, top, width, height against current styles
+        // and only apply changes if they are not the same.
+
+        /* apply the new sizes */
+        var sizeOpts = {width: w};
+        if (this.options.position == 'absolute') {
+            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+m.left,
+                top: t+m.top
+            });
+            sizeOpts.height = h;
+        } else {
+            if (this.options.height) {
+                sizeOpts.height = this.options.height;
+            }
+        }
+        this.domObj.setBorderBoxSize(sizeOpts);
+
+        if (this.options.propagate) {
+            // propogate changes to children
+            var o = {forceResize: options ? options.forceResize : false};
+            $A(this.domObj.childNodes).each(function(child){
+                if (child.resize && child.getStyle('display') != 'none') {
+                    child.resize.delay(0,child,o);
+                }
+            });
+        }
+
+        this.fireEvent('sizeChange',this);
+    }
+});// $Id: toolbar.js 857 2010-04-20 12:12:11Z pagameba $
+/**
+ * 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: list
+     * {<Jx.List>} the list that holds the items in this toolbar
+     */
+    list : 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: {
+        /* 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: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. If scroll is set to true
+         * this option does nothing. Default: 'left', valid values: 'left',
+         * 'center', or 'right'
+         */
+        align: 'left',
+        /* Option: scroll
+         * if true, the toolbar may scroll if the contents are wider than
+         * the size of the toolbar
+         */
+        scroll: true,
+        template: '<ul class="jxToolbar"></ul>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolbar'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxToolbar', this);
+        if ($defined(this.options.id)) {
+            this.domObj.id = this.options.id;
+        }
+
+        this.list = new Jx.List(this.domObj, {
+            onAdd: function(item) {
+                this.fireEvent('add', this);
+            }.bind(this),
+            onRemove: function(item) {
+                this.fireEvent('remove', this);
+            }.bind(this)
+        });
+
+        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 = document.id(parent).retrieve('jxBarContainer');
+        if (!tbc) {
+            tbc = new Jx.Toolbar.Container({
+                parent: parent,
+                position: this.options.position,
+                autoSize: this.options.autoSize,
+                align: this.options.align,
+                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) {
+            var item = thing;
+            if (item.domObj) {
+                item = item.domObj;
+            }
+
+            if (item.tagName == 'LI') {
+                if (!item.hasClass('jxToolItem')) {
+                    item.addClass('jxToolItem');
+                }
+            } else {
+                item = new Jx.Toolbar.Item(thing);
+            }
+            this.list.add(item);
+        }, this);
+        
+        //Update the size of the toolbar container.
+        this.update();
+        
+        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');
+        this.list.remove(li);
+        this.update();
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the toolbar
+     */
+    empty: function() {
+      this.list.each(function(item){this.remove(item);},this);
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the Toolbar (when it is acting as a menu bar).
+     */
+    deactivate: function() {
+        this.list.each(function(item){
+            if (item.retrieve('jxMenu')) {
+                item.retrieve('jxMenu').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);
+    },
+    /**
+     * Method: update
+     * Updates the size of the UL so that the size is always consistently the 
+     * exact size of the size of the sum of the buttons. This will keep all of 
+     * the buttons on one line.
+     */
+    update: function () {
+        // if (['top','bottom'].contains(this.options.position)) {
+        //     (function(){
+        //         var s = 0;
+        //         var children = this.domObj.getChildren();
+        //         children.each(function(button){
+        //             var size = button.getMarginBoxSize();
+        //             s += size.width +0.5;
+        //         },this);
+        //         if (s !== 0) {
+        //             this.domObj.setStyle('width', Math.round(s));
+        //         } else {
+        //             this.domObj.setStyle('width','auto');
+        //         }
+        //     }).delay(1,this);
+        // }
+        this.fireEvent('update');
+    },
+    changeText : function(lang) {
+      this.update();
+    }
+});
+// $Id: container.js 926 2010-05-27 12:56:21Z pagameba $
+/**
+ * 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,
+    Binds: ['update'],
+    pluginNamespace: 'ToolbarContainer',
+    /**
+     * 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,
+        /**
+         * Option: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. This option overrides
+         * scroll if set to something other than the default. Default: 'left',
+         * valid values are 'left','center', or 'right'
+         */
+        align: 'left',
+        template: "<div class='jxBarContainer'><div class='jxBarControls'></div></div>",
+        scrollerTemplate: "<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"
+    },
+    classes: new Hash({
+        domObj: 'jxBarContainer',
+        scroller: 'jxBarScroller',
+        //used to hide the overflow of the wrapper
+        wrapper: 'jxBarWrapper',
+        controls: 'jxBarControls'
+        //used to allow multiple toolbars to float next to each other
+    }),
+
+    updating: false,
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Container
+     */
+    render: function() {
+        this.parent();
+        /* if a container was passed in, use it instead of the one from the
+         * template
+         */
+        if (document.id(this.options.parent)) {
+            this.domObj = document.id(this.options.parent);
+            this.elements = new Hash({
+                'jxBarContainer': this.domObj
+            });
+            this.domObj.addClass('jxBarContainer');
+            this.domObj.grab(this.controls);
+            this.domObj.addEvent('sizeChange', this.update);
+        }
+        // make sure that align is valid
+        if (!["left","center","right"].contains(this.options.align)) {
+          this.options.align = 'left';
+        }
+        // align the toolbar appropriately
+        this.domObj.addClass('jxToolbarAlign' + this.options.align.capitalize());
+        
+        // scrolling is only available if it is left aligned.
+        this.options.scroll = this.options.align == 'left' &&
+                              this.options.scroll;
+        if (!['center', 'right'].contains(this.options.align) && this.options.scroll) {
+            this.processElements(this.options.scrollerTemplate, this.classes);
+            this.domObj.grab(this.scroller, 'top');
+        }
+
+        /* 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', function(){
+              this.domObj.getParent().addEvent('sizeChange', this.update);
+              this.update();
+            });
+
+            this.scrollLeft = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollLeft).addClass('jxBarScrollLeft');
+            this.scrollLeft.addEvents({
+                click: this.scroll.bind(this, 'left')
+            });
+
+            this.scrollRight = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollRight).addClass('jxBarScrollRight');
+            this.scrollRight.addEvents({
+                click: this.scroll.bind(this, 'right')
+            });
+
+        } else if (this.options.scroll && ['left', 'right'].contains(this.options.position)) {
+            //do we do scrolling up and down?
+            //for now disable scroll in this case
+            this.options.scroll = false;
+        } else {
+            this.options.scroll = false;
+        }
+
+        this.addEvent('add', this.update);
+        if (this.options.toolbars) {
+            this.add(this.options.toolbars);
+        }
+    },
+
+    /**
+     * APIMethod: update
+     * Updates the scroller enablement dependent on the total size of the
+     * toolbar(s).
+     */
+    update: function() {
+        if (this.options.scroll) {
+            if (['top', 'bottom'].contains(this.options.position)) {
+                var tbcSize = this.domObj.getContentBoxSize().width;
+
+                var s = 0;
+                //next check to see if we need the scrollers or not.
+                var children = this.wrapper.getChildren();
+                if (children.length > 0) {
+                    children.each(function(tb) {
+                        s += tb.getMarginBoxSize().width;
+                    },
+                    this);
+
+                    var scrollerSize = tbcSize;
+
+                    if (s === 0) {
+                        this.scrollLeft.setEnabled(false);
+                        this.scrollRight.setEnabled(false);
+                    } else {
+
+
+                        var leftMargin = this.wrapper.getStyle('margin-left').toInt();
+                        scrollerSize -= this.controls.getMarginBoxSize().width;
+
+
+                        if (leftMargin < 0) {
+                            //has been scrolled left so activate the right scroller
+                            this.scrollLeft.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollLeft.setEnabled(false);
+                        }
+
+                        if (s + leftMargin > scrollerSize) {
+                            //we need the right one
+                            this.scrollRight.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollRight.setEnabled(false);
+                        }
+                    }
+
+                } else {
+                    this.scrollRight.setEnabled(false);
+                    this.scrollLeft.setEnabled(false);
+                }
+                this.scroller.setStyle('width', scrollerSize);
+
+                this.findFirstVisible();
+                this.updating = false;
+            }
+        }
+    },
+    /**
+     * Method: findFirstVisible
+     * Finds the first visible button on the toolbar and saves a reference in 
+     * the scroller object
+     */
+    findFirstVisible: function() {
+        if ($defined(this.scroller.retrieve('buttonPointer'))) {
+            return;
+        };
+
+        var children = this.wrapper.getChildren();
+
+        if (children.length > 0) {
+            children.each(function(toolbar) {
+                var buttons = toolbar.getChildren();
+                if (buttons.length > 1) {
+                    buttons.each(function(button) {
+                        var pos = button.getCoordinates(this.scroller);
+                        if (pos.left >= 0 && !$defined(this.scroller.retrieve('buttonPointer'))) {
+                            //this is the first visible button
+                            this.scroller.store('buttonPointer', button);
+                        }
+                    },
+                    this);
+                }
+            },
+            this);
+        }
+    },
+
+    /**
+     * APIMethod: 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('update', this.update.bind(this));
+                thing.addEvent('show', this.scrollIntoView.bind(this));
+            }
+            if (this.wrapper) {
+                this.wrapper.adopt(thing.domObj);
+            } else {
+                this.domObj.adopt(thing.domObj);
+            }
+            this.domObj.addClass('jxBar' + this.options.position.capitalize());
+        },
+        this);
+        if (arguments.length > 0) {
+            this.fireEvent('add', this);
+        }
+        return this;
+    },
+
+    /**
+     * Method: scroll
+     * Does the work of scrolling the toolbar to a specific position.
+     *
+     * Parameters:
+     * direction - whether to scroll left or right
+     */
+    scroll: function(direction) {
+        if (this.updating) {
+            return
+        };
+        this.updating = true;
+
+        var currentButton = this.scroller.retrieve('buttonPointer');
+        if (direction === 'left') {
+            //need to tween the amount of the previous button
+            var previousButton = this.scroller.retrieve('previousPointer');
+            if (!previousButton) {
+                previousButton = this.getPreviousButton(currentButton);
+            }
+            if (previousButton) {
+                var w = previousButton.getMarginBoxSize().width;
+                var ml = this.wrapper.getStyle('margin-left').toInt();
+                ml += w;
+                if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                    //scroll it
+                    this.wrapper.get('tween', {
+                        property: 'margin-left',
+                        onComplete: this.afterTweenLeft.bind(this, previousButton)
+                    }).start(ml);
+                } else {
+                    //set it
+                    this.wrapper.setStyle('margin-left', ml);
+                    this.afterTweenLeft(previousButton);
+                }
+            } else {
+                this.update();
+            }
+        } else {
+            //must be right
+            var w = currentButton.getMarginBoxSize().width;
+
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= w;
+
+            //now, if Fx is defined tween the margin to the left to
+            //hide the current button
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+
+        }
+    },
+
+    /**
+     * Method: afterTweenRight
+     * Updates pointers to buttons after the toolbar scrolls right
+     *
+     * Parameters:
+     * currentButton - the button that was currently first before the scroll
+     */
+    afterTweenRight: function(currentButton) {
+        var np = this.getNextButton(currentButton);
+        if (!np) {
+            np = currentButton;
+        }
+        this.scroller.store('buttonPointer', np);
+        if (np !== currentButton) {
+            this.scroller.store('previousPointer', currentButton);
+        }
+        this.update();
+    },
+    /**
+     * Method: afterTweenLeft
+     * Updates pointers to buttons after the toolbar scrolls left
+     *
+     * Parameters:
+     * previousButton - the button that was to the left of the first visible
+     *      button.
+     */
+    afterTweenLeft: function(previousButton) {
+        this.scroller.store('buttonPointer', previousButton);
+        var pp = this.getPreviousButton(previousButton);
+        if ($defined(pp)) {
+            this.scroller.store('previousPointer', pp);
+        } else {
+            this.scroller.eliminate('previousPointer');
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 instanceof Jx.Widget) {
+            item.dispose();
+        } else {
+            document.id(item).dispose();
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 currentButton = this.scroller.retrieve('buttonPointer');
+        // if (!$defined(currentButton)) return;
+        if (item instanceof Jx.Widget) {
+            item = item.domObj;
+            while (!item.hasClass('jxToolItem')) {
+                item = item.getParent();
+            }
+        }
+        var pos = item.getCoordinates(this.scroller);
+        var scrollerSize = this.scroller.getStyle('width').toInt();
+
+        if (pos.right > 0 && pos.right <= scrollerSize && pos.left > 0 && pos.left <= scrollerSize) {
+           //we are completely on screen 
+            return;
+        };
+
+        if (pos.right > scrollerSize) {
+            //it's right of the scroller
+            var diff = pos.right - scrollerSize;
+
+            //loop through toolbar items until we have enough width to
+            //make the item visible
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            var w = currentButton.getMarginBoxSize().width;
+            var np;
+            while (w < diff && $defined(currentButton)) {
+                np = this.getNextButton(currentButton);
+                if (np) {
+                    w += np.getMarginBoxSize().width;
+                } else {
+                    break;
+                }
+                currentButton = np;
+            }
+
+            ml -= w;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+        } else {
+            //it's left of the scroller
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= pos.left;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenLeft.bind(this, item)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenLeft(item);
+            }
+        }
+
+    },
+    /**
+     * Method: getPreviousButton
+     * Finds the button to the left of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getPreviousButton: function(currentButton) {
+        pp = currentButton.getPrevious();
+        if (!$defined(pp)) {
+            //check for a new toolbar
+            pp = currentButton.getParent().getPrevious();
+            if (pp) {
+                pp = pp.getLast();
+            }
+        }
+        return pp;
+    },
+    /**
+     * Method: getNextButton
+     * Finds the button to the right of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getNextButton: function(currentButton) {
+        np = currentButton.getNext();
+        if (!np) {
+            np = currentButton.getParent().getNext();
+            if (np) {
+                np = np.getFirst();
+            }
+        }
+        return np;
+    }
+
+});// $Id: toolbar.item.js 626 2009-11-20 13:22:22Z pagameba $
+/**
+ * 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.Widget,
+    options: {
+        /* Option: active
+         * is this item active or not?  Default is true.
+         */
+        active: true,
+        template: '<li class="jxToolItem"></li>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolItem'
+    }),
+
+    parameters: ['jxThing'],
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Item.
+     */
+    render: function() {
+        this.parent();
+        var el = document.id(this.options.jxThing);
+        if (el) {
+            this.domObj.adopt(el);
+        }
+    }
+});// $Id: panel.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Panel
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * some user controls as determined by the options passed to the
+ * constructor.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * close - fired when the panel is closed
+ * collapse - fired when the panel is collapsed
+ * expand - fired when the panel is opened
+ * 
+ * MooTools.lang Keys:
+ * - panel.collapseTooltip
+ * - panel.collapseLabel
+ * - panel.expandlabel
+ * - panel.maximizeTooltip
+ * - panel.maximizeLabel
+ * - panel.restoreTooltip
+ * - panel.restoreLabel
+ * - panel.closeTooltip
+ * - panel.closeLabel
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel = new Class({
+    Family: 'Jx.Panel',
+    Extends: Jx.Widget,
+
+    toolbarContainers: {
+        top: null,
+        right: null,
+        bottom: null,
+        left: null
+    },
+
+     options: {
+        position: null,
+        collapsedClass: 'jxPanelMin',
+        collapseClass: 'jxPanelCollapse',
+        menuClass: 'jxPanelMenu',
+        maximizeClass: 'jxPanelMaximize',
+        closeClass: 'jxPanelClose',
+
+        /* Option: label
+         * String, the title of the Jx Panel
+         */
+        label: '&nbsp;',
+        /* Option: height
+         * integer, fixed height to give the panel - no fixed height by
+         * default.
+         */
+        height: null,
+        /* Option: collapse
+         * boolean, determine if the panel can be collapsed and expanded
+         * by the user.  This puts a control into the title bar for the user
+         * to control the state of the panel.
+         */
+        collapse: true,
+        /* Option: close
+         * boolean, determine if the panel can be closed (hidden) by the user.
+         * The application needs to provide a way to re-open the panel after
+         * it is closed.  The closeable property extends to dialogs created by
+         * floating panels.  This option puts a control in the title bar of
+         * the panel.
+         */
+        close: false,
+        /* Option: closed
+         * boolean, initial state of the panel (true to start the panel
+         *  closed), default is false
+         */
+        closed: false,
+        /* Option: hideTitle
+         * Boolean, hide the title bar if true.  False by default.
+         */
+        hideTitle: false,
+        /* Option: toolbars
+         * array of Jx.Toolbar objects to put in the panel.  The position
+         * of each toolbar is used to position the toolbar within the panel.
+         */
+        toolbars: [],
+        type: 'panel',
+        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',
+        controlButtonTemplate: '<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'
+    },
+    classes: new Hash({
+        domObj: 'jxPanel',
+        title: 'jxPanelTitle',
+        domImg: 'jxPanelIcon',
+        domLabel: 'jxPanelLabel',
+        domControls: 'jxPanelControls',
+        contentContainer: 'jxPanelContentContainer',
+        content: 'jxPanelContent'
+    }),
+
+    /**
+     * APIMethod: render
+     * Initialize a new Jx.Panel instance
+     */
+    render : function(){
+        this.parent();
+
+        this.toolbars = this.options ? this.options.toolbars || [] : [];
+
+        this.options.position = ($defined(this.options.height) && !$defined(this.options.position)) ? 'relative' : 'absolute';
+
+        if (this.options.image && this.domImg) {
+            this.domImg.setStyle('backgroundImage', 'url('+this.options.image+')');
+        }
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        var tbDiv = new Element('div');
+        this.domControls.adopt(tbDiv);
+        this.toolbar = new Jx.Toolbar({parent:tbDiv, scroll: false});
+
+        var that = this;
+        if (this.options.menu) {
+            this.menu = new Jx.Menu({
+                image: Jx.aPixel.src
+            }, {
+              buttonTemplate: this.options.controlButtonTemplate
+            });
+            this.menu.domObj.addClass(this.options.menuClass);
+            this.menu.domObj.addClass('jxButtonContentLeft');
+            this.toolbar.add(this.menu);
+        }
+
+        //var b, item;
+        if (this.options.collapse) {
+            this.colB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'collapseTooltip'},
+                onClick: function() {
+                    that.toggleCollapse();
+                }
+            });
+            this.colB.domObj.addClass(this.options.collapseClass);
+            this.addEvents({
+                collapse: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'expandTooltip'});
+                }.bind(this),
+                expand: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.colB);
+            if (this.menu) {
+                this.colM = new Jx.Menu.Item({
+                    label: this.options.collapseLabel,
+                    onClick: function() { that.toggleCollapse(); }
+                });
+                var item = this.colM
+                this.addEvents({
+                    collapse: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+                    }.bind(this),
+                    expand: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(item);
+            }
+        }
+
+        if (this.options.maximize) {
+            this.maxB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'maximizeTooltip'},
+                onClick: function() {
+                    that.maximize();
+                }
+            });
+            this.maxB.domObj.addClass(this.options.maximizeClass);
+            this.addEvents({
+                maximize: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'restoreTooltip'});
+                }.bind(this),
+                restore: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.maxB);
+            if (this.menu) {
+                this.maxM = new Jx.Menu.Item({
+                    label: this.options.maximizeLabel,
+                    onClick: function() { that.maximize(); }
+                });
+                
+                this.addEvents({
+                    maximize: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'maximizeLabel'});
+                    }.bind(this),
+                    restore: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'restoreLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(this.maxM);
+            }
+        }
+
+        if (this.options.close) {
+            this.closeB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'closeTooltip'},
+                onClick: function() {
+                    that.close();
+                }
+            });
+            this.closeB.domObj.addClass(this.options.closeClass);
+            this.toolbar.add(this.closeB);
+            if (this.menu) {
+                this.closeM = new Jx.Menu.Item({
+                    label: {set:'Jx',key:'panel',value:'closeLabel'},
+                    onClick: function() {
+                        that.close();
+                    }
+                });
+                this.menu.add(item);
+            }
+
+        }
+
+        this.title.addEvent('dblclick', function() {
+            that.toggleCollapse();
+        });
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        var jxl = new Jx.Layout(this.domObj, $merge(this.options, {propagate:false}));
+        var layoutHandler = this.layoutContent.bind(this);
+        jxl.addEvent('sizeChange', layoutHandler);
+
+        if (this.options.hideTitle) {
+            this.title.dispose();
+        }
+
+        if (Jx.type(this.options.toolbars) == 'array') {
+            this.options.toolbars.each(function(tb){
+                var position = tb.options.position;
+                var tbc = this.toolbarContainers[position];
+                if (!tbc) {
+                    tbc = new Element('div');
+                    new Jx.Layout(tbc);
+                    this.contentContainer.adopt(tbc);
+                    this.toolbarContainers[position] = tbc;
+                }
+                tb.addTo(tbc);
+            }, this);
+        }
+
+        new Jx.Layout(this.contentContainer);
+        new Jx.Layout(this.content);
+
+        this.loadContent(this.content);
+
+        this.toggleCollapse(this.options.closed);
+
+        this.addEvent('addTo', function() {
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: layoutContent
+     * the sizeChange event of the <Jx.Layout> that manages the outer container
+     * is intercepted and passed through this method to handle resizing of the
+     * panel contents because we need to do some calculations if the panel
+     * is collapsed and if there are toolbars to put around the content area.
+     */
+    layoutContent: function() {
+        var titleHeight = 0;
+        var top = 0;
+        var bottom = 0;
+        var left = 0;
+        var right = 0;
+        var tbc;
+        var tb;
+        var position;
+        if (!this.options.hideTitle && this.title.parentNode == this.domObj) {
+            titleHeight = this.title.getMarginBoxSize().height;
+        }
+        var domSize = this.domObj.getContentBoxSize();
+        if (domSize.height > titleHeight) {
+            this.contentContainer.setStyle('display','block');
+            this.options.closed = false;
+            this.contentContainer.resize({
+                top: titleHeight,
+                height: null,
+                bottom: 0
+            });
+            ['left','right'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.width = 'auto';
+                }
+            }, this);
+            ['top','bottom'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.height = '';
+                }
+            }, this);
+            if (Jx.type(this.options.toolbars) == 'array') {
+                this.options.toolbars.each(function(tb){
+                    tb.update();
+                    position = tb.options.position;
+                    tbc = this.toolbarContainers[position];
+                    // IE 6 doesn't seem to want to measure the width of
+                    // things correctly
+                    if (Browser.Engine.trident4) {
+                        var oldParent = document.id(tbc.parentNode);
+                        tbc.style.visibility = 'hidden';
+                        document.id(document.body).adopt(tbc);
+                    }
+                    var size = tbc.getBorderBoxSize();
+                    // put it back into its real parent now we are done
+                    // measuring
+                    if (Browser.Engine.trident4) {
+                        oldParent.adopt(tbc);
+                        tbc.style.visibility = '';
+                    }
+                    switch(position) {
+                        case 'bottom':
+                            bottom = size.height;
+                            break;
+                        case 'left':
+                            left = size.width;
+                            break;
+                        case 'right':
+                            right = size.width;
+                            break;
+                        case 'top':
+                        default:
+                            top = size.height;
+                            break;
+                    }
+                },this);
+            }
+            tbc = this.toolbarContainers['top'];
+            if (tbc) {
+                tbc.resize({top: 0, left: left, right: right, bottom: null, height: top, width: null});
+            }
+            tbc = this.toolbarContainers['bottom'];
+            if (tbc) {
+                tbc.resize({top: null, left: left, right: right, bottom: 0, height: bottom, width: null});
+            }
+            tbc = this.toolbarContainers['left'];
+            if (tbc) {
+                tbc.resize({top: top, left: 0, right: null, bottom: bottom, height: null, width: left});
+            }
+            tbc = this.toolbarContainers['right'];
+            if (tbc) {
+                tbc.resize({top: top, left: null, right: 0, bottom: bottom, height: null, width: right});
+            }
+            this.content.resize({top: top, bottom: bottom, left: left, right: right});
+        } else {
+            this.contentContainer.setStyle('display','none');
+            this.options.closed = true;
+        }
+        this.fireEvent('sizeChange', this);
+    },
+
+    /**
+     * Method: setLabel
+     * Set the label in the title bar of this panel
+     *
+     * Parameters:
+     * s - {String} the new label
+     */
+    setLabel: function(s) {
+        this.domLabel.set('html',this.getText(s));
+    },
+    /**
+     * Method: getLabel
+     * Get the label of the title bar of this panel
+     *
+     * Returns:
+     * {String} the label
+     */
+    getLabel: function() {
+        return this.domLabel.get('html');
+    },
+    /**
+     * Method: finalize
+     * Clean up the panel
+     */
+    finalize: function() {
+        this.domObj = null;
+        this.deregisterIds();
+    },
+    /**
+     * Method: maximize
+     * Maximize this panel
+     */
+    maximize: function() {
+        if (this.manager) {
+            this.manager.maximizePanel(this);
+        }
+    },
+    /**
+     * Method: setContent
+     * set the content of this panel to some HTML
+     *
+     * Parameters:
+     * html - {String} the new HTML to go in the panel
+     */
+    setContent : function (html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+    },
+    /**
+     * Method: setContentURL
+     * Set the content of this panel to come from some URL.
+     *
+     * Parameters:
+     * url - {String} URL to some HTML content for this panel
+     */
+    setContentURL : function (url) {
+        this.bContentReady = false;
+        this.setBusy(true);
+        if (arguments[1]) {
+            this.onContentReady = arguments[1];
+        }
+        if (url.indexOf('?') == -1) {
+            url = url + '?';
+        }
+        var a = new Request({
+            url: url,
+            method: 'get',
+            evalScripts:true,
+            onSuccess:this.panelContentLoaded.bind(this),
+            requestHeaders: ['If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT']
+        }).send();
+    },
+    /**
+     * Method: panelContentLoaded
+     * When the content of the panel is loaded from a remote URL, this
+     * method is called when the ajax request returns.
+     *
+     * Parameters:
+     * html - {String} the html return from xhr.onSuccess
+     */
+    panelContentLoaded: function(html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+        this.setBusy(false);
+        if (this.onContentReady) {
+            window.setTimeout(this.onContentReady.bind(this),1);
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','none');
+                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;
+                }
+                this.domObj.resize({height: height});
+                this.fireEvent('collapse', this);
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','block');
+                this.domObj.resize({height: this.options.height});
+                this.fireEvent('expand', this);
+            }
+        }
+    },
+
+    /**
+     * Method: close
+     * Closes the panel (completely hiding it).
+     */
+    close: function() {
+        this.domObj.dispose();
+        this.fireEvent('close', this);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();	//TODO: change this class so that we can access these properties without too much voodoo...
+    	if($defined(this.closeB)) {
+    		this.closeB.setTooltip({set:'Jx',key:'panel',value:'closeTooltip'});
+    	}
+    	if ($defined(this.closeM)) {
+    		this.closeM.setLabel({set:'Jx',key:'panel',value:'closeLabel'});
+    	}
+    	if ($defined(this.maxB)) {
+    		this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+    	}
+    	if ($defined(this.colB)) {
+    		this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+    	}
+    	if ($defined(this.colM)) {
+	    	if (this.options.closed == true) {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+	    	} else {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+	    	}
+    	}
+      if (this.options.label && this.domLabel) {
+          this.setLabel(this.options.label);
+      }
+      // TODO: is this the right method to call?
+      // if toolbars left/right are used and localized, they may change their size..
+      this.layoutContent();
+    }
+});// $Id: dialog.js 894 2010-05-07 09:10:29Z conrad.barthelmes $
+/**
+ * Class: Jx.Dialog
+ *
+ * Extends: <Jx.Panel>
+ *
+ * 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
+ * operating system dialog boxes, including:
+ *
+ * - dialogs may be modal (user must dismiss the dialog to continue) or
+ * non-modal
+ *
+ * - dialogs are movable (user can drag the title bar to move the dialog
+ * around)
+ *
+ * - dialogs may be a fixed size or allow user resizing.
+ *
+ * Jx.Dialog uses <Jx.ContentLoader> to load content into the content area
+ * of the dialog.  Refer to the <Jx.ContentLoader> documentation for details
+ * on content options.
+ *
+ * Example:
+ * (code)
+ * var dialog = new Jx.Dialog();
+ * (end)
+ *
+ * Events:
+ * open - triggered when the dialog is opened
+ * close - triggered when the dialog is closed
+ * change - triggered when the value of an input in the dialog is changed
+ * resize - triggered when the dialog is resized
+ *
+ * Extends:
+ * Jx.Dialog extends <Jx.Panel>, please go there for more details.
+ * 
+ * MooTools.lang Keys:
+ * - dialog.resizeToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog = new Class({
+    Family: 'Jx.Dialog',
+    Extends: Jx.Panel,
+
+    options: {
+        /* Option: modal
+         * (optional) {Boolean} controls whether the dialog will be modal
+         * or not.  The default is to create modal dialogs.
+         */
+        modal: true,
+        /** 
+         * Option: maskOptions
+         */
+        maskOptions: {
+          'class':'jxModalMask',
+          maskMargins: true,
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          }
+        },
+        eventMaskOptions: {
+          'class':'jxEventMask',
+          maskMargins: false,
+          useIframeShim: false,
+          destroyOnHide: true
+        },
+        /* just overrides default position of panel, don't document this */
+        position: 'absolute',
+        /* Option: width
+         * (optional) {Integer} the initial width in pixels of the dialog.
+         * The default value is 250 if not specified.
+         */
+        width: 250,
+        /* Option: height
+         * (optional) {Integer} the initial height in pixels of the
+         * dialog. The default value is 250 if not specified.
+         */
+        height: 250,
+        /* Option: horizontal
+         * (optional) {String} the horizontal rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        horizontal: 'center center',
+        /* Option: vertical
+         * (optional) {String} the vertical rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        vertical: 'center center',
+        /* Option: label
+         * (optional) {String} the title of the dialog box.
+         */
+        label: '',
+        /* Option: parent
+         * (optional) {HTMLElement} a reference to an HTML element that
+         * the dialog is to be contained by.  The default value is for the dialog
+         * to be contained by the body element.
+         */
+        //parent: null,
+        /* Option: resize
+         * (optional) {Boolean} determines whether the dialog is
+         * resizeable by the user or not.  Default is false.
+         */
+        resize: false,
+
+        /* Option: move
+         * (optional) {Boolean} determines whether the dialog is
+         * moveable by the user or not.  Default is true.
+         */
+        move: true,
+        /* Option: close
+         * (optional) {Boolean} determines whether the dialog is
+         * closeable by the user or not.  Default is true.
+         */
+        close: true,
+        /**
+         * Option: useKeyboard
+         * (optional) {Boolean} determines whether the Dialog listens to keyboard events globally
+         * Default is false
+         */
+        useKeyboard : false,
+        /**
+         * Option: keys
+         * (optional) {Object} refers with the syntax for MooTools Keyboard Class
+         * to functions. Set key to false to disable it manually 
+         */
+        keys: {
+          'esc' : 'close'
+        },
+        /**
+         * Option: keyboardMethods
+         *
+         * can be used to overwrite existing keyboard methods that are used inside
+         * this.options.keys - also possible to add new ones.
+         * Functions are bound to the dialog when using 'this'
+         *
+         * example:
+         *  keys : {
+         *    'alt+enter' : 'maximizeDialog'
+         *  },
+         *  keyboardMethods: {
+         *    'maximizeDialog' : function(ev){
+         *      ev.preventDefault();
+         *      this.maximize();
+         *    }
+         *  }
+         */
+        keyboardMethods : {},
+        collapsedClass: 'jxDialogMin',
+        collapseClass: 'jxDialogCollapse',
+        menuClass: 'jxDialogMenu',
+        maximizeClass: 'jxDialogMaximize',
+        closeClass: 'jxDialogClose',
+        type: 'dialog',
+        template: '<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'
+    },
+    classes: new Hash({
+        domObj: 'jxDialog',
+        title: 'jxDialogTitle',
+        domImg: 'jxDialogIcon',
+        domLabel: 'jxDialogLabel',
+        domControls: 'jxDialogControls',
+        contentContainer: 'jxDialogContentContainer',
+        content: 'jxDialogContent'
+    }),
+    /**
+     * MooTools Keyboard class for Events (mostly used in Dialog.Confirm, Prompt or Message)
+     * But also optional here with esc to close
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * renders Jx.Dialog
+     */
+    render: function() {
+        this.isOpening = false;
+        this.firstShow = true;
+
+        this.options = $merge(
+            {parent:document.body}, // these are defaults that can be overridden
+            this.options,
+            {position: 'absolute'} // these override anything passed to the options
+        );
+
+        /* initialize the panel overriding the type and position */
+        this.parent();
+        this.openOnLoaded = this.open.bind(this);
+        this.options.parent = document.id(this.options.parent);
+
+        this.domObj.setStyle('display','none');
+        this.options.parent.adopt(this.domObj);
+
+        /* the dialog is moveable by its title bar */
+        if (this.options.move && typeof Drag != 'undefined') {
+            this.title.addClass('jxDialogMoveable');
+            new Drag(this.domObj, {
+                handle: this.title,
+                onBeforeStart: (function(){
+                    this.stack();
+                }).bind(this),
+                onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
+                    this.contentContainer.setStyle('visibility','hidden');
+                    this.chrome.addClass('jxChromeDrag');
+                }).bind(this),
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    this.contentContainer.setStyle('visibility','');
+                    var left = Math.max(this.chromeOffsets.left, parseInt(this.domObj.style.left,10));
+                    var top = Math.max(this.chromeOffsets.top, parseInt(this.domObj.style.top,10));
+                    this.options.horizontal = left + ' left';
+                    this.options.vertical = top + ' top';
+                    this.position(this.domObj, this.options.parent, this.options);
+                    this.options.left = parseInt(this.domObj.style.left,10);
+                    this.options.top = parseInt(this.domObj.style.top,10);
+                    if (!this.options.closed) {
+                        this.domObj.resize(this.options);
+                    }
+                }).bind(this)
+            });
+        }
+
+        /* the dialog is resizeable */
+        if (this.options.resize && typeof Drag != 'undefined') {
+            this.resizeHandle = new Element('div', {
+                'class':'jxDialogResize',
+                title: this.getText({set:'Jx',key:'panel',value:'resizeTooltip'}),
+                styles: {
+                    'display':this.options.closed?'none':'block'
+                }
+            });
+            this.domObj.appendChild(this.resizeHandle);
+
+            this.resizeHandleSize = this.resizeHandle.getSize();
+            this.resizeHandle.setStyles({
+                bottom: this.resizeHandleSize.height,
+                right: this.resizeHandleSize.width
+            });
+            this.domObj.makeResizable({
+                handle:this.resizeHandle,
+                onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
+                    this.contentContainer.setStyle('visibility','hidden');
+                    this.chrome.addClass('jxChromeDrag');
+                }).bind(this),
+                onDrag: (function() {
+                    this.resizeChrome(this.domObj);
+                }).bind(this),
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    var size = this.domObj.getMarginBoxSize();
+                    this.options.width = size.width;
+                    this.options.height = size.height;
+                    this.layoutContent();
+                    this.domObj.resize(this.options);
+                    this.contentContainer.setStyle('visibility','');
+                    this.fireEvent('resize');
+                    this.resizeChrome(this.domObj);
+
+                }).bind(this)
+            });
+        }
+        /* this adjusts the zIndex of the dialogs when activated */
+        this.domObj.addEvent('mousedown', (function(){
+            this.stack();
+        }).bind(this));
+
+        // initialize keyboard class
+        this.initializeKeyboard();
+    },
+
+    /**
+     * Method: resize
+     * resize the dialog.  This can be called when the dialog is closed
+     * or open.
+     *
+     * Parameters:
+     * width - the new width
+     * height - the new height
+     * autoPosition - boolean, false by default, if resizing an open dialog
+     * setting this to true will reposition it according to its position
+     * rules.
+     */
+    resize: function(width, height, autoPosition) {
+        this.options.width = width;
+        this.options.height = height;
+        if (this.domObj.getStyle('display') != 'none') {
+            this.layoutContent();
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            if (autoPosition) {
+                this.position(this.domObj, this.options.parent, this.options);
+            }
+        } else {
+            this.firstShow = false;
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * overload panel's sizeChanged method
+     */
+    sizeChanged: function() {
+        if (!this.options.closed) {
+            this.layoutContent();
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','none');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','none');
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','block');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','block');
+            }
+        }
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+            this.fireEvent('collapse');
+        } else {
+            this.domObj.resize(this.options);
+            this.fireEvent('expand');
+        }
+        this.showChrome(this.domObj);
+    },
+    
+    /**
+     * Method: maximize
+     * Called when the maximize button of a dialog is clicked. It will maximize
+     * the dialog to match the size of its parent.
+     */
+    maximize: function () {
+        
+        if (!this.maximized) {
+            //get size of parent
+            var p = this.options.parent;
+            var size;
+            
+            if (p === document.body) {
+                size = Jx.getPageDimensions();
+            } else {
+                size = p.getBorderBoxSize();
+            }
+            this.previousSettings = {
+                width: this.options.width,
+                height: this.options.height,
+                horizontal: this.options.horizontal,
+                vertical: this.options.vertical,
+                left: this.options.left,
+                right: this.options.right,
+                top: this.options.top,
+                bottom: this.options.bottom
+            };
+            this.options.width = size.width;
+            this.options.height = size.height;
+            this.options.vertical = '0 top';
+            this.options.horizontal = '0 left';
+            this.options.right = 0;
+            this.options.left = 0;
+            this.options.top = 0;
+            this.options.bottom = 0;
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = true;
+            this.domObj.addClass('jxDialogMaximized');
+            this.fireEvent('maximize');
+        } else {
+            this.options = $merge(this.options, this.previousSettings);
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = false;
+            if (this.domObj.hasClass('jxDialogMaximized')) {
+                this.domObj.removeClass('jxDialogMaximized');
+            }
+            this.fireEvent('restore');
+        }
+    },
+
+    /**
+     * Method: show
+     * show the dialog, external code should use the <Jx.Dialog::open> method
+     * to make the dialog visible.
+     */
+    show : function( ) {
+        /* prepare the dialog for display */
+        this.domObj.setStyles({
+            'display': 'block',
+            'visibility': 'hidden'
+        });
+        this.toolbar.update();
+        
+        /* do the modal thing */
+        if (this.options.modal && this.options.parent.mask) {
+          var opts = $merge(this.options.maskOptions || {}, {
+            style: {
+              'z-index': Jx.getNumber(this.domObj.getStyle('z-index')) - 1
+            }
+          });
+          this.options.parent.mask(opts);
+          Jx.Stack.stack(this.options.parent.get('mask').element);
+        }
+        /* stack the dialog */
+        this.stack();
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+        } else {
+            this.domObj.resize(this.options);
+        }
+        
+        if (this.firstShow) {
+            this.contentContainer.resize({forceResize: true});
+            this.layoutContent();
+            this.firstShow = false;
+            /* if the chrome got built before the first dialog show, it might
+             * not have been properly created and we should clear it so it
+             * does get built properly
+             */
+            if (this.chrome) {
+                this.chrome.dispose();
+                this.chrome = null;
+            }
+        }
+        /* update or create the chrome */
+        this.showChrome(this.domObj);
+        /* put it in the right place using auto-positioning */
+        this.position(this.domObj, this.options.parent, this.options);
+        this.domObj.setStyle('visibility', 'visible');
+    },
+    /**
+     * Method: hide
+     * hide the dialog, external code should use the <Jx.Dialog::close>
+     * method to hide the dialog.
+     */
+    hide : function() {
+        this.domObj.setStyle('display','none');
+        this.unstack();
+        if (this.options.modal && this.options.parent.unmask) {
+          Jx.Stack.unstack(this.options.parent.get('mask').element);
+          this.options.parent.unmask();
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.deactivate();
+        }
+    },
+    /**
+     * Method: openURL
+     * open the dialog and load content from the provided url.  If you don't
+     * provide a URL then the dialog opens normally.
+     *
+     * Parameters:
+     * url - <String> the url to load when opening.
+     */
+    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);
+        } else {
+            this.open();
+        }
+    },
+
+    /**
+     * Method: open
+     * open the dialog.  This may be delayed depending on the
+     * asynchronous loading of dialog content.  The onOpen
+     * callback function is called when the dialog actually
+     * opens
+     */
+    open: function() {
+        if (!this.isOpening) {
+            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.openOnLoaded);
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.activate();
+        }
+    },
+    /**
+     * Method: close
+     * close the dialog and trigger the onClose callback function
+     * if necessary
+     */
+    close: function() {
+        this.isOpening = false;
+        this.hide();
+        this.fireEvent('close');
+    },
+
+    cleanup: function() { },
+    
+    /**
+     * APIMethod: isOpen
+     * returns true if the dialog is currently open, false otherwise
+     */
+    isOpen: function () {
+        //check to see if we're visible
+        return !((this.domObj.getStyle('display') === 'none') || (this.domObj.getStyle('visibility') === 'hidden'));
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.maxM)) {
+			if (this.maximize) {
+				this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'restoreLabel'}));
+	    	} else {
+	    		this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'maximizeLabel'}));
+	    	}
+    	}
+    	if ($defined(this.resizeHandle)) {
+    		this.resizeHandle.set('title', this.getText({set:'Jx',key:'dialog',value:'resizeTooltip'}));
+    	}
+      this.toggleCollapse(false);
+    },
+
+    initializeKeyboard: function() {
+      if(this.options.useKeyboard) {
+        var self = this;
+        this.keyboardEvents = {};
+        this.keyboardMethods = {
+          close : function(ev) {ev.preventDefault();self.close()}
+        }
+        this.keyboard = new Keyboard({
+          events: this.getKeyboardEvents()
+        });
+      }
+    },
+
+    /**
+     * Method: getKeyboardMethods
+     * used by this and all child classes to have methods listen to keyboard events,
+     * returned object will be parsed to the events object of a MooTools Keyboard instance
+     *
+     * @return Object
+     */
+    getKeyboardEvents : function() {
+      var self = this;
+      for(var i in this.options.keys) {
+        // only add a reference once, otherwise keyboard events will be fired twice in subclasses
+        if(!$defined(this.keyboardEvents[i])) {
+          if($defined(this.keyboardMethods[this.options.keys[i]])) {
+            this.keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+          }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+            this.keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+          }else if(Jx.type(this.options.keys[i]) == 'function') {
+            this.keyboardEvents[i] = this.options.keys[i].bind(self);
+          }else{
+            // allow disabling of special keys by setting them to false or null with having a warning
+            if(this.options.keyboardMethods[this.options.keys[i]] != false) {
+              $defined(console) ? console.warn("keyboard method %o not defined for %o", this.options.keys[i], this) : false;
+            }
+          }
+        }
+      }
+      return this.keyboardEvents;
+    }
+});
+
+// $Id: splitter.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Splitter
+ *
+ * Extends: <Jx.Object>
+ *
+ * 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.
+ *
+ * A horizontal split creates containers that divide the space horizontally
+ * with vertical bars between the containers.  A vertical split divides
+ * the space vertically and creates horizontal bars between the containers.
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - splitter.barToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Splitter = new Class({
+    Family: 'Jx.Splitter',
+    Extends: Jx.Object,
+    /**
+     * Property: domObj
+     * {HTMLElement} the element being split
+     */
+    domObj: null,
+    /**
+     * Property: elements
+     * {Array} an array of elements that are displayed in each of the split
+     * areas
+     */
+    elements: null,
+    /**
+     * Property: bars
+     * {Array} an array of the bars between each of the elements used to
+     * resize the split areas.
+     */
+    bars: null,
+    /**
+     * Property: firstUpdate
+     * {Boolean} track the first resize event so that unexposed Jx things
+     * can be forced to calculate their size the first time they are exposed.
+     */
+    firstUpdate: true,
+    options: {
+        /* Option: useChildren
+         * {Boolean} if set to true, then the children of the
+         * element to be split are used as the elements.  The default value is
+         * false.  If this is set, then the elements and splitInto options
+         * are ignored.
+         */
+        useChildren: false,
+        /* Option: splitInto
+         * {Integer} the number of elements to split the domObj into.
+         * If not set, then the length of the elements option is used, or 2 if
+         * elements is not specified.  If splitInto is specified and elements
+         * is specified, then splitInto is used.  If there are more elements than
+         * splitInto specifies, then the extras are ignored.  If there are less
+         * elements than splitInto specifies, then extras are created.
+         */
+        splitInto: 2,
+        /* Option: elements
+         * {Array} an array of elements to put into the split areas.
+         * If splitInto is not set, then it is calculated from the length of
+         * this array.
+         */
+        elements: null,
+        /* Option: containerOptions
+         * {Array} an array of objects that provide options
+         *  for the <Jx.Layout> constraints on each element.
+         */
+        containerOptions: [],
+        /* Option: barOptions
+         * {Array} an array of object that provide options for the bars,
+         * this array should be one less than the number of elements in the
+         * splitter.  The barOptions objects can contain a snap property indicating
+         * that a default snap object should be created in the bar and the value
+         * of 'before' or 'after' indicates which element it snaps open/shut.
+         */
+        barOptions: [],
+        /* Option: layout
+         * {String} either 'horizontal' or 'vertical', indicating the
+         * direction in which the domObj is to be split.
+         */
+        layout: 'horizontal',
+        /* Option: snaps
+         * {Array} an array of objects which can be used to snap
+         * elements open or closed.
+         */
+        snaps: [],
+        /* Option: onStart
+         * an optional function to call when a bar starts dragging
+         */
+        onStart: null,
+        /* Option: onFinish
+         * an optional function to call when a bar finishes dragging
+         */
+        onFinish: null
+    },
+
+    parameters: ['domObj','options'],
+
+    /**
+     * APIMethod: init
+     * Create a new instance of Jx.Splitter
+     */
+    init: function() {
+        this.domObj = document.id(this.options.domObj);
+        this.domObj.addClass('jxSplitContainer');
+        var jxLayout = this.domObj.retrieve('jxLayout');
+        if (jxLayout) {
+            jxLayout.addEvent('sizeChange', this.sizeChanged.bind(this));
+        }
+
+        this.elements = [];
+        this.bars = [];
+        var i;
+        var nSplits = 2;
+        if (this.options.useChildren) {
+            this.elements = this.domObj.getChildren();
+            nSplits = this.elements.length;
+        } else {
+            nSplits = this.options.elements ?
+                            this.options.elements.length :
+                            this.options.splitInto;
+            for (i=0; i<nSplits; i++) {
+                var el;
+                if (this.options.elements && this.options.elements[i]) {
+                    if (this.options.elements[i].domObj) {
+                        el = this.options.elements[i].domObj;
+                    } else {
+                        el = document.id(this.options.elements[i]);
+                    }
+                    if (!el) {
+                        el = this.prepareElement();
+                        el.id = this.options.elements[i];
+                    }
+                } else {
+                    el = this.prepareElement();
+                }
+                this.elements[i] = el;
+                this.domObj.adopt(this.elements[i]);
+            }
+        }
+        this.elements.each(function(el) { el.addClass('jxSplitArea'); });
+        for (i=0; i<nSplits; i++) {
+            var jxl = this.elements[i].retrieve('jxLayout');
+            if (!jxl) {
+                new Jx.Layout(this.elements[i], this.options.containerOptions[i]);
+            } else {
+                if (this.options.containerOptions[i]) {
+                    jxl.resize($merge(this.options.containerOptions[i],
+                        {position:'absolute'}));
+                } else {
+                    jxl.resize({position: 'absolute'});
+                }
+            }
+        }
+
+        for (i=1; i<nSplits; i++) {
+            var bar;
+            if (this.options.prepareBar) {
+                bar = this.options.prepareBar(i-1);
+            } else {
+                bar = this.prepareBar();
+            }
+            bar.store('splitterObj', this);
+            bar.store('leftSide',this.elements[i-1]);
+            bar.store('rightSide', this.elements[i]);
+            this.elements[i-1].store('rightBar', bar);
+            this.elements[i].store('leftBar', bar);
+            this.domObj.adopt(bar);
+            this.bars[i-1] = bar;
+        }
+
+        //making dragging dependent on mootools Drag class
+        if ($defined(Drag)) {
+            this.establishConstraints();
+        }
+
+        for (i=0; i<this.options.barOptions.length; i++) {
+            if (!this.bars[i]) {
+                continue;
+            }
+            var opt = this.options.barOptions[i];
+            if (opt && opt.snap && (opt.snap == 'before' || opt.snap == 'after')) {
+                var element;
+                if (opt.snap == 'before') {
+                    element = this.bars[i].retrieve('leftSide');
+                } else if (opt.snap == 'after') {
+                    element = this.bars[i].retrieve('rightSide');
+                }
+                var snap;
+                var snapEvents;
+                if (opt.snapElement) {
+                    snap = opt.snapElement;
+                    snapEvents = opt.snapEvents || ['click', 'dblclick'];
+                } else {
+                    snap = this.bars[i];
+                    snapEvents = opt.snapEvents || ['dblclick'];
+                }
+                if (!snap.parentNode) {
+                    this.bars[i].adopt(snap);
+                }
+                new Jx.Splitter.Snap(snap, element, this, snapEvents);
+            }
+        }
+
+        for (i=0; i<this.options.snaps.length; i++) {
+            if (this.options.snaps[i]) {
+                new Jx.Splitter.Snap(this.options.snaps[i], this.elements[i], this);
+            }
+        }
+
+        this.sizeChanged();
+    },
+    /**
+     * Method: prepareElement
+     * Prepare a new, empty element to go into a split area.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that goes into a split area.
+     */
+    prepareElement: function(){
+        var o = new Element('div', {styles:{position:'absolute'}});
+        return o;
+    },
+
+    /**
+     * Method: prepareBar
+     * Prepare a new, empty bar to go into between split areas.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that becomes a bar.
+     */
+    prepareBar: function() {
+        var o = new Element('div', {
+            'class': 'jxSplitBar'+this.options.layout.capitalize(),
+            'title': this.getText({set:'Jx',key:'splitter',value:'barToolTip'})
+        });
+        return o;
+    },
+
+    /**
+     * Method: establishConstraints
+     * Setup the initial set of constraints that set the behaviour of the
+     * bars between the elements in the split area.
+     */
+    establishConstraints: function() {
+        var modifiers = {x:null,y:null};
+        var fn;
+        if (this.options.layout == 'horizontal') {
+            modifiers.x = "left";
+            fn = this.dragHorizontal;
+        } else {
+            modifiers.y = "top";
+            fn = this.dragVertical;
+        }
+        if (typeof Drag != 'undefined') {
+            this.bars.each(function(bar){
+                var mask;
+                new Drag(bar, {
+                    //limit: limit,
+                    modifiers: modifiers,
+                    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');
+                        if (obj.retrieve('splitterObj') != this) {
+                            return;
+                        }
+                        fn.apply(this,[obj]);
+                        this.fireEvent('complete',[obj]);
+                        this.fireEvent('finish',[obj]);
+                    }).bind(this),
+                    onBeforeStart: (function(obj) {
+                        this.fireEvent('beforeStart',[obj]);
+                        mask = new Element('div',{'class':'jxSplitterMask'}).inject(obj, 'after');
+                    }).bind(this),
+                    onStart: (function(obj, event) {
+                        this.fireEvent('start',[obj, event]);
+                    }).bind(this)
+                });
+            }, this);
+        }
+    },
+
+    /**
+     * Method: dragHorizontal
+     * In a horizontally split container, handle a bar being dragged left or
+     * right by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragHorizontal: function(obj) {
+        var leftEdge = parseInt(obj.style.left,10);
+        var leftSide = obj.retrieve('leftSide');
+        var rightSide = obj.retrieve('rightSide');
+        var leftJxl = leftSide.retrieve('jxLayout');
+        var rightJxl = rightSide.retrieve('jxLayout');
+
+        var paddingLeft = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        /* process right side first */
+        var rsLeft, rsWidth, rsRight;
+
+        var size = obj.retrieve('size');
+        if (!size) {
+            size = obj.getBorderBoxSize();
+            obj.store('size',size);
+        }
+        rsLeft = leftEdge + size.width - paddingLeft;
+
+        var parentSize = this.domObj.getContentBoxSize();
+
+        if (rightJxl.options.width != null) {
+            rsWidth = rightJxl.options.width + rightJxl.options.left - rsLeft;
+            rsRight = parentSize.width - rsLeft - rsWidth;
+        } else {
+            rsWidth = parentSize.width - rightJxl.options.right - rsLeft;
+            rsRight = rightJxl.options.right;
+        }
+
+        /* enforce constraints on right side */
+        if (rsWidth < 0) {
+            rsWidth = 0;
+        }
+
+        if (rsWidth < rightJxl.options.minWidth) {
+            rsWidth = rightJxl.options.minWidth;
+        }
+        if (rightJxl.options.maxWidth >= 0 && rsWidth > rightJxl.options.maxWidth) {
+            rsWidth = rightJxl.options.maxWidth;
+        }
+
+        rsLeft = parentSize.width - rsRight - rsWidth;
+        leftEdge = rsLeft - size.width;
+
+        /* process left side */
+        var lsLeft, lsWidth;
+        lsLeft = leftJxl.options.left;
+        lsWidth = leftEdge - lsLeft;
+
+        /* enforce constraints on left */
+        if (lsWidth < 0) {
+            lsWidth = 0;
+        }
+        if (lsWidth < leftJxl.options.minWidth) {
+            lsWidth = leftJxl.options.minWidth;
+        }
+        if (leftJxl.options.maxWidth >= 0 &&
+            lsWidth > leftJxl.options.maxWidth) {
+            lsWidth = leftJxl.options.maxWidth;
+        }
+
+        /* update the leftEdge to accomodate constraints */
+        if (lsLeft + lsWidth != leftEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            leftEdge = lsLeft + lsWidth;
+            var delta = leftEdge + size.width - rsLeft;
+            rsLeft += delta;
+            rsWidth -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.left = paddingLeft + leftEdge + 'px';
+
+        /* update leftSide positions */
+        if (leftJxl.options.width == null) {
+            parentSize = this.domObj.getContentBoxSize();
+            leftSide.resize({right: parentSize.width - lsLeft-lsWidth});
+        } else {
+            leftSide.resize({width: lsWidth});
+        }
+
+        /* update rightSide position */
+        if (rightJxl.options.width == null) {
+            rightSide.resize({left:rsLeft});
+        } else {
+            rightSide.resize({left: rsLeft, width: rsWidth});
+        }
+    },
+
+    /**
+     * Method: dragVertical
+     * In a vertically split container, handle a bar being dragged up or
+     * down by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragVertical: function(obj) {
+        /* top edge of the bar */
+        var topEdge = parseInt(obj.style.top,10);
+
+        /* the containers on either side of the bar */
+        var topSide = obj.retrieve('leftSide');
+        var bottomSide = obj.retrieve('rightSide');
+        var topJxl = topSide.retrieve('jxLayout');
+        var bottomJxl = bottomSide.retrieve('jxLayout');
+
+        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) {
+            size = obj.getBorderBoxSize();
+            obj.store('size', size);
+        }
+        var parentSize = this.domObj.getContentBoxSize();
+
+        /* process top side first */
+        var bsTop, bsHeight, bsBottom;
+
+        /* top edge of bottom side is the top edge of bar plus the height of the bar */
+        bsTop = topEdge + size.height - paddingTop;
+
+        if (bottomJxl.options.height != null) {
+            /* bottom side height is fixed */
+            bsHeight = bottomJxl.options.height + bottomJxl.options.top - bsTop;
+            bsBottom = parentSize.height - bsTop - bsHeight;
+        } else {
+            /* bottom side height is not fixed. */
+            bsHeight = parentSize.height - bottomJxl.options.bottom - bsTop;
+            bsBottom = bottomJxl.options.bottom;
+        }
+
+        /* enforce constraints on bottom side */
+        if (bsHeight < 0) {
+            bsHeight = 0;
+        }
+
+        if (bsHeight < bottomJxl.options.minHeight) {
+            bsHeight = bottomJxl.options.minHeight;
+        }
+
+        if (bottomJxl.options.maxHeight >= 0 && bsHeight > bottomJxl.options.maxHeight) {
+            bsHeight = bottomJxl.options.maxHeight;
+        }
+
+        /* recalculate the top of the bottom side in case it changed
+           due to a constraint.  The bar may have moved also.
+         */
+        bsTop = parentSize.height - bsBottom - bsHeight;
+        topEdge = bsTop - size.height;
+
+        /* process left side */
+        var tsTop, tsHeight;
+        tsTop = topJxl.options.top;
+        tsHeight = topEdge - tsTop;
+
+        /* enforce constraints on left */
+        if (tsHeight < 0) {
+            tsHeight = 0;
+        }
+        if (tsHeight < topJxl.options.minHeight) {
+            tsHeight = topJxl.options.minHeight;
+        }
+        if (topJxl.options.maxHeight >= 0 &&
+            tsHeight > topJxl.options.maxHeight) {
+            tsHeight = topJxl.options.maxHeight;
+        }
+
+        /* update the topEdge to accomodate constraints */
+        if (tsTop + tsHeight != topEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            topEdge = tsTop + tsHeight;
+            var delta = topEdge + size.height - bsTop;
+            bsTop += delta;
+            bsHeight -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.top = paddingTop + topEdge + 'px';
+
+        /* update topSide positions */
+        if (topJxl.options.height == null) {
+            topSide.resize({bottom: parentSize.height - tsTop-tsHeight});
+        } else {
+            topSide.resize({height: tsHeight});
+        }
+
+        /* update bottomSide position */
+        if (bottomJxl.options.height == null) {
+            bottomSide.resize({top:bsTop});
+        } else {
+            bottomSide.resize({top: bsTop, height: bsHeight});
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * handle the size of the container being changed.
+     */
+    sizeChanged: function() {
+        if (this.options.layout == 'horizontal') {
+            this.horizontalResize();
+        } else {
+            this.verticalResize();
+        }
+    },
+
+    /**
+     * Method: horizontalResize
+     * Resize a horizontally layed-out container
+     */
+    horizontalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().width;
+        var overallWidth = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.width == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size',size);
+            }
+            availableSpace -= size.width;
+        }
+
+        var nVariable = 0, w = 0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.width != null) {
+                availableSpace -= parseInt(jxo.width,10);
+            } else {
+                w = 0;
+                if (jxo.right != 0 ||
+                    jxo.left != 0) {
+                    w = e.getBorderBoxSize().width;
+                }
+
+                availableSpace -= w;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.width;
+            jxo.width = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var leftPadding = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.width != null) {
+                 jxl.resize({left: currentPosition});
+                 currentPosition += jxo.width;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 if (jxo.right != 0 || jxo.left != 0) {
+                     w = e.getBorderBoxSize().width + a;
+                 } else {
+                     w = a;
+                 }
+
+                 if (w < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + w/nVariable;
+                     }
+                     w = 0;
+                 }
+                 if (w < jxo.minWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.minWidth)/nVariable;
+                     }
+                     w = jxo.minWidth;
+                 }
+                 if (jxo.maxWidth >= 0 && w > jxo.maxWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.maxWidth)/nVariable;
+                     }
+                     w = e.options.maxWidth;
+                 }
+
+                 var r = overallWidth - currentPosition - w;
+                 jxl.resize({left: currentPosition, right: r});
+                 currentPosition += w;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.setStyle('left', leftPadding + currentPosition);
+                 currentPosition += rightBar.retrieve('size').width;
+             }
+         }
+    },
+
+    /**
+     * Method: verticalResize
+     * Resize a vertically layed out container.
+     */
+    verticalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().height;
+        var overallHeight = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.height == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size', size);
+            }
+            availableSpace -= size.height;
+        }
+
+        var nVariable = 0, h=0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.height != null) {
+                availableSpace -= parseInt(jxo.height,10);
+            } else {
+                if (jxo.bottom != 0 || jxo.top != 0) {
+                    h = e.getBorderBoxSize().height;
+                }
+
+                availableSpace -= h;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.height;
+            jxo.height = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var paddingTop = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['top']);
+            return m.padding.top;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.height != null) {
+                 jxl.resize({top: currentPosition});
+                 currentPosition += jxo.height;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 h = 0;
+                 if (jxo.bottom != 0 || jxo.top != 0) {
+                     h = e.getBorderBoxSize().height + a;
+                 } else {
+                     h = a;
+                 }
+
+                 if (h < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + h/nVariable;
+                     }
+                     h = 0;
+                 }
+                 if (h < jxo.minHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.minHeight)/nVariable;
+                     }
+                     h = jxo.minHeight;
+                 }
+                 if (jxo.maxHeight >= 0 && h > jxo.maxHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.maxHeight)/nVariable;
+                     }
+                     h = jxo.maxHeight;
+                 }
+
+                 var r = overallHeight - currentPosition - h;
+                 jxl.resize({top: currentPosition, bottom: r});
+                 currentPosition += h;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.style.top = paddingTop + currentPosition + 'px';
+                 currentPosition += rightBar.retrieve('size').height;
+             }
+         }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	this.bars.each(function(bar){
+    		document.id(bar).set('title', this.getText({set:'Jx',key:'splitter',value:'barToolTip'}));
+    	},this);	
+    }
+});// $Id: panelset.js 895 2010-05-07 19:05:19Z conrad.barthelmes $
+/**
+ * Class: Jx.PanelSet
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A panel set manages a set of panels within a DOM element.  The PanelSet
+ * fills its container by resizing the panels in the set to fill the width and
+ * then distributing the height of the container across all the panels. 
+ * Panels can be resized by dragging their respective title bars to make them
+ * taller or shorter.  The maximize button on the panel title will cause all
+ * other panels to be closed and the target panel to be expanded to fill the
+ * remaining space.  In this respect, PanelSet works like a traditional
+ * Accordion control.
+ *
+ * When creating panels for use within a panel set, it is important to use the
+ * proper options.  You must override the collapse option and set it to false
+ * and add a maximize option set to true.  You must also not include options
+ * for menu and close.
+ *
+ * Example:
+ * (code)
+ * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'c1'});
+ * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'c2'});
+ * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'c3'});
+ * var panelSet = new Jx.PanelSet('panels', [p1,p2,p3]);
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - panelset.barTooltip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.PanelSet = new Class({
+    Family: 'Jx.PanelSet',
+    Extends: Jx.Widget,
+
+    options: {
+        /* Option: parent
+         * the object to add the panel set to
+         */
+        parent: null,
+        /* Option: panels
+         * an array of <Jx.Panel> objects that will be managed by the set.
+         */
+        panels: []
+    },
+
+    /**
+     * Property: panels
+     * {Array} the panels being managed by the set
+     */
+    panels: null,
+    /**
+     * Property: height
+     * {Integer} the height of the container, cached for speed
+     */
+    height: null,
+    /**
+     * Property: firstLayout
+     * {Boolean} true until the panel set has first been resized
+     */
+    firstLayout: true,
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.PanelSet.
+     */
+    render: function() {
+        if (this.options.panels) {
+            this.panels = this.options.panels;
+            this.options.panels = null;
+        }
+        this.domObj = new Element('div');
+        new Jx.Layout(this.domObj);
+
+        //make a fake panel so we get the right number of splitters
+        var d = new Element('div', {styles:{position:'absolute'}});
+        new Jx.Layout(d, {minHeight:0,maxHeight:0,height:0});
+        var elements = [d];
+        this.panels.each(function(panel){
+            elements.push(panel.domObj);
+            panel.options.hideTitle = true;
+            panel.contentContainer.resize({top:0});
+            panel.toggleCollapse = this.maximizePanel.bind(this,panel);
+            panel.domObj.store('Jx.Panel', panel);
+            panel.manager = this;
+        }, this);
+
+        this.splitter = new Jx.Splitter(this.domObj, {
+            splitInto: this.panels.length+1,
+            layout: 'vertical',
+            elements: elements,
+            prepareBar: (function(i) {
+                var bar = new Element('div', {
+                    'class': 'jxPanelBar',
+                    'title': this.getText({set:'Jx',key:'panelset',value:'barToolTip'})
+                });
+
+                var panel = this.panels[i];
+                panel.title.setStyle('visibility', 'hidden');
+                document.id(document.body).adopt(panel.title);
+                var size = panel.title.getBorderBoxSize();
+                bar.adopt(panel.title);
+                panel.title.setStyle('visibility','');
+
+                bar.setStyle('height', size.height);
+                bar.store('size', size);
+
+                return bar;
+            }).bind(this)
+        });
+        this.addEvent('addTo', function() {
+            document.id(this.domObj.parentNode).setStyle('overflow', 'hidden');
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: maximizePanel
+     * Maximize a panel, taking up all available space (taking into
+     * consideration any minimum or maximum values)
+     */
+    maximizePanel: function(panel) {
+        var domHeight = this.domObj.getContentBoxSize().height;
+        var space = domHeight;
+        var panelSize = panel.domObj.retrieve('jxLayout').options.maxHeight;
+        var panelIndex,i,p,thePanel,o,panelHeight;
+        /* calculate how much space might be left after setting all the panels to
+         * their minimum height (except the one we are resizing of course)
+         */
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            space -= p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                space -= o.minHeight;
+            } else {
+                panelIndex = i;
+            }
+        }
+
+        // calculate how much space the panel will take and what will be left over
+        if (panelSize == -1 || panelSize >= space) {
+            panelSize = space;
+            space = 0;
+        } else {
+            space = space - panelSize;
+        }
+        var top = 0;
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            top += p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // this panel can stay open at its current height
+                        space -= panelHeight;
+                        p.resize({top: top, height: panelHeight});
+                        top += panelHeight;
+                    } else {
+                        // this panel needs to shrink some
+                        if (space > o.minHeight) {
+                            // it can use all the space
+                            p.resize({top: top, height: space});
+                            top += space;
+                            space = 0;
+                        } else {
+                            p.resize({top: top, height: o.minHeight});
+                            top += o.minHeight;
+                        }
+                    }
+                } else {
+                    // no more space, just shrink away
+                    p.resize({top:top, height: o.minHeight});
+                    top += o.minHeight;
+                }
+                p.retrieve('rightBar').style.top = top + 'px';
+            } else {
+                break;
+            }
+        }
+
+        /* now work from the bottom up */
+        var bottom = domHeight;
+        for (i=this.splitter.elements.length - 1; i > 0; i--) {
+            p = this.splitter.elements[i];
+            if (p !== panel.domObj) {
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // panel can stay open
+                        bottom -= panelHeight;
+                        space -= panelHeight;
+                        p.resize({top: bottom, height: panelHeight});
+                    } else {
+                        if (space > o.minHeight) {
+                            bottom -= space;
+                            p.resize({top: bottom, height: space});
+                            space = 0;
+                        } else {
+                            bottom -= o.minHeight;
+                            p.resize({top: bottom, height: o.minHeight});
+                        }
+                    }
+                } else {
+                    bottom -= o.minHeight;
+                    p.resize({top: bottom, height: o.minHeight, bottom: null});
+                }
+                bottom -= p.retrieve('leftBar').getBorderBoxSize().height;
+                p.retrieve('leftBar').style.top = bottom + 'px';
+
+            } else {
+                break;
+            }
+        }
+        panel.domObj.resize({top: top, height:panelSize, bottom: null});
+        this.fireEvent('panelMaximize',panel);
+    },
+    
+    createText: function (lang) {
+      this.parent();
+      //barTooltip is handled by the splitter's createText() function
+    }
+});// $Id: message.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Dialog.Message
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Message is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user. It only presents an OK button.
+ * 
+ * MooTools.lang Keys:
+ * - message.okButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Message = new Class({
+    Family: 'Jx.Dialog.Message',
+    Extends: Jx.Dialog,
+    Binds: ['onOk'],
+    options: {
+        /**
+         * Option: message
+         * The message to display to the user
+         */
+        message: '',
+        /**
+         * Option: width
+         * default width of message dialogs is 300px
+         */
+        width: 300,
+        /**
+         * Option: height
+         * default height of message dialogs is 150px
+         */
+        height: 150,
+        /**
+         * Option: close
+         * by default, message dialogs are closable
+         */
+        close: true,
+        /**
+         * Option: resize
+         * by default, message dialogs are resizable
+         */
+        resize: true,
+        /**
+         * Option: collapse
+         * by default, message dialogs are not collapsible
+         */
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * Method: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'message',value:'okButton'}),
+            onClick: this.onOk
+        });
+        this.buttons.add(this.ok);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.message);
+        if (type === 'string' || type == 'object' || type == 'element') {
+            this.question = new Element('div', {
+                'class': 'jxMessage'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.message));
+              break;
+              case 'element':
+                this.options.message.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxMessage');
+        }
+        this.options.content = this.question;
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok = function(ev) { ev.preventDefault(); self.close(); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onOk
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onOk: function () {
+        this.close();
+    },
+    
+    /**
+     * APIMethod: setMessage
+     * set the message of the dialog, useful for responding to language
+     * changes on the fly.
+     *
+     * Parameters
+     * message - {String} the new message
+     */
+    setMessage: function(message) {
+      this.options.message = message;
+      if ($defined(this.question)) {
+        this.question.set('html',this.getText(message));
+      }
+    },
+    
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.ok)) {
+        this.ok.setLabel({set:'Jx',key:'message',value:'okButton'});
+      }
+      if(Jx.type(this.options.message) === 'object') {
+        this.question.set('html', this.getText(this.options.message))
+      }
+    }
+});
+// $Id: confirm.js 895 2010-05-07 19:05:19Z conrad.barthelmes $
+/**
+ * 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.
+ * 
+ * MooTools.lang Keys:
+ * - confirm.affirmitiveLabel
+ * - confirm.negativeLabel
+ * 
+ * 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: '',
+        /**
+         * Jx.Dialog option defaults
+         */
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        },
+        width: 300,
+        height: 150,
+        close: false,
+        resize: true,
+        collapse: false
+    },
+    /**
+     * Reference to MooTools keyboards Class for handling keypress events like Enter or ESC
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * creates the dialog
+     */
+    render: function () {
+        //create content to be added
+        //turn scrolling off as confirm only has 2 buttons.
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll: false});
+
+        // COMMENT: returning boolean would be more what people expect instead of a localized label of a button?
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'affirmativeLabel'}),
+            onClick: this.onClick.bind(this, true)
+        }),
+        this.cancel = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'negativeLabel'}),
+            onClick: this.onClick.bind(this, false)
+        })
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.question);
+        if (type === 'string' || type === 'object' || type == 'element'){
+            this.question = new Element('div', {
+                'class': 'jxConfirmQuestion'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.question));
+              break;
+              case 'element':
+                this.options.question.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxConfirmQuestion');
+        }
+        this.options.content = this.question;
+
+        // add default key functions
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        // add new ones
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * 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]);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'confirm',value:'affirmativeLabel'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'confirm',value:'negativeLabel'});
+    	}
+      if(Jx.type(this.options.question) === 'object') {
+        this.question.set('html', this.getText(this.options.question))
+      }
+    }
+
+});// $Id: tooltip.js 835 2010-04-07 22:59:37Z conrad.barthelmes $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends : Jx.Widget,
+    Binds: ['enter', 'leave', 'move'],
+    options : {
+        /**
+         * Option: offsets
+         * An object with x and y components for where to put the tip related
+         * to the mouse cursor.
+         */
+        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'],
+
+    /**
+     * Method: 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' || Jx.type(this.options.tip) == 'object') {
+            this.domObj.set('html', this.getText(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);
+        this.options.target.addEvent('mouseleave', this.leave);
+        this.options.target.addEvent('mousemove', this.move);
+    },
+
+    /**
+     * Method: enter
+     * Method run when the cursor passes over an element with a tip
+     *
+     * Parameters:
+     * event - the event object
+     */
+    enter : function (event) {
+        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
+     */
+    leave : function (event) {
+        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);
+    },
+    /**
+     * APIMethod: detach
+     * Called to manually remove a tooltip.
+     */
+    detach : function () {
+        this.target.eliminate('Tip');
+        this.destroy();
+    }
+});
+// $Id: fieldset.js 867 2010-04-23 15:54:04Z conrad.barthelmes $
+/**
+ * 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({
+    Family: 'Jx.Fieldset',
+    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
+    },
+
+    classes: new Hash({
+        domObj: 'jxFieldset',
+        legend: 'jxFieldsetLegend'
+    }),
+
+    /**
+     * 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;
+        }
+
+        //FIELDSET
+        if (this.domObj) {
+            if ($defined(this.options.id)) {
+                this.domObj.set('id', this.options.id);
+            }
+            if ($defined(this.options.fieldsetClass)) {
+                this.domObj.addClass(this.options.fieldsetClass);
+            }
+        }
+
+        if (this.legend) {
+            if ($defined(this.options.legend)) {
+                this.legend.set('html', this.getText(this.options.legend));
+                if ($defined(this.options.legendClass)) {
+                    this.legend.addClass(this.options.legendClass);
+                }
+            } else {
+                this.legend.destroy();
+            }
+        }
+    },
+    /**
+     * APIMethod: add
+     * Adds fields to this fieldset
+     *
+     * Parameters:
+     * pass as many fields to this method as you like. They should 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 ($defined(field.jxFamily) && !$defined(field.form) && $defined(this.form)) {
+                field.form = this.form;
+                this.form.addField(field);
+            }
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: addTo
+     *
+     */
+    addTo: function(what) {
+        if (what instanceof Jx.Form) {
+            this.form = what;
+        } else if (what instanceof Jx.Fieldset) {
+            this.form = what.form;
+        }
+        return this.parent(what);
+    }
+    
+});
+// $Id: form.js 901 2010-05-10 19:33:28Z jonlb at comcast.net $
+/**
+ * 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
+ * through the use of a plugin (Jx.Plugin.Form.Validator).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Form = new Class({
+    Family: 'Jx.Form',
+    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: formClass
+         */
+        formClass: null,
+        /**
+         * Option: name
+         * the name property for the form
+         */
+        name: '',
+        /**
+         * Option: acceptCharset
+         * the character encoding to be used. Defaults to utf-8.
+         */
+        acceptCharset: 'utf-8',
+
+        template: '<form class="jxForm"></form>'
+    },
+    
+    /**
+     * Property: defaultAction
+     * the default field to activate if the user hits the enter key in this
+     * form.  Set by specifying default: true as an option to a field.  Will
+     * only work if the default is a Jx button field or an input of a type
+     * that is a button
+     */
+    defaultAction: null,
+
+    /**
+     * Property: fields
+     * An array of all of the single fields (not contained in a fieldset) for
+     * this form
+     */
+    fields : null,
+    /**
+     * Property: pluginNamespace
+     * required variable for plugins
+     */
+    pluginNamespace: 'Form',
+
+    classes: $H({
+        domObj: 'jxForm'
+    }),
+    
+    init: function() {
+      this.parent();
+      this.fields = new Hash();
+    },
+    /**
+     * APIMethod: render
+     * Constructs the form but does not add it to anything to be shown. The
+     * caller should use form.addTo() to add the form to the DOM.
+     */
+    render : function () {
+        this.parent();
+        //create the form first
+        this.domObj.set({
+            'method' : this.options.method,
+            'action' : this.options.action,
+            'name' : this.options.name,
+            'accept-charset': this.options.acceptCharset,
+            events: {
+                keypress: function(e) {
+                    if (e.key == 'enter' && 
+                        e.target.tagName != "TEXTAREA" && 
+                        this.defaultAction &&
+                        this.defaultAction.click) {
+                        document.id(this.defaultAction).focus();
+                        this.defaultAction.click();
+                        e.stop();
+                    }
+                }.bind(this)
+            }
+        });
+
+        if (this.options.fileUpload) {
+            this.domObj.set('enctype', 'multipart/form-data');
+        }
+        
+        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);
+        if (field.options.defaultAction) {
+            this.defaultAction = 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) && 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) {
+        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);
+            } else if (field instanceof Jx.Fieldset && !$defined(field.form)) {
+                field.form = this;
+            }
+            
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: reset
+     * Resets all fields back to their original value
+     */
+    reset : function () {
+        this.fields.each(function (field, name) {
+            field.reset();
+        }, this);
+        this.fireEvent('reset',this);
+    },
+    /**
+     * APIMethod: getFieldsByName
+     * Allows retrieving a field from a form by the name of the field (NOT the
+     * ID).
+     *
+     * Parameters:
+     * name - {string} the name of the field to find
+     */
+    getFieldsByName: function (name) {
+        var fields = [];
+        this.fields.each(function(val, id){
+            if (val.name === name) {
+                fields.push(val);
+            }
+        },this);
+        return fields;
+    },
+    /**
+     * APIMethod: getField
+     * Returns a Jx.Field object by its ID.
+     *
+     * Parameters:
+     * id - {string} the id of the field to find.
+     */
+    getField: function (id) {
+        if (this.fields.has(id)) {
+            return this.fields.get(id);
+        } 
+        return null;
+    },
+    /**
+     * APIMethod: setBusy
+     * Sets the busy state of the Form and all of it's fields.
+     *
+     * Parameters:
+     * state - {boolean} indicated whether the form is busy or not.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.parent(state);
+      this.fields.each(function(field) {
+        field.setBusy(state, true);
+      });
+    }
+});
+// $Id: field.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Field
+ *
+ * Extends: <Jx.Widget>
+ *
+ * This class is the base class for all form fields.
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - field.requiredText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field = new Class({
+    Family: 'Jx.Field',
+    Extends : Jx.Widget,
+    pluginNamespace: 'Field',
+    Binds: ['changeText'],
+    
+    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 provided
+         * that the plugin Jx.Plugin.Field.Validator has been added to this
+         * field.
+         */
+        required : false,
+        /**
+         * 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,
+        /**
+         * Option: defaultAction
+         * {Boolean} defaults to false, if true and this field is a button
+         * of some kind (Jx.Button, a button or an input of type submit) then
+         * if the user hits the enter key on any field in the form except a
+         * textarea, this field will be activated as if clicked
+         */
+        defaultAction: 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 : new Hash({
+        domObj: 'jxInputContainer',
+        label: 'jxInputLabel',
+        tag: 'jxInputTag'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render : function () {
+        this.classes.set('field', 'jxInput'+this.type);
+        var name = $defined(this.options.name) ? this.options.name : '';
+        this.options.template = this.options.template.substitute({name:name});
+        this.parent();
+
+        this.id = ($defined(this.options.id)) ? this.options.id : this
+                .generateId();
+        this.name = this.options.name;
+
+        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';
+            }
+        }
+
+
+        // FIELD
+        if (this.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 events
+            this.field.addEvents({
+              'focus': this.onFocus.bind(this),
+              'blur': this.onBlur.bind(this),
+              'change': this.onChange.bind(this)
+            });
+
+            this.field.store('field', this);
+
+            // add click event to label to set the focus to the field
+            // COMMENT: tried it without a function using addEvent('click', this.field.focus.bind(this)) but crashed in IE
+            if(this.label) {
+              this.label.addEvent('click', function() {
+                this.field.focus();
+              }.bind(this));
+            }
+        }
+        // LABEL
+        if (this.label) {
+            if ($defined(this.options.labelClass)) {
+                this.label.addClass(this.options.labelClass);
+            }
+            if ($defined(this.options.label)) {
+                this.label.set('html', this.getText(this.options.label)
+                        + this.options.labelSeparator);
+            }
+
+            this.label.set('for', this.id);
+
+            if (this.options.required) {
+                this.requiredText = new Element('em', {
+                    'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+                    'class' : 'required'
+                });
+                this.requiredText.inject(this.label);
+            }
+
+        }
+
+        // TAG
+        if (this.tag) {
+            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) {
+        if (!this.options.readonly) {
+            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.setValue(this.options.value);
+        this.fireEvent('reset', this);
+    },
+    /**
+     * APIMethod: disable
+     * Disabled the field
+     */
+    disable : function () {
+        this.options.disabled = true;
+        this.field.set("disabled", "disabled");
+        this.field.addClass('jxFieldDisabled');
+    },
+    /**
+     * APIMethod: enable
+     * Enables the field
+     */
+    enable : function () {
+        this.options.disabled = false;
+        this.field.erase("disabled");
+        this.field.removeClass('jxFieldDisabled');
+    },
+    
+    /**
+     * APIMethod: addTo
+     * Overrides default Jx.Widget AddTo() so that we can call .add() if
+     * adding to a Jx.Form or Jx.Fieldset object.
+     *
+     * Parameters:
+     * what - the element or object to add this field to.
+     * where - where in the object to place it. Not valid if adding to Jx.Form
+     *      or Jx.Fieldset.
+     */
+    addTo: function(what, where) {
+        if (what instanceof Jx.Fieldset || what instanceof Jx.Form) {
+            what.add(this);
+        } else {
+            this.parent(what, where);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        this.parent();
+        if ($defined(this.options.label) && this.label) {
+          this.label.set('html', this.getText(this.options.label) + this.options.labelSeparator);
+        }
+        if(this.options.required) {
+          this.requiredText = new Element('em', {
+              'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+              'class' : 'required'
+          });
+          this.requiredText.inject(this.label);
+        }
+        if ($defined(this.requiredText)) {
+          this.requiredText.set('html',this.getText({set:'Jx',key:'field',value:'requiredText'}));
+        }
+    }, 
+    
+    onFocus: function() {
+      this.fireEvent('focus', this);
+    },
+    
+    onBlur: function () {
+      this.fireEvent('blur',this);
+    },
+    
+    onChange: function () {
+      this.fireEvent('change', this);
+    },
+    
+    setBusy: function(state, withoutMask) {
+      if (!withoutMask) {
+        this.parent(state);
+      }
+      this.field.set('readonly', state || this.options.readonly);
+    }
+
+});
+// $Id: text.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span></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: prompt.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Dialog.Prompt
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Prompt is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user and ask for a text response. 
+ * 
+ * MooTools.lang Keys:
+ * - prompt.okButton
+ * - prompt.cancelButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Prompt = new Class({
+
+    Extends: Jx.Dialog,
+
+    options: {
+        /**
+         * Option: prompt
+         * The message to display to the user
+         */
+        prompt: '',
+        /**
+         * Option: startingValue
+         * The startingvalue to place in the input field
+         */
+        startingValue: '',
+        /**
+         * Option: fieldOptions,
+         * Object with various
+         */
+        fieldOptions: {
+          type : 'Text',
+          options: {},
+          validate : true,
+          validatorOptions: {
+            validators: ['required'],
+            validateOnBlur: true,
+            validateOnChange : false
+          },
+          showErrorMsg : true
+        },
+        /**
+         * Jx.Dialog option defaults
+         */
+        width: 400,
+        height: 200,
+        close: true,
+        resize: true,
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * APIMethod: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'okButton'}),
+                onClick: this.onClick.bind(this, true)
+            });
+        this.cancel = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'cancelButton'}),
+                onClick: this.onClick.bind(this, false)
+            });
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+
+        var fOpts = this.options.fieldOptions;
+            fOpts.options.label = this.getText(this.options.prompt);
+            fOpts.options.value = this.options.startingValue;
+            fOpts.options.containerClass = 'jxPrompt';
+
+        if(Jx.type(fOpts.type) === 'string' && $defined(Jx.Field[fOpts.type.capitalize()])) {
+          this.field = new Jx.Field[fOpts.type.capitalize()](fOpts.options);
+        }else if(Jx.type(fOpts.type) === 'Jx.Object'){
+          this.field = fOpts.type;
+        }else{
+          // warning and fallback?
+          window.console ? console.warn("Field type does not exist %o, using Jx.Field.Text", fOpts.type) : false;
+          this.field = new Jx.Field.Text(fOpts.options);
+        }
+
+        if(this.options.fieldOptions.validate) {
+          this.validator = new Jx.Plugin.Field.Validator(this.options.fieldOptions.validatorOptions);
+          this.validator.attach(this.field);
+        }
+
+        this.options.content = document.id(this.field);
+        
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onClick
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onClick: function (value) {
+        if(value && $defined(this.validator)) {
+          if(this.validator.isValid()) {
+            this.isOpening = false;
+            this.hide();
+            this.fireEvent('close', [this, value, this.field.getValue()]);
+          }else{
+            //this.options.content.adopt(this.validator.getError());
+            this.field.field.focus.delay(50, this.field.field);
+            //todo: show error messages ?
+          }
+        }else{
+          this.isOpening = false;
+          this.hide();
+          this.fireEvent('close', [this, value, this.field.getValue()]);
+        }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'prompt',value:'okButton'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'prompt',value:'cancelButton'});
+    	}
+      this.field.label.set('html', this.getText(this.options.prompt));
+    }
+
+
+});
+// $Id: dataview.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * 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
+        }
+    },
+
+    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('storeDataLoaded', this.bound.update);
+        this.options.data.addEvent('storeSortFinished', this.bound.update);
+        this.options.data.addEvent('storeDataLoadFailed', this.bound.update);
+
+        if (this.options.data.loaded) {
+            this.update();
+        }
+
+    },
+
+    /**
+     * Method: draw
+     * begins the process of creating the items
+     */
+    draw: function () {
+        var n = this.options.data.count();
+        if ($defined(n) && n > 0) {
+            for (var i = 0; i < n; i++) {
+                this.options.data.moveTo(i);
+
+                var item = this.createItem();
+                this.list.add(item);
+            }
+        } else {
+            var empty = new Element('div', {html: this.options.emptyTemplate});
+            this.list.add(item);
+        }
+        this.fireEvent('renderDone', this);
+    },
+    /**
+     * Method: createItem
+     * Actually does the work of getting the data from the store
+     * and creating a single item based on the provided template
+     */
+    createItem: function () {
+        //create the item
+        var itemObj = {};
+        this.itemCols.each(function (col) {
+            itemObj[col] = this.options.data.get(col);
+        }, this);
+        var itemTemp = this.options.itemTemplate.substitute(itemObj);
+        var item = new Element('div', {
+            'class': this.options.itemClass,
+            html: itemTemp
+        });
+        return item;
+    },
+    /**
+     * APIMethod: update
+     * This method begins the process of creating the items. It is called when
+     * the store is loaded or can be called to manually recreate the view.
+     */
+    update: function () {
+        if (!this.updating) {
+            this.updating = true;
+            this.list.empty();
+            this.options.data.sort(this.options.sortColumns);
+            this.draw();
+            this.updating = false;
+        }
+    },
+    /**
+     * Method: parseTemplate
+     * parses the provided template to determine which store columns are
+     * required to complete it.
+     *
+     * Parameters:
+     * template - the template to parse
+     */
+    parseTemplate: function (template) {
+        //we parse the template based on the columns in the data store looking
+        //for the pattern {column-name}. If it's in there we add it to the
+        //array of ones to look for
+        var columns = this.options.data.getColumns();
+        var arr = [];
+        columns.each(function (col) {
+            var s = '{' + col.name + '}';
+            if (template.contains(s)) {
+                arr.push(col.name);
+            }
+        }, this);
+        return arr;
+    },
+    /**
+     * Method: enterItem
+     * Fires mouseenter event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    enterItem: function(item, list){
+        this.fireEvent('mouseenter', item, list);
+    },
+    /**
+     * Method: leaveItem
+     * Fires mouseleave event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    leaveItem: function(item, list){
+        this.fireEvent('mouseleave', item, list);
+    },
+    /**
+     * Method: selectItem
+     * Fires select event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    selectItem: function(item, list){
+        this.fireEvent('select', item, list);
+    },
+    /**
+     * Method: unselectItem
+     * Fires unselect event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    unselectItem: function(item, list){
+        this.fireEvent('unselect', item, list);
+    },
+    /**
+     * Method: addItem
+     * Fires add event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    addItem: function(item, list) {
+        this.fireEvent('add', item, list);
+    },
+    /**
+     * Method: removeItem
+     * Fires remove event
+     *
+     * Parameters:
+     * item - the item that is the target of the event
+     * list - the list this item is in.
+     */
+    removeItem: function(item, list) {
+        this.fireEvent('remove', item, list);
+    },
+    /**
+     * Method: createList
+     * Creates the list object
+     *
+     * Parameters:
+     * container - the container to use in the list
+     * options - the options for the list
+     */
+    createList: function(container, options){
+        return new Jx.List(container, $extend({
+            onMouseenter: this.enterItem.bind(this),
+            onMouseleave: this.leaveItem.bind(this),
+            onSelect:  this.selectItem.bind(this),
+            onAdd: this.addItem.bind(this),
+            onRemove: this.removeItem.bind(this),
+            onUnselect: this.unselectItem.bind(this)
+        }, options));
+    }
+});
+// $Id: group.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Panel.DataView.Group
+ *
+ * Extends: <Jx.Panel.DataView>
+ *
+ * This extension of Jx.Panel.DataView that provides for grouping the items
+ * by a particular column.
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.DataView.Group = new Class({
+
+    Extends: Jx.Panel.DataView,
+
+    options: {
+        /**
+         * Option: groupTemplate
+         * The template used to render the group heading
+         */
+        groupTemplate: null,
+        /**
+         * Option: groupContainerClass
+         * The class added to the group container. All of the items and header
+         * for a single grouping is contained by a div that has this class added.
+         */
+        groupContainerClass: null,
+        /**
+         * Option: groupHeaderClass
+         * The class added to the heading. Used for styling.
+         */
+        groupHeaderClass: null,
+        /**
+         * Option: listOption
+         * Options to pass to the main list
+         */
+        listOptions: {
+            select: false,
+            hover: false
+        },
+        /**
+         * Option: itemOption
+         * Options to pass to the item lists
+         */
+        itemOptions: {
+            select: true,
+            hover: true,
+            hoverClass: 'jxItemHover',
+            selectClass: 'jxItemSelect'
+        }
+    },
+
+    init: function() {
+        this.groupCols = this.parseTemplate(this.options.groupTemplate);
+        this.itemManager = new Jx.Selection({
+            eventToFire: {
+                select: 'itemselect',
+                unselect: 'itemunselect'
+            },
+            selectClass: 'jxItemSelected'
+        });
+        this.groupManager = new Jx.Selection({
+            eventToFire: {
+                select: 'groupselect',
+                unselect: 'groupunselect'
+            },
+            selectClass: 'jxGroupSelected'
+        });
+        this.parent();
+
+    },
+    /**
+     * APIMethod: render
+     * sets up the list container and calls the parent class' render function.
+     */
+    render: function () {
+        this.list = this.createList(this.domA, this.listOptions, this.groupManager);
+        this.parent();
+
+    },
+    /**
+     * Method: draw
+     * actually does the work of creating the view
+     */
+    draw: function () {
+        var d = this.options.data;
+        var n = d.count();
+
+        if ($defined(n) && n > 0) {
+            var currentGroup = '';
+            var itemList = null;
+
+            for (var i = 0; i < n; i++) {
+                d.moveTo(i);
+                var group = d.get(this.options.sortColumns[0]);
+
+                if (group !== currentGroup) {
+                    //we have a new grouping
+
+                    //group container
+                    var container =  new Element('div', {
+                        'class': this.options.groupContainerClass
+                    });
+                    var l = this.createList(container,{
+                        select: false,
+                        hover: false
+                    });
+                    this.list.add(l.container);
+
+                    //group header
+                    currentGroup = group;
+                    var obj = {};
+                    this.groupCols.each(function (col) {
+                        obj[col] = d.get(col);
+                    }, this);
+                    var temp = this.options.groupTemplate.substitute(obj);
+                    var g = new Element('div', {
+                        'class': this.options.groupHeaderClass,
+                        'html': temp,
+                        id: 'group-' + group.replace(" ","-","g")
+                    });
+                    l.add(g);
+
+                    //items container
+                    var currentItemContainer = new Element('div', {
+                        'class': this.options.containerClass
+                    });
+                    itemList = this.createList(currentItemContainer, this.options.itemOptions, this.itemManager);
+                    l.add(itemList.container);
+                }
+
+                var item = this.createItem();
+                itemList.add(item);
+            }
+        } else {
+            var empty = new Element('div', {html: this.options.emptyTemplate});
+            this.list.add(empty);
+        }
+        this.fireEvent('renderDone', this);
+    },
+
+    /**
+     * Method: createList
+     * Creates the list object
+     *
+     * Parameters:
+     * container - the container to use in the list
+     * options - the options for the list
+     * manager - <Jx.Selection> which selection obj to connect to this list
+     */
+    createList: function(container, options, manager){
+        return new Jx.List(container, $extend({
+            onMouseenter: this.enterItem.bind(this),
+            onMouseleave: this.leaveItem.bind(this),
+            onAdd: this.addItem.bind(this),
+            onRemove: this.removeItem.bind(this)
+        }, options), manager);
+    }
+
+});
+// $Id: listitem.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.ListItem',
+    Extends: Jx.Widget,
+
+    options: {
+        enabled: true,
+        template: '<li class="jxListItemContainer jxListItem"></li>'
+    },
+
+    classes: new Hash({
+        domObj: 'jxListItemContainer',
+        domContent: 'jxListItem'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render: function () {
+        this.parent();
+        this.domContent.store('jxListItem', this);
+        this.domObj.store('jxListTarget', this.domContent);
+        this.loadContent(this.domContent);
+    },
+
+    enable: function(state) {
+
+    }
+});// $Id: listview.js 898 2010-05-10 04:08:44Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends: Jx.Widget,
+
+    pluginNamespace: 'ListView',
+
+    options: {
+        template: '<ul class="jxListView jxList"></ul>',
+        /**
+         * Option: listOptions
+         * control the behaviour of the list, see <Jx.List>
+         */
+        listOptions: {
+            hover: true,
+            press: true,
+            select: true
+        }
+    },
+
+    classes: new Hash({
+        domObj: 'jxListView',
+        listObj: 'jxList'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    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;
+        }
+
+        this.list = new Jx.List(this.listObj, this.options.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;
+    },
+
+    empty: function () {
+        this.list.empty();
+        return this;
+    }
+});// $Id: hidden.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputHidden" type="hidden" name="{name}"/></span>'
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Hidden'
+
+});
+
+
+
+
+/**
+ * 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.
+ *
+ * MooTools.lang Keys:
+ * - file.browseLabel
+ * 
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="jxInputTag"></span></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,
+        /**
+         * Option: mode
+         * determines whether this file field acts in single upload mode or
+         * multiple file upload mode. The multiple upload mode was done to work with
+         * Jx.Panel.FileUpload. When in multiple mode, this field will remove the actual
+         * file control after a file is selected, fires an event to signify the selection but will
+         * hold on to the files until told to upload them. Obviously 'multiple' mode isn't designed
+         * to be used when the control is outside of the Upload Panel (unless the user designs
+         * their own upload panel around it).
+         */
+        mode: 'single',
+        /**
+         * Events
+         */
+        onFileUploadBegin: $empty,
+        onFileUploadComplete: $empty,
+        onFileUploadProgress: $empty,
+        onFileUploadError: $empty,
+        onFileSelected: $empty
+
+    },
+    /**
+     * Property: type
+     * The Field type used in rendering
+     */
+    type: 'File',
+    /**
+     * Property: forms
+     * holds all form references when we're in multiple mode
+     */
+    forms: null,
+
+    init: function () {
+        this.parent();
+
+        this.forms = new Hash();
+        //create the iframe
+        //we use the same iFrame for each so we don't have to recreate it each time
+        this.setupIframe = true;
+        this.iframe = new IFrame(null, {
+            styles: {
+                'display':'none',
+                'visibility':'hidden'
+            },
+            name : this.generateId()
+        });
+        this.iframe.inject(document.body);
+        this.iframe.addEvent('load', this.processIFrameUpload.bind(this));
+
+    },
+
+    /**
+     * 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 : '<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'
+        });
+        this.browseButton = new Jx.Button({
+            label: this.getText({set:'Jx',key:'file',value:'browseLabel'})
+        });
+
+        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.options.mode=='single' && this.field.value !== '' && (this.text.field.value !== this.field.value)) {
+            this.text.field.value = this.field.value;
+            this.fireEvent('fileSelected', this);
+            if (this.options.autoUpload) {
+                this.uploadSingle();
+            }
+        } else if (this.options.mode=='multiple') {
+            var filename = this.field.value;
+            var form = this.prepForm();
+            this.forms.set(filename, form);
+            this.text.setValue('');
+            //fire the selected event.
+            this.fireEvent('fileSelected', filename);
+        }
+    },
+    /**
+     * 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');
+    },
+
+    prepForm: function () {
+        //load in the form
+        var form = new Jx.Form({
+            action : this.options.handlerUrl,
+            name : 'jxUploadForm',
+            fileUpload: true
+        });
+
+        //move the form input into it (cloneNode)
+        var parent = document.id(this.field.getParent());
+        var sibling = document.id(this.field).getPrevious();
+        var clone = this.field.clone().cloneEvents(this.field);
+        document.id(form).grab(this.field);
+        // tried clone.replaces(this.field) but it didn't seem to work
+        if (sibling) {
+          clone.inject(sibling, 'after');
+        } else if (parent) {
+            clone.inject(parent, 'top');
+        }
+        this.field = clone;
+
+        this.mouseLeave();
+
+        return form;
+    },
+
+    upload: function () {
+        //do we have files to upload?
+        if (this.forms.getLength() > 0) {
+            var keys = this.forms.getKeys();
+            this.currentKey = keys[0];
+            var form = this.forms.get(this.currentKey);
+            this.forms.erase(this.currentKey);
+            this.uploadSingle(form);
+        } else {
+            //fire finished event...
+            this.fireEvent('allUploadsComplete', this);
+        }
+    },
+    /**
+     * APIMethod: uploadSingle
+     * Call this to upload the file to the server
+     */
+    uploadSingle: function (form) {
+        this.form = $defined(form) ? form : this.prepForm();
+
+        this.fireEvent('fileUploadBegin', [this.currentKey, this]);
+
+        this.text.setValue('');
+
+        document.id(this.form).set('target', this.iframe.get('name')).setStyles({
+            visibility: 'hidden',
+            display: 'none'
+        }).inject(document.body);
+
+        //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');
+        }
+
+        //submit the form
+        document.id(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('fileUploadProgress', [data, this.currentKey, this]);
+            if (data.current < data.total) {
+                this.polling = true;
+                this.pollUpload();
+            } else {
+                this.polling = false;
+            }
+        }
+    },
+    /**
+     * Method: uploadFailure
+     * called if there is a problem getting progress on the upload
+     */
+    uploadFailure: function (xhr) {
+        this.fireEvent('fileUploadProgressError', [this, xhr]);
+    },
+    /**
+     * 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
+        if (!this.setupIframe) {
+            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;
+                this.uploadCleanUp();
+                this.fireEvent('fileUploadComplete', [data, this.currentKey, this]);
+            } else {
+                this.fireEvent('fileUploadError', [data , this.currentKey, this]);
+            }
+
+            if (this.options.mode == 'multiple') {
+                this.upload();
+            } else {
+                this.fireEvent('allUploadsComplete', this);
+            }
+        } else {
+            this.setupIframe = false;
+        }
+    },
+    /**
+     * 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();
+            if (this.options.mode == 'single') {
+                this.iframe.destroy();
+            }
+        }
+    },
+    /**
+     * APIMethod: remove
+     * Removes a file from the hash of forms to upload.
+     *
+     * Parameters:
+     * filename - the filename indicating which file to remove.
+     */
+    remove: function (filename) {
+        if (this.forms.has(filename)) {
+            this.forms.erase(filename);
+        }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.browseButton)) {
+    		this.browseButton.setLabel( this.getText({set:'Jx',key:'file',value:'browseLabel'}) );
+    	}
+    }
+});
+/**
+ * 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
+ * 
+ * MooTools.lang keys:
+ * - progressbar.messageText
+ * - progressbar.progressText
+ *
+ * Copyright (c) 2010 by Jonathan Bomgardner
+ * Licensed under an mit-style license
+ */
+Jx.Progressbar = new Class({
+    Family: 'Jx.Progressbar',
+    Extends: Jx.Widget,
+    
+    options: {
+        onUpdate: $empty,
+        onComplete: $empty,
+        /**
+         * Option: parent
+         * The element to put this progressbar into
+         */
+        parent: null,
+        /**
+         * Option: progressText
+         * Text to show while processing, uses 
+         * {progress} von {total}
+         */
+        progressText : null,
+        /**
+         * Option: template
+         * The template used to create the progressbar
+         */
+        template: '<div class="jxProgressBar-container"><div class="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div></div>'
+    },
+    /**
+     * Property: classes
+     * The classes used in the template
+     */
+    classes: new Hash({
+        domObj: 'jxProgressBar-container',
+        message: 'jxProgressBar-message', 
+        container: 'jxProgressBar',
+        outline: 'jxProgressBar-outline',
+        fill: 'jxProgressBar-fill',
+        text: '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.parent();
+        
+        if ($defined(this.options.parent)) {
+            this.domObj.inject(document.id(this.options.parent));
+        }
+        
+        this.domObj.addClass('jxProgressStarting');
+
+        //we need to know the width of the bar
+        this.width = document.id(this.domObj).getContentBoxSize().width;
+        
+        //Message
+        if (this.message) {
+            if ($defined(MooTools.lang.get('Jx','progressbar').messageText)) {
+                this.message.set('html', this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+            } else {
+                this.message.destroy();
+            }
+        }
+
+        //Fill
+        if (this.fill) {
+            this.fill.setStyles({
+                'width': 0
+            });
+        }
+        
+        //TODO: check for {progress} and {total} in progressText
+        var obj = {};
+        var progressText = this.options.progressText == null ? 
+                              this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                              this.getText(this.options.progressText);
+        if (progressText.contains('{progress}')) {
+            obj.progress = 0;
+        }
+        if (progressText.contains('{total}')) {
+            obj.total = 0;
+        }
+        
+        //Progress text
+        if (this.text) {
+            this.text.set('html', 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) {
+    	
+    	//check for starting class
+    	if (this.domObj.hasClass('jxProgressStarting')) {
+    		this.domObj.removeClass('jxProgressStarting').addClass('jxProgressWorking');
+    	}
+    	
+        var newWidth = (progress * this.width) / total;
+        
+        //update bar width
+        this.text.get('tween', {property:'width', onComplete: function() {
+            var obj = {};
+            var progressText = this.options.progressText == null ?
+                                  this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                                  this.getText(this.options.progressText);
+            if (progressText.contains('{progress}')) {
+                obj.progress = progress;
+            }
+            if (progressText.contains('{total}')) {
+                obj.total = total;
+            }
+            var t = progressText.substitute(obj);
+            this.text.set('text', t);
+        }.bind(this)}).start(newWidth);
+        
+        this.fill.get('tween', {property: 'width', onComplete: (function () {
+            
+            if (total === progress) {
+                this.complete = true;
+                this.domObj.removeClass('jxProgressWorking').addClass('jxProgressFinished');
+                this.fireEvent('complete');
+            } else {
+                this.fireEvent('update');
+            }
+        }).bind(this)}).start(newWidth);
+        
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.message) {
+    		this.message.set('html',this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+    	}
+        //progress text will update on next update.
+    }
+    
+});// $Id: upload.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Panel.FileUpload
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This class extends Jx.Panel to provide a consistent interface for uploading
+ * files in an application.
+ * 
+ * MooTools.lang Keys:
+ * - upload.buttonText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.FileUpload = new Class({
+
+    Family: 'Jx.Panel.FileUpload',
+    Extends: Jx.Panel,
+    Binds: ['moveToQueue','fileUploadBegin', 'fileUploadComplete','allUploadsComplete', 'fileUploadProgressError,', 'fileUploadError', 'fileUploadProgress'],
+
+    options: {
+        /**
+         * Option: file
+         * An object containing the options for Jx.Field.File
+         */
+        file: {
+            autoUpload: false,
+            progress: false,
+            progressIDUrl: '',
+            handlerUrl: '',
+            progressUrl: ''
+        },
+
+        progressOptions: {
+            template: "<li class='jxListItemContainer jxProgressBar-container' id='{id}'><div class='jxProgressBar'><div class='jxProgressBar-outline'></div><div class='jxProgressBar-fill'></div><div class='jxProgressBar-text'></div></div></li>",
+            containerClass: 'progress-container',
+            messageText: null,
+            messageClass: 'progress-message',
+            progressText: 'Uploading {filename}',
+            progressClass: 'progress-bar'
+        },
+        /**
+         * Option: onFileComplete
+         * 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: 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: [],
+
+    listTemplate: "<li class='jxListItemContainer' id='{id}'><a class='jxListItem' href='javascript:void(0);'><span class='itemLabel jxUploadFileName'>{name}</span><span class='jxUploadFileDelete' title='Remove this file from the queue.'></span></a></li>",
+    /**
+     * Method: 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="jxInputContainer jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';
+
+        this.file = new Jx.Field.File(this.fileOpt);
+        this.file.addEvent('fileSelected', this.moveToQueue);
+        this.file.addTo(this.domObjA);
+
+        this.listView = new Jx.ListView({
+            template: '<ul class="jxListView jxList jxUploadQueue"></ul>'
+            
+        }).addTo(this.domObjA);
+
+        //this is the upload button at the bottom of the panel.
+        this.uploadBtn = new Jx.Button({
+            label : this.getText({set:'Jx',key:'upload',value:'buttonText'}),
+            onClick: this.upload.bind(this)
+        });
+        var tlb = new Jx.Toolbar({position: 'bottom', scroll: false}).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 (filename) {
+        var theTemplate = new String(this.listTemplate).substitute({
+            name: filename,
+            id: filename
+        });
+        var item = new Jx.ListItem({template:theTemplate, enabled: true});
+
+        $(item).getElement('.jxUploadFileDelete').addEvent('click', function(){
+            this.listView.remove(item);
+            this.file.remove(filename);
+            if (this.listView.list.count() == 0) {
+                this.uploadBtn.setEnabled(false);
+            }
+        }.bind(this));
+        this.listView.add(item);
+
+        if (!this.uploadBtn.isEnabled()) {
+            this.uploadBtn.setEnabled(true);
+        }
+
+    },
+    /**
+     * Method: upload
+     * Called when the user clicks the upload button. Runs the upload process.
+     */
+    upload: function () {
+
+        this.file.addEvents({
+            'fileUploadBegin': this.fileUploadBegin ,
+            'fileUploadComplete': this.fileUploadComplete,
+            'allUploadsComplete': this.allUploadsComplete,
+            'fileUploadError': this.fileUploadError,
+            'fileUploadProgress': this.fileUploadProgress,
+            'fileUploadProgressError': this.fileUploadError
+        });
+
+
+        this.file.upload();
+    },
+
+    fileUploadBegin: function (filename) {
+        if (this.options.file.progress) {
+            //progressbar
+            //setup options
+            // TODO: should (at least some of) these options be available to
+            // the developer?
+            var options = $merge({},this.options.progressOptions);
+            options.progressText = options.progressText.substitute({filename: filename});
+            options.template = options.template.substitute({id: filename});
+            this.pb = new Jx.Progressbar(options);
+            var item = document.id(filename);
+            this.oldContents = item;
+            this.listView.replace(item,$(this.pb));
+        } else {
+            var icon = document.id(filename).getElement('.jxUploadFileDelete')
+            icon.addClass('jxUploadFileProgress').set('title','File Uploading...');
+        }
+    },
+
+    /**
+     * Method: fileUploadComplete
+     * Called when a single file is uploaded completely .
+     *
+     * Parameters:
+     * data - the data returned from the event
+     * filename - the filename of 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, filename) {
+
+        if (this.options.file.progress) {
+            //show this old contents...
+            this.listView.replace(document.id(filename),this.oldContents);
+        }
+        var icon = document.id(filename).getElement('.jxUploadFileDelete');
+        icon.erase('title');
+        if (icon.hasClass('jxUploadFileProgress')) {
+            icon.removeClass('jxUploadFileProgress').addClass('jxUploadFileError');
+        } else {
+            icon.addClass('jxUploadFileError');
+        }
+        if ($defined(data.error) && $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 (filename) {
+
+        if (this.options.removeOnComplete) {
+           this.listView.remove(document.id(filename));
+        } else {
+            if (this.options.file.progress) {
+                this.listView.replace(document.id(filename),this.oldContents);
+            }
+            var l = document.id(filename).getElement('.jxUploadFileDelete');
+            if (l.hasClass('jxUploadFileDelete')) {
+                l.addClass('jxUploadFileComplete');
+            } else if (l.hasClass('jxUploadFileProgress')) {
+                l.removeClass('jxUploadFileProgress').addClass('jxUploadFileComplete');
+            }
+        }
+
+        this.fireEvent('fileUploadComplete', filename);
+    },
+    /**
+     * 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) {
+        if (this.options.progress) {
+            this.pb.update(data.total, data.current);
+        }
+    },
+    /**
+     * Method: allUploadCompleted
+     * Called when the Jx.Field.File completes uploading
+     * all files. Sets upload button to disabled and fires the allUploadCompleted
+     * event.
+     */
+    allUploadsComplete: function () {
+        this.uploadBtn.setEnabled(false);
+        this.fireEvent('allUploadsCompleted',this);
+    },
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.uploadBtn)) {
+        this.uploadBtn.setLabel({set:'Jx',key:'upload',value:'buttonText'});
+      }
+    }
+});
+// $Id: column.js 920 2010-05-25 18:39:55Z conrad.barthelmes $
+/**
+ * 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({
+
+	Family: 'Jx.Column',
+    Extends: Jx.Widget,
+
+    options: {
+        /**
+         * Option: renderMode
+         * The mode to use in rendering this column to determine its width. 
+         * Valid options include
+         * 
+         * fit - auto calculates the width for the best fit to the text. This 
+         * 			is the default.
+         * fixed - uses the value passed in the width option, doesn't 
+         * 			auto calculate.
+         * expand - uses the value in the width option as a minimum width and 
+         * 			allows this column to expand and take up any leftover space. 
+         * 			NOTE: there can be only 1 expand column in a grid. The 
+         * 			Jx.Columns object will only take the first column with this 
+         * 			option as the expanding column. All remaining columns marked 
+         * 			"expand" will be treated as "fixed".
+         */
+        renderMode: 'fit',
+        /**
+         * Option: width
+         * Determines the width of the column when using 'fixed' rendering mode
+         * or acts as a minimum width when using 'expand' mode.
+         */
+        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: name
+         * The name given to this column
+         */
+        name: '',
+        /**
+         * Option: template
+         */
+        template: null,
+        /**
+         * Option: renderer
+         * an instance of a Jx.Grid.Renderer to use in rendering the content
+         * of this column or a config object for creating one like so:
+         *
+         * (code)
+         * {
+         *     name: 'Text',
+         *     options: { ... renderer options ... }
+         * }
+         */
+        renderer: null
+    },
+    
+    classes: $H({
+    	domObj: 'jxGridCellContent'
+    }),
+    /**
+     * 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.name = this.options.name;
+
+        //adjust header for column
+        if (!$defined(this.options.template)) {
+            this.options.template = '<span class="jxGridCellContent">' + this.name.capitalize() + '</span>';
+        }
+
+        this.parent();
+        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
+            this.grid = this.options.grid;
+        }
+
+        //check renderer
+        if (!$defined(this.options.renderer)) {
+            //set a default renderer
+            this.options.renderer = new Jx.Grid.Renderer.Text({
+                textTemplate: '{' + this.name + '}'
+            });
+        } else {
+            if (!(this.options.renderer instanceof Jx.Grid.Renderer)) {
+                var t = Jx.type(this.options.renderer);
+                if (t === 'object') {
+                    if(!$defined(this.options.renderer.options.textTemplate)) {
+                      this.options.renderer.options.textTemplate = '{' + this.name + '}';
+                    }
+                    if(!$defined(this.options.renderer.name)) {
+                      this.options.renderer.name = 'Text';
+                    }
+                    this.options.renderer = new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](
+                            this.options.renderer.options);
+                }
+            }
+        }
+
+        this.options.renderer.setColumn(this);
+
+        this.sortImg = new Element('img', {
+            src: Jx.aPixel.src
+        });
+
+    },
+    	
+    /**
+     * APIMethod: getHeaderHTML
+     */
+    getHeaderHTML : function () {
+    	if (this.isSortable()) {
+    		this.sortImg.inject(this.domObj);	
+    	}
+      return this.domObj;
+    },
+
+    setWidth: function(newWidth, asCellWidth) {
+        asCellWidth = $defined(asCellWidth) ? asCellWidth : false;
+
+        var delta = this.cellWidth - this.width;
+        if (!asCellWidth) {
+    	    this.width = parseInt(newWidth,10);
+    	    this.cellWidth = this.width + delta;
+    	    this.options.width = newWidth;
+        } else {
+            this.width = parseInt(newWidth,10) - delta;
+            this.cellWidth = newWidth;
+            this.options.width = this.width;
+        }
+      if (this.rule && parseInt(this.width,10) >= 0) {
+          this.rule.style.width = parseInt(this.width,10) + "px";
+      }
+      if (this.cellRule && parseInt(this.cellWidth,10) >= 0) {
+          this.cellRule.style.width = parseInt(this.cellWidth,10) + "px";
+      }
+    },
+    
+    getWidth: function () {
+    	return this.width;
+    },
+    
+    getCellWidth: function() {
+      return this.cellWidth;
+    },
+    
+    /**
+     * APIMethod: calculateWidth
+     * returns the width of the column.
+     *
+     * Parameters:
+     * rowHeader - flag to tell us if this calculation is for the row header
+     */
+    calculateWidth : function (rowHeader) {
+        //if this gets called then we assume that we want to calculate the width
+    	rowHeader = $defined(rowHeader) ? rowHeader : false;
+        var maxWidth;
+        var maxCellWidth;
+        
+        var model = this.grid.getModel();
+        model.first();
+        if ((this.options.renderMode == 'fixed' || 
+             this.options.renderMode == 'expand') && 
+            model.valid) {
+          var t = new Element('span', {
+            'class': 'jxGridCellContent',
+            html: 'a',
+            styles: {
+              width: this.options.width
+            }
+          });
+          var s = this.measure(t,'jxGridCell');
+          maxWidth = s.content.width;
+          maxCellWidth = s.cell.width;
+        } else {
+            //calculate the width
+            var oldPos = model.getPosition();
+            maxWidth = maxCellWidth = 0;
+            while (model.valid()) {
+                //check size by placing text into a TD and measuring it.
+                this.options.renderer.render();
+                var text = document.id(this.options.renderer);
+                var klass = 'jxGridCell';
+                if (this.grid.row.useHeaders()
+                        && this.options.name === this.grid.row
+                        .getRowHeaderColumn()) {
+                    klass = 'jxGridRowHead';
+                }
+                var s = this.measure(text, klass, rowHeader, model.getPosition());
+                if (s.content.width > maxWidth) {
+                    maxWidth = s.content.width;
+                }
+                if (s.cell.width > maxCellWidth) {
+                  maxCellWidth = s.cell.width
+                }
+                if (model.hasNext()) {
+                    model.next();
+                } else {
+                    break;
+                }
+            }
+
+            //check the column header as well (unless this is the row header)
+            if (!(this.grid.row.useHeaders() && 
+                this.options.name === this.grid.row.getRowHeaderColumn())) {
+                klass = 'jxGridColHead';
+                if (this.isEditable()) {
+                    klass += ' jxColEditable';
+                }
+                if (this.isResizable()) {
+                    klass += ' jxColResizable';
+                }
+                if (this.isSortable()) {
+                    klass += ' jxColSortable';
+                }
+                s = this.measure(this.domObj.clone(), klass);
+                if (s.content.width > maxWidth) {
+                    maxWidth = s.content.width;
+                }
+                if (s.cell.width > maxCellWidth) {
+                  maxCellWidth = s.cell.width
+                }
+            }
+        }
+        
+        this.width = maxWidth;
+        this.cellWidth = maxCellWidth;
+        model.moveTo(oldPos);
+        return this.width;
+    },
+    /**
+     * 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.
+     * rowHeader - 
+     * row - 
+     */
+    measure : function (text, klass, rowHeader, row) {
+        var d = new Element('span', {
+            'class' : klass
+        });
+        text.inject(d);
+        //d.setStyle('height', this.grid.row.getHeight(row));
+        d.setStyles({
+            'visibility' : 'hidden',
+            'width' : 'auto'
+        });
+        
+        d.inject(document.body, 'bottom');
+        var s = d.measure(function () {
+            //if not rowHeader, get size of innner span
+            if (!rowHeader) {
+                return {
+                  content: this.getFirst().getContentBoxSize(),
+                  cell: this.getFirst().getMarginBoxSize()
+                }
+            } else {
+                return {
+                  content: this.getMarginBoxSize(),
+                  cell: 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: isAttached
+     * returns whether this column is attached to a store.
+     */
+    isAttached: function () {
+        return this.options.renderer.attached;
+    },
+
+    /**
+     * APIMethod: getHTML
+     * calls render method of the renderer object passed in.
+     */
+    getHTML : function () {
+        this.options.renderer.render();
+        return document.id(this.options.renderer);
+    }
+
+});// $Id: columns.js 886 2010-04-29 23:07:02Z jonlb at comcast.net $
+/**
+ * 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({
+
+  Family: 'Jx.Columns',
+    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'],
+    /**
+     * Property: hasExpandable
+     * 
+     */
+    hasExpandable: null,
+
+    /**
+     * 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.hasExpandable = false;
+
+        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") {
+                this.columns.push(new Jx.Column(col,this.grid));
+            }
+            var c = this.columns[this.columns.length - 1 ];
+            if (c.options.renderMode === 'expand') {
+                this.hasExpandable = true;
+            }
+
+        }, 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
+        var cell = headers[index];
+          var hClasses = cell.get('class').split(' ').filter(function (cls) {
+            if(this.options.useHeaders)
+              return cls.test('jxColHead-')
+            else
+              return cls.test('jxCol-');
+          }.bind(this));
+        var parts = hClasses[0].split('-');
+        return this.getByName(parts[1]);
+    },
+
+    /**
+     * APIMethod: getHeaders
+     * Returns a row with the headers in it.
+     *
+     * Parameters:
+     * row - the row to add the headers to.
+     */
+    getHeaders : function (list) {
+        var r = this.grid.row.useHeaders();
+        var hf = this.grid.row.getRowHeaderColumn();
+        this.columns.each(function (col, idx) {
+            if (r && hf === col.options.name) {
+                //do nothing
+            } else if (!col.isHidden()) {
+                var th = new Element('th', {
+                    'class' : 'jxGridColHead jxGridCol'+idx
+                });
+                th.adopt(col.getHeaderHTML());
+                // th.setStyle('width', col.getWidth());
+                th.addClass('jxColHead-' + col.name);
+                //add other styles for different attributes
+                if (col.isEditable()) {
+                    th.addClass('jxColEditable');
+                }
+                if (col.isResizable()) {
+                    th.addClass('jxColResizable');
+                }
+                if (col.isSortable()) {
+                    th.addClass('jxColSortable');
+                }
+                list.add(th);
+                th.store('jxCellData', {
+                   column: col,
+                   colHeader: true,
+                   index: idx
+                });
+            }
+        }, this);
+        return list;
+    },
+    /**
+     * APIMethod: getColumnCells
+     * Appends the cells from each column for a specific row
+     *
+     * Parameters:
+     * list - the Jx.List instance to add the cells to.
+     */
+    getColumnCells : function (list) {
+        var r = this.grid.row;
+        var f = r.getRowHeaderColumn();
+        var h = r.useHeaders();
+        this.columns.each(function (col, idx) {
+            if (h && col.name !== f && !col.isHidden()) {
+                list.add(this.getColumnCell(col, idx));
+            } else if (!h && !col.isHidden()) {
+                list.add(this.getColumnCell(col, idx));
+            }
+        }, this);
+        if (!this.hasExpandable) {
+            list.add(new Element('td',{
+                'class': 'jxGridCellUnattached'
+            }));
+        }
+    },
+    /**
+     * 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.name);
+        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');
+        }
+        if (!col.isAttached()) {
+            td.addClass('jxGridCellUnattached');
+        }
+
+        td.store('jxCellData',{
+            col: col,
+            index: idx, //This is the position of the column
+            row: this.grid.model.getPosition()
+        });
+
+        return td;
+    },
+    
+    calculateWidths: function () {
+      //to calculate widths we loop through each column
+      var expand = null;
+      var totalWidth = 0;
+      var rowHeaderWidth = 0;
+      this.columns.each(function(col,idx){
+        //are we checking the rowheader?
+        var rowHeader = false;
+        if (col.name == this.grid.row.options.headerColumn) {
+          rowHeader = true;
+        }
+        //if it's fixed, set the width to the passed in width
+        if (col.options.renderMode == 'fixed') {
+          col.calculateWidth(); //col.setWidth(col.options.width);
+          
+        } else if (col.options.renderMode == 'fit') {
+          col.calculateWidth(rowHeader);
+        } else if (col.options.renderMode == 'expand' && !$defined(expand)) {
+          expand = col;
+        } else {
+          //treat it as fixed if has width, otherwise as fit
+          if ($defined(col.options.width)) {
+            col.setWidth(col.options.width);
+          } else {
+            col.calculateWidth(rowHeader);
+          }
+        }
+        if (!col.isHidden() && !(col.name == this.grid.row.options.headerColumn)) {
+            totalWidth += Jx.getNumber(col.getCellWidth());
+            if (rowHeader) {
+                rowHeaderWidth = col.getWidth();
+            }
+        }
+      },this);
+      
+      // width of the container
+      //var containerWidth = this.grid.gridObj.getContentBoxSize();
+      var gridSize = this.grid.gridObj.getContentBoxSize();
+      if (gridSize.width > totalWidth) {
+        //now figure the expand column
+        if ($defined(expand)) {
+          // var leftOverSpace = gridSize.width - totalWidth + rowHeaderWidth;
+          var leftOverSpace = gridSize.width - totalWidth
+          //account for right borders in firefox...
+          if (Browser.Engine.gecko) {
+            leftOverSpace -= this.getColumnCount(true);
+          } else {
+            // -2 is for the right hand border on the cell and the table for all other browsers
+            leftOverSpace -= 2;
+          }
+          if (leftOverSpace >= expand.options.width) {
+            //in order for this to be set properly the cellWidth must be the
+            //leftover space. we need to figure out the delta value and
+            //subtract it from the leftover width
+            expand.options.width = leftOverSpace;
+            expand.calculateWidth();
+            expand.setWidth(leftOverSpace, true);
+            totalWidth += leftOverSpace;
+          } else {
+            expand.setWidth(expand.options.width);
+          }
+        }
+      } //else {
+      this.grid.gridTable.setContentBoxSize({'width': totalWidth});
+      this.grid.colTable.setContentBoxSize({'width': totalWidth});
+      // }
+    },
+
+    createRules: function(styleSheet, scope) {
+        this.columns.each(function(col, idx) {
+            var selector = scope+' .jxGridCol'+idx
+            var dec = '';
+            if (col.options.renderMode === 'fixed' || col.options.renderMode === 'expand') {
+              //set the white-space to 'normal !important'
+              dec = 'white-space: normal !important';
+            }
+            col.cellRule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+            col.cellRule.style.width = col.getCellWidth() + "px";
+
+            var selector = scope+" .jxGridCol" + idx + " .jxGridCellContent";
+            col.rule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+            col.rule.style.width = col.getWidth() + "px";
+
+        }, this);
+    },
+
+    updateRule: function(column) {
+        var col = this.getByName(column);
+        if (col.options.renderMode === 'fit') {
+          col.calculateWidth();
+        }
+        col.rule.style.width = col.getWidth() + "px";
+        col.cellRule.style.width = col.getCellWidth() + "px";
+    },
+    
+    /**
+     * APIMethod: getColumnCount
+     * returns the number of columns in this model (including hidden).
+     */
+    getColumnCount : function (noHidden) {
+        noHidden = $defined(noHidden) ? false : true;
+        var total = this.columns.length;
+        if (noHidden) {
+            this.columns.each(function(col){
+                if (col.isHidden()) {
+                    total -= 1;
+                }
+            },this);
+        }
+        return total;
+    },
+    /**
+     * 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
+        var c;
+        var i = -1;
+        var self = this;
+        headers.each(function (h) {
+            i++;
+            var hClasses = h.get('class').split(' ').filter(function (cls) {
+                if(self.options.useHeaders)
+                  return cls.test('jxColHead-');
+                else
+                  return cls.test('jxCol-');
+            });
+            hClasses.each(function (cls) {
+                if (cls.test(name)) {
+                    c = i;
+                }
+            });
+        }, this);
+        return c;
+    }
+
+});
+// $Id: row.js 887 2010-04-30 12:46:38Z pagameba $
+/**
+ * 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({
+
+	Family: 'Jx.Row',
+    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: headerColumn
+         * The name of the column in the model to use as the header
+         */
+        headerColumn : 'id'
+    },
+    /**
+     * Property: grid
+     * A reference to the grid that this row model belongs to
+     */
+    grid : null,
+    /**
+     * Property: heights
+     * This will hold the calculated height of each row in the grid.
+     */
+    heights: [],
+    /**
+     * Property: rules
+     * A hash that will hold all of the CSS rules for the rows.
+     */
+    rules: $H(),
+
+    parameters: ['options','grid'],
+
+    /**
+     * 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 (row) {
+
+        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;
+        }
+        tr.store('jxRowData', {row: row});
+        tr.addClass('jxGridRow'+row);
+        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('th', {
+            'class' : 'jxGridRowHead'
+        });
+        var col = this.grid.columns.getByName(this.options.headerColumn);
+        var el = col.getHTML();
+        el.inject(th);
+        th.store('jxCellData',{
+        	row: model.getPosition(),
+        	rowHeader: true
+        });
+        return th;
+
+    },
+    /**
+     * 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.getByName(this.options.headerColumn);
+    	if (!$defined(col.getWidth())) {
+    		col.calculateWidth(true);
+    	}
+        return col.getWidth();
+    },
+
+    /**
+     * APIMethod: getHeight
+     * determines and returns the height of a row
+     */
+    getHeight : function (row) {
+        //this should eventually compute a height, however, we would need
+        //a fixed width to do so reliably. For right now, we use a fixed height
+        //for all rows.
+    	if ((!$defined(this.options.rowHeight) || this.options.rowHeight === 'auto') && $defined(this.heights[row])) {
+    		return this.heights[row];
+    	} else if (Jx.type(this.options.rowHeight === 'number')) {
+    		return this.options.rowHeight;
+    	}
+    },
+    calculateHeights : function () {
+    	//grab all rows in the grid body
+      document.id(this.grid.gridTableBody).getChildren().each(function(row){
+        row = document.id(row);
+        var data = row.retrieve('jxRowData');
+        var s = row.getContentBoxSize();
+        this.heights[data.row] = s.height;
+      },this);
+      document.id(this.grid.rowTableHead).getChildren().each(function(row){
+        row = document.id(row);
+        var data = row.retrieve('jxRowData');
+        if (data) {
+          var s = row.getContentBoxSize();
+          this.heights[data.row] = Math.max(this.heights[data.row],s.height);
+          if (Browser.Engine.webkit) {
+              //for some reason webkit (Safari and Chrome)
+              this.heights[data.row] -= 1;
+          }
+        }
+      },this);
+    	
+    },
+    
+    createRules: function(styleSheet, scope) {
+        this.grid.gridTableBody.getChildren().each(function(row, idx) {
+            var selector = scope+' .jxGridRow'+idx + ', ' + scope + ' .jxGridRow'+idx+' .jxGridCellContent';
+            var rule = Jx.Styles.insertCssRule(selector, '', styleSheet);
+            this.rules.set('jxGridRow'+idx, rule);
+            rule.style.height = this.heights[idx] + "px";
+
+            if (Browser.Engine.webkit) {
+                selector += " th";
+                var thRule = Jx.Styles.insertCssRule(selector, '', styleSheet);
+                thRule.style.height = this.heights[idx] + "px";
+                
+                this.rules.set('th_jxGridRow'+idx, thRule);
+            }
+
+        }, this);
+    },
+    
+    updateRules: function() {
+      this.grid.gridTableBody.getChildren().each(function(row, idx) {
+        var h = this.heights[idx] + "px";
+        this.rules.get('jxGridRow'+idx).style.height = h;
+        if (Browser.Engine.webkit) {
+          this.rules.get('th_jxGridRow'+idx).style.height = h;
+        }
+      }, this);
+    },
+    
+    /**
+     * 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
+     *
+     * Parameters:
+     * list - Jx.List instance to add the header to
+     */
+    getRowHeader : function (list) {
+        var th = this.getRowHeaderCell();
+        //if (this.grid.model.getPosition() === 0) {
+        //    var rowWidth = this.getRowHeaderWidth();
+        //    th.setStyle("width", rowWidth);
+        //}
+        th.store('jxCellData', {
+            rowHeader: true,
+            row: this.grid.model.getPosition()
+        });
+        list.add(th);
+    },
+    /**
+     * APIMethod: getRowHeaderColumn
+     * returns the name of the column that is used for the row header
+     */
+    getRowHeaderColumn : function () {
+        return this.options.headerColumn;
+    }
+});
+// $Id: plugin.js 822 2010-03-31 11:28:31Z conrad.barthelmes $
+/**
+ * Class: Jx.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({
+
+    Family: "Jx.Plugin",
+
+    Extends: Jx.Object,
+
+    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);
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    }
+});// $Id: plugin.grid.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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 897 2010-05-07 20:03:33Z conrad.barthelmes $
+/**
+ * Class: Jx.Grid
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A tabular control that has fixed, optional, 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
+ * 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. 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 be a Jx.Store or extended from it.
+ *
+ * Events:
+ * gridCellEnter(cell, list) - called when the mouse enters a cell
+ * gridCellLeave(cell, list) - called when the mouse leaves a cell
+ * gridCellSelect(cell) - called when a cell is clicked
+ * gridMouseLeave() - called when the mouse leaves the grid at any point.
+ *
+ *
+ * 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',
+    Extends : Jx.Widget,
+
+    Binds: ['modelChanged','render','addRow','removeRow','removeRows',
+            'onSelect', 'onUnselect','onMouseEnter','onMouseLeave'],
+    
+    options : {
+        /**
+         * Option: parent
+         * the HTML element to create the grid inside. The grid will resize
+         * to fill the domObj.
+         */
+        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.
+         */
+        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
+         */
+        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: styleSheet
+     * the name of the dynamic style sheet to use for manipulating styles
+     */
+    styleSheet: 'JxGridStyles',
+    /**
+     * Property: pluginNamespace
+     * the required variable for plugins
+     */
+    pluginNamespace: 'Grid',
+    /**
+     * Property: selection
+     * holds the Jx.Selection instance used by the cell lists
+     */
+    selection: null,
+    /**
+     * Property: lists
+     * An array of Jx.List instances, one per row. All of them use the same
+     * Jx.Selection instance
+     */
+    lists: [],
+
+    /**
+     * Constructor: Jx.Grid
+     */
+    init : function () {
+        this.uniqueId = this.generateId('jxGrid_');
+        
+        var opts;
+        if ($defined(this.options.model)
+                && this.options.model instanceof Jx.Store) {
+            this.model = this.options.model;
+            this.model.addEvent('storeColumnChanged', this.modelChanged);
+            this.model.addEvent('storeSortFinished', this.render);
+            this.model.addEvent('storeRecordAdded', this.addRow);
+            this.model.addEvent('storeRecordRemoved', this.removeRow);
+            this.model.addEvent('storeMultipleRecordsRemoved', this.removeRows);
+        }
+
+        if ($defined(this.options.columns)) {
+            if (this.options.columns instanceof Jx.Columns) {
+                this.columns = this.options.columns;
+            } else if (Jx.type(this.options.columns) === 'object') {
+                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") {
+                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)
+        });
+        
+        //we need to know if the mouse leaves the grid so we can turn off prelighters and the such
+        this.domObj.addEvent('mouseleave',function(){
+            this.fireEvent('gridMouseLeave');
+        }.bind(this));
+
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+
+        //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('thead');
+        this.colTable.appendChild(this.colTableBody);
+        this.colObj.appendChild(this.colTable);
+
+        //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);
+
+        //The actual body of the grid
+        this.gridObj = new Element('div', {
+            'class' : 'jxGridContainer',
+            styles : {
+                overflow : 'auto'
+            }
+        });
+        this.gridTable = new Element('table', {
+            'class' : 'jxGridTable jxGridContent'
+        });
+        this.gridTableBody = new Element('tbody');
+        this.gridTable.appendChild(this.gridTableBody);
+        this.gridObj.appendChild(this.gridTable);
+
+        var target = this;
+
+        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));
+
+        //setup the selection
+        this.selection = new Jx.Selection();
+        this.selection.addEvents({
+            select: this.onSelect,
+            unselect: this.onUnselect
+        });
+        this.parent();
+
+        this.domObj.store('grid', this);
+    },
+
+    /**
+     * Method: onScroll
+     * handle the grid scrolling by updating the position of the headers
+     */
+    onScroll : function () {
+        this.colObj.scrollLeft = this.gridObj.scrollLeft;
+        this.rowObj.scrollTop = this.gridObj.scrollTop;
+    },
+
+
+    /**
+     * 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 () {
+        if (!this.model) {
+            return;
+        }
+
+        var colHeight = this.columns.useHeaders() ? this.columns
+                .getHeaderHeight() : 1;
+        var rowWidth = this.row.useHeaders() ? this.row
+                .getRowHeaderWidth() : 1;
+
+        var size = this.domObj.getContentBoxSize();
+
+
+        
+        /* -1 because of the right/bottom borders */
+        this.rowColObj.setStyles({
+            width : rowWidth - 1,
+            height : colHeight - 1
+        });
+        this.rowObj.setStyles({
+            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
+        });
+
+        this.gridObj.setStyles({
+            top : colHeight,
+            left : rowWidth,
+            width : size.width - rowWidth - 1,
+            height : size.height - colHeight - 1
+        });
+
+    },
+
+    resizeRowsCols: function (mode) {
+        mode = $defined(mode) ? mode : 'all';
+
+        if (mode === 'all' || mode === 'columns') {
+            Jx.Styles.removeStyleSheet(this.styleSheet + "Columns");
+            Jx.Styles.enableStyleSheet(this.styleSheet + "Columns");
+            this.columns.calculateWidths();
+            this.columns.createRules(this.styleSheet + "Columns", "."+this.uniqueId);
+        }
+        
+        if (mode === 'all' || mode === 'rows') {
+            Jx.Styles.removeStyleSheet(this.styleSheet + "Rows");
+            Jx.Styles.enableStyleSheet(this.styleSheet + "Rows");
+            this.row.calculateHeights();
+            this.row.createRules(this.styleSheet + "Rows", "."+this.uniqueId);
+        }
+
+    },
+
+    /**
+     * 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. However, It needs to have
+     * the same columns
+     *
+     * Parameters:
+     * model - {Object} the model to use for this grid
+     */
+    setModel : function (model) {
+        this.model = model;
+        if (this.model) {
+            this.render();
+            this.domObj.resize();
+        } else {
+            this.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.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;
+
+        document.id(this.rowColObj).empty();
+        
+        if (Jx.Styles.isStyleSheetDefined(this.styleSheet)) {
+        	Jx.Styles.removeStyleSheet(this.styleSheet);
+        }
+
+    },
+
+    /**
+     * APIMethod: render
+     * Create the grid for the current model
+     */
+    render : function () {
+        this.destroyGrid();
+
+        this.fireEvent('beginCreateGrid', this);
+
+        if (this.model && this.model.loaded) {
+            var model = this.model;
+            var nColumns = this.columns.getColumnCount();
+            var nRows = model.count();
+            var th;
+
+            /* create header if necessary */
+            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 headerList = this.makeList(trBody);
+
+                this.columns.getHeaders(headerList);
+
+                /* one extra column at the end for filler */
+                th = new Element('th', {
+                    'class':'jxGridColHead',
+                    styles: {
+                      width: 1000,
+                      height: colHeight - 1
+                    }
+                }).inject(trBody);
+            } else {
+                //hide the headers
+                this.colTableBody.setStyle('visibility', 'hidden');
+            }
+
+            //This section actually adds the rows
+            this.model.first();
+            while (this.model.valid()) {
+                tr = this.row.getGridRowElement(this.model.getPosition());
+                var rl = this.makeList(tr);
+                this.gridTableBody.appendChild(tr);
+                //this.rowList.add(rl.container);
+
+                //Actually add the columns
+                this.columns.getColumnCells(rl);
+
+                if (this.model.hasNext()) {
+                    this.model.next();
+                } else {
+                    break;
+                }
+
+            }
+            
+            
+            //Moved rowheaders after other columns so we can figure the heights
+            //of each row (after render)
+            if (this.row.useHeaders()) {
+                this.rowTableHead.setStyle('visibility', 'visible');
+
+                //loop through all rows and add header
+                this.model.first();
+                while (this.model.valid()) {
+                    var tr = new Element('tr',{
+                    	'class': 'jxGridRow'+this.model.getPosition()
+                    });
+                    tr.store('jxRowData', {row:this.model.getPosition()});
+                    var rowHeaderList = this.makeList(tr);
+                    this.row.getRowHeader(rowHeaderList);
+                    this.rowTableHead.appendChild(tr);
+                    if (this.model.hasNext()) {
+                        this.model.next();
+                    } else {
+                        break;
+                    }
+                }
+                /* one extra row at the end for filler */
+                tr = new Element('tr').inject(this.rowTableHead);
+                th = new Element('th', {
+                    'class' : 'jxGridRowHead',
+                    styles : {
+                        width : this.row.getRowHeaderWidth(),
+                        height : 1000
+                    }
+                }).inject(tr);
+            } else {
+                //hide row headers
+                this.rowTableHead.setStyle('visibility', 'hidden');
+            }
+            
+            this.domObj.resize();
+            this.resizeRowsCols();
+            this.resize();
+
+            this.fireEvent('doneCreateGrid', this);
+        } else {
+            this.model.load();
+        }
+        
+    },
+
+    /**
+     * 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);
+        var td = document.id(this.gridObj.childNodes[0].childNodes[0].childNodes[row].childNodes[column]);
+
+        var currentRow = this.model.getPosition();
+        this.model.moveTo(row);
+        // need to find out whether the header is used or not, to have the right reference back
+        var colIndex = this.options.row.useHeaders ? column+1 : column;
+        var newTD = this.columns.getColumnCell(this.columns.getByName(col),colIndex);
+        //get parent list
+        var list = td.getParent().retrieve('jxList');
+        list.replace(td, newTD);
+        this.columns.updateRule(col);
+        this.model.moveTo(currentRow);
+    },
+    
+    /**
+     * APIMethod: addRow
+     * Adds a row to the table. Can add to either the beginning or the end 
+     * based on passed flag
+     */
+    addRow: function (store, record, position) {
+        if (this.model.loaded) {
+            if (position === 'bottom') {
+                this.model.last();
+            } else {
+                this.model.first();
+                this.renumberGrid(0, 1);
+            }
+            
+            //row header
+            if (this.row.useHeaders()) {
+                var rowHeight = this.row.getHeight();
+                var tr = new Element('tr', {
+                    styles : {
+                        height : rowHeight
+                    }
+                });
+                var rowHeaderList = this.makeList(tr);
+                this.row.getRowHeader(rowHeaderList);
+                if (position === 'top') {
+                    tr.inject(this.rowTableHead, position);
+                } else {
+                    var lastTr = this.rowTableHead.children[this.rowTableHead.children.length - 1];
+                    tr.inject(lastTr, 'before');
+                }
+            }
+            tr = this.row.getGridRowElement();
+            tr.store('jxRowData', {row: this.model.getPosition()});
+            var rl = this.makeList(tr);
+            this.columns.getColumnCells(rl);
+            tr.inject(this.gridTableBody, position);
+        }
+    },
+    
+    renumberGrid: function (offset, increment) {
+        var l = this.gridTable.rows.length;
+        for (var i = offset; i < l; i++) {
+            var r = document.id(this.gridTable.rows[i]);
+            var d = r.retrieve('jxRowData');
+            d.row += increment;
+            r.store('jxRowData', d);
+            $A(r.children).each(function(cell){
+                var d = cell.retrieve('jxCellData');
+                d.row += increment;
+                cell.store('jxCellData', d);
+            },this);
+        }
+    },
+    
+    removeRow: function (store, index) {
+        this.gridTable.deleteRow(index);
+        this.rowTable.deleteRow(index);
+        this.renumberGrid(index, -1);
+    },
+    
+    removeRows: function (store, first, last) {
+      for (var i = first; i <= last; i++) {
+          this.removeRow(first);
+      }
+    },
+    
+    /**
+     * Method: makeList
+     * utility method used to make row lists
+     *
+     * Parameters:
+     * container - the row to use as the Jx.List container
+     */
+    makeList: function (container) {
+        var l = new Jx.List(container, {
+            hover: true,
+            select: true
+        }, this.selection);
+        var target = this;
+        l.addEvents({
+            mouseenter: this.onMouseEnter,
+            mouseleave: this.onMouseLeave
+        });
+        this.lists.push(l);
+        return l;
+    },
+
+    onSelect: function (cell, select) {
+        this.fireEvent('gridCellSelect', [cell,select,this]);
+    },
+
+    onUnselect: function (cell, select) {
+        this.fireEvent('gridCellUnselect', [cell,select,this]);
+    },
+
+    onMouseEnter: function (cell, list) {
+        this.fireEvent('gridCellEnter', [cell,list,this]);
+    },
+
+    onMouseLeave: function (cell, list) {
+        this.fireEvent('gridCellLeave', [cell,list,this]);
+    },
+
+    changeText : function(lang) {
+        this.parent();
+        /*
+        this.resize();
+        this.resizeRowsCols();
+        */
+        this.render();
+    }
+
+});
+/**
+ * Class: Jx.Grid.Renderer
+ * This is the base class and namespace for all grid renderers.
+ * 
+ * Extends: <Jx.Widget>
+ * We extended Jx.Widget to take advantage of templating support.
+ */
+Jx.Grid.Renderer = new Class({
+  
+  Family: 'Jx.Grid.Renderer',
+  Extends: Jx.Widget,
+  
+  parameters: ['options'],
+  
+  options: {
+    deferRender: true,
+    /**
+     * Option: template
+     * The template for rendering this cell. Will be processed as per
+     * the Jx.Widget standard.
+     */
+    template: '<span class="jxGridCellContent"></span>'
+  },
+    /**
+     * APIProperty: attached
+     * tells whether this renderer is used in attached mode
+     * or not. Should be set by renderers that get a reference to
+     * the store.
+     */
+  attached: null,
+
+  classes: $H({
+    domObj: 'jxGridCellContent'
+  }),
+
+  column: null,
+
+  init: function () {
+    this.parent();
+    this.attached = false;
+  },
+  
+  render: function () {
+    this.parent();
+  },
+  
+  setColumn: function (column) {
+    if (column instanceof Jx.Column) {
+      this.column = column;
+    }
+  }
+  
+});/**
+ * Class: Jx.Grid.Renderer.Text
+ * This is the default renderer for grid cells. It works the same as the 
+ * original column implementation. It needs a store, a field name, and an 
+ * optional formatter as well as other options.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Text = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Text',
+  Extends: Jx.Grid.Renderer,
+  
+  options: {
+        /**
+         * Option: formatter
+         * an instance of <Jx.Formatter> or one of its subclasses which
+         * will be used to format the data in this column. It can also be
+         * an object containing the name (This should be the part after
+         * Jx.Formatter in the class name. For instance, to get a currency
+         * formatter, specify 'Currency' as the name.) and options for the
+         * needed formatter (see individual formatters for options).
+         * (code)
+         * {
+         *    name: 'formatter name',
+         *    options: {}
+         * }
+         * (end)
+         */
+        formatter: null,
+        /**
+         * Option: textTemplate
+         * Will be used for creating the text that goes iside the template. Use
+         * placeholders for indicating the field(s). You can add as much text 
+         * as you want. for example, if you wanted to display someone's full 
+         * name that is brokem up in the model with first and last names you 
+         * can write a template like '{lastName}, {firstName}' and as long as 
+         * the text between { and } are field names in the store they will be
+         * substituted properly.
+         */
+        textTemplate: null,
+        /**
+         * Option: css
+         * A string or function to use in adding classes to the text
+         */
+        css: null
+  },
+  
+  store: null,
+  
+  columnsNeeded: null,
+  
+  
+  init: function () {
+    this.parent();
+    //check the formatter
+    if ($defined(this.options.formatter)
+                && !(this.options.formatter instanceof Jx.Formatter)) {
+            var t = Jx.type(this.options.formatter);
+            if (t === 'object') {
+                // allow users to leave the options object blank
+                if(!$defined(this.options.formatter.options)) {
+                  this.options.formatter.options = {}
+                }
+                this.options.formatter = new Jx.Formatter[this.options.formatter.name](
+                        this.options.formatter.options);
+            }
+        }
+  },
+  
+  setColumn: function (column) {
+    this.parent();
+    
+    this.store = column.grid.getModel();
+    this.attached = true;
+    
+    if ($defined(this.options.textTemplate)) {
+      this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+    }
+  },
+  
+  render: function () {
+    this.parent();
+    
+    var text = '';
+    if ($defined(this.options.textTemplate)) {
+        if (!$defined(this.columnsNeeded) || (Jx.type(this.columnsNeeded) === 'array' && this.columnsNeeded.length === 0)) {
+            this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+        }
+        text = this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded);
+    } 
+    if ($defined(this.options.formatter)) {
+        text = this.options.formatter.format(text);
+    }
+
+    this.domObj.set('html',text);
+
+    if ($defined(this.options.css) && Jx.type(this.options.css) === 'function') {
+      this.domObj.addClass(this.options.css.run(text));
+    } else if ($defined(this.options.css) && Jx.type(this.options.css) === 'string'){
+      this.domObj.addClass(this.options.css);
+    }
+        
+  }
+
+});/**
+ * Class: Jx.Grid.Renderer.CheckBox
+ * Renders a checkbox into the cell. Allows options for connecting the cell
+ * to a model field and propogating changes back to the store.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Checkbox = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Checkbox',
+  Extends: Jx.Grid.Renderer,
+  
+  Binds: ['onBlur','onChange'],
+  
+  options: {
+    useStore: false,
+    field: null,
+    updateStore: false,
+    checkboxOptions: {
+      template : '<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',
+      name: ''
+    }
+  },
+  
+  init: function () {
+    this.parent();
+  },
+  
+  render: function () {
+    this.parent();
+    var checkbox = new Jx.Field.Checkbox(this.options.checkboxOptions);
+    this.domObj.adopt(document.id(checkbox));
+    
+    if (this.options.useStore) {
+      //set initial state
+      checkbox.setValue(this.store.get(this.options.field));
+    }
+    
+    //hook up change and blur events to change store field
+    checkbox.addEvents({
+      'blur': this.onBlur,
+      'change': this.onChange
+    });
+  },
+  
+  setColumn: function (column) {
+    this.column = column;
+    
+    if (this.options.useStore) {
+      this.store = this.column.grid.getModel();
+      this.attached = true;
+    }
+  },
+  
+  onBlur: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  onChange: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  updateStore: function (field) {
+    var newValue = field.getValue();
+    
+    var data = document.id(field).getParent().retrieve('jxCellData');
+    var row = data.row;
+    
+    if (this.store.get(this.options.field, row) !== newValue) {
+      this.store.set(this.options.field, newValue, row);
+    }
+  }
+  
+  
+});/**
+ * Class: Jx.Grid.Renderer.Button
+ * Renders a <Jx.Button> into the cell. You can add s many buttons as you'd like per column by passing button configs
+ * in as an array option to options.buttonOptions
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Button = new Class({
+
+    Family: 'Jx.Grid.Renderer.Button',
+    Extends: Jx.Grid.Renderer,
+
+    Binds: [],
+
+    options: {
+        template: '<span class="buttons"></span>',
+        /**
+         * Option: buttonOptions
+         * an array of option configurations for <Jx.Button>
+         */
+        buttonOptions: null
+    },
+
+    classes:  $H({
+        domObj: 'buttons'
+    }),
+
+    init: function () {
+        this.parent();
+    },
+
+    render: function () {
+        this.parent();
+
+        $A(this.options.buttonOptions).each(function(opts){
+            var button = new Jx.Button(opts);
+            this.domObj.grab(document.id(button));
+        },this);
+
+    }
+});// $Id: grid.selector.js 826 2010-03-31 18:46:16Z pagameba $
+/**
+ * Class: Jx.Plugin.Selector
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * 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({
+
+	Family: 'Jx.Plugin.Grid.Selector',
+    Extends : Jx.Plugin,
+    
+    Binds: ['select','checkSelection','checkAll','afterGridRender'],
+
+    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,
+        /**
+         * Option: multiple
+         * Allow multiple selections
+         */
+        multiple: false,
+        /**
+         * Option: useCheckColumn
+         * Whether to use a check box column as the row header or as the 
+         * first column in the grid and use it for manipulating selections.
+         */
+        useCheckColumn: false,
+        /**
+         * Option: checkAsHeader
+         * Determines if the check column is the header of the rows
+         */
+        checkAsHeader: false
+    },
+    /**
+     * Property: selected
+     * Holds arrays of selected rows and/or columns and their headers
+     */
+    selected: null,
+    
+    /**
+     * 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.selected = $H({
+            columns: [],
+            rows: [],
+            rowHeads: [],
+            columnHeads: []
+        });
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * Parameters:
+     * grid - The instance of Jx.Grid to attach to
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+            return;
+        }
+        this.grid = grid;
+        this.grid.addEvent('gridCellSelect', this.select);
+        if (this.options.cell) {
+            this.oldSelectionClass = this.grid.selection.options.selectedClass;
+            this.grid.selection.options.selectClass = "jxGridCellSelected";
+            if (this.options.multiple) {
+            	this.grid.selection.options.selectMode = 'multiple';
+            }
+        }
+        
+        //setup check column if needed
+        if (this.options.useCheckColumn) {
+        	
+        	var template = '<span class="jxGridCellContent">';
+        	
+        	if (this.options.multiple) {
+        		template += '<span class="jxInputContainer jxInputContainerCheck"><input class="jxInputCheck" type="checkbox" name="checkAll" id="checkAll"/></span>';
+        	} else {
+        		template += '</span>';
+        	}
+        	
+        	template += "</span>";
+        	
+        	this.checkColumn = new Jx.Column({
+        		template: template,
+        		renderMode: 'fit',
+        		renderer: new Jx.Grid.Renderer.Checkbox({
+        		//	onChange: this.checkSelection
+        		}),
+        		name: 'selection'
+        	}, this.grid);
+        	this.grid.columns.columns.reverse();
+        	this.grid.columns.columns.push(this.checkColumn);
+        	this.grid.columns.columns.reverse();
+        	
+        	if (this.options.checkAsHeader) {
+        		this.oldHeaderColumn = this.grid.row.options.headerColumn;
+        		this.grid.row.options.headerColumn = 'selection';
+        		
+        		if (this.options.multiple) {
+                    this.grid.addEvent('doneCreateGrid', this.afterGridRender);
+        		}
+        	}
+            //attach event to header
+            if (this.options.multiple) {
+                var ch = document.id(this.checkColumn).getElement('input');
+                ch.addEvents({
+                    'change': this.checkAll
+                });
+            }
+
+        }
+    },
+
+    afterGridRender: function () {
+        if (this.options.checkAsHeader) {
+            var chkCol = document.id(this.checkColumn).clone();
+            chkCol.getElement('input').addEvent('change',this.checkAll);
+            this.grid.rowColObj.adopt(chkCol);
+            //document.id(this.checkColumn).inject(this.grid.rowColObj);
+        }
+        this.grid.removeEvent('doneCreateGrid',this.afterGridRender);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridCellSelect', this.select);
+            if (this.options.cell) {
+                this.grid.selection.options.selectedClass = this.oldSelectionClass;
+            }
+        }
+        if (this.options.useCheckColumn) {
+        	var col = this.grid.columns.getByName('selection');
+        	this.grid.columns.columns.erase(col);
+        	if (this.options.checkAsHeader) {
+        		this.grid.row.options.headerColumn = this.oldHeaderColumn;
+        	}
+        }
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning selections on.
+     * 
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'column', or 'row'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+        if (opt === 'cell') {
+            this.oldSelectionClass = this.grid.selection.options.selectedClass;
+            this.grid.selection.options.selectClass = "jxGridCellSelected";
+        }
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning selections off.
+     * 
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'column', or 'row'
+     */
+    deactivate: function (opt) {
+        this.options[opt] = false;
+        if (opt === 'cell') {
+            this.grid.selection.selected().each(function(cell){
+                this.grid.selection.unselect(cell);
+            },this);
+            this.grid.selection.options.selectClass = this.oldSelectionClass;
+            
+        } else if (opt === 'row') {
+        	
+        	this.selected.get('rows').each(function(row){
+        		row.removeClass('jxGridRowSelected');
+        	},this);
+        	this.selected.set('rows',[]);
+        	
+        	this.selected.get('rowHeads').each(function(rowHead){
+        		rowHead.removeClass('jxGridRowHeaderSelected');
+        	},this);
+        	this.selected.set('rowHeads',[]);
+        	
+        } else {
+            this.selected.get('columns').each(function(column){
+                for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                    this.grid.gridTable.rows[i].cells[column].removeClass('jxGridColumnSelected');
+                }
+            },this);
+            this.selected.set('columns',[]);
+            
+            this.selected.get('columnHeads').each(function(rowHead){
+        		rowHead.removeClass('jxGridColumnHeaderSelected');
+        	},this);
+        	this.selected.set('columnHeads',[]);
+        }
+    },
+    /**
+     * Method: select
+     * dispatches the grid click to the various selection methods
+     */
+    select : function (cell) {
+
+        // console.log('select method');
+        var data = cell.retrieve('jxCellData');
+        // console.log(data);
+
+        if (this.options.row && $defined(data.row)) {
+            this.selectRow(data.row);
+        }
+
+        if (this.options.column && $defined(data.index)) {
+            if (this.grid.row.useHeaders()) {
+                this.selectColumn(data.index - 1);
+            } else {
+                this.selectColumn(data.index);
+            }
+        }
+
+    },
+    /**
+     * Method: selectRow
+     * Select a row and apply the jxGridRowSelected style to it.
+     *
+     * Parameters:
+     * row - {Integer} the row to select
+     */
+    selectRow: function (row) {
+        if (!this.options.row) { return; }
+
+        var tr = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
+        tr = document.id(tr);
+        
+        var rows = this.selected.get('rows');
+	    if (tr.hasClass('jxGridRowSelected')) {
+	        tr.removeClass('jxGridRowSelected');
+	        this.setCheckField(row, false);
+
+            if (this.options.multiple && this.options.useCheckColumn) {
+                if (this.options.checkAsHeader) {
+                    document.id(this.grid.rowColObj).getElement('input').removeProperty('checked');
+                } else {
+                    document.id(this.checkColumn).getElement('input').removeProperty('checked');
+                }
+            }
+
+	        //search array and remove this item
+	        rows.erase(tr);
+	    } else {
+	        rows.push(tr);
+	        tr.addClass('jxGridRowSelected');
+	        this.setCheckField(row, true);
+	    }
+	        
+	    if (!this.options.multiple) {
+	    	rows.each(function(row){
+	    		if (row !== tr) {
+	    			row.removeClass('jxGridRowSelected');
+	    			this.setCheckField(row.retrieve('jxRowData').row,false);
+	    			rows.erase(row);
+	    		}
+	    	},this);
+        }
+        	
+	    this.selectRowHeader(row);
+
+    },
+    
+    setCheckField: function (row, checked) {
+    	if (this.options.useCheckColumn) {
+    		var check;
+    		if (this.options.checkAsHeader) {
+    			check = document.id(this.grid.rowTableHead.rows[row].cells[0]).getFirst().getFirst();
+    		} else {
+    			var col = this.grid.columns.getIndexFromGrid(this.checkColumn.name);
+    			check = document.id(this.grid.gridTableBody.rows[row].cells[col]).getFirst().getFirst();
+    		}
+        	check.retrieve('field').setValue(checked);
+        }
+    },
+    /**
+     * Method: selectRowHeader
+     * Apply the jxGridRowHeaderSelected 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;
+        }
+        cell = document.id(cell);
+        var cells = this.selected.get('rowHeads');
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridRowHeaderSelected');
+            cells.erase(cell);
+        } else {
+        	cell.addClass('jxGridRowHeaderSelected');
+        	cells.push(cell);
+        }
+        
+        if (!this.options.multiple) {
+        	cells.each(function(c){
+        		if (c !== cell) {
+        			c.removeClass('jxGridRowHeaderSelected');
+        			cells.erase(c);
+        		}
+        	},this);
+        }
+        
+    },
+    /**
+     * 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) {
+        	var cols = this.selected.get('columns');
+        	
+        	var m = '';
+            if (cols.contains(col)) {
+            	//deselect
+            	m = 'removeClass';
+            	cols.erase(col);
+            } else {
+            	//select
+            	m = 'addClass';
+            	cols.push(col);
+            }
+            for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                this.grid.gridTable.rows[i].cells[col][m]('jxGridColumnSelected');
+            }
+            
+            if (!this.options.multiple) {
+            	cols.each(function(c){
+            		if (c !== col) {
+            			for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                            this.grid.gridTable.rows[i].cells[c].removeClass('jxGridColumnSelected');
+                        }
+            			cols.erase(c);
+            		}
+            	},this);
+            }
+            
+            this.selectColumnHeader(col);
+        }
+    },
+    /**
+     * 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;
+        }
+
+        cell = document.id(cell);
+        cells = this.selected.get('columnHeads');
+        
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridColumnHeaderSelected');
+            cells.erase(cell);
+        } else {
+        	cell.addClass('jxGridColumnHeaderSelected');
+        	cells.push(cell);
+        }
+        
+        if (!this.options.multiple) {
+        	cells.each(function(c){
+        		if (c !== cell) {
+        			c.removeClass('jxGridColumnHeaderSelected');
+        			cells.erase(c);
+        		}
+        	},this);
+        }
+
+    },
+    /**
+     * Method: checkSelection
+     * Checks whether a row's check box is/isn't checked and modifies the 
+     * selection appropriately.
+     * 
+     * Parameters:
+     * column - <Jx.Column> that created the checkbox
+     * field - <Jx.Field.Checkbox> instance that was checked/unchecked
+     */
+    checkSelection: function (column, field) {
+    	var data = document.id(field).getParent().retrieve('jxCellData');
+    	this.selectRow(data.row);
+    },
+    /**
+     * Method: checkAll
+     * Checks all checkboxes in the column the selector inserted.
+     */
+    checkAll: function () {
+        var col;
+        var rows;
+        var checked;
+
+        checked = this.options.checkAsHeader ? this.grid.rowColObj.getElement('input').get('checked') :
+                this.checkColumn.domObj.getElement('input').get('checked');
+
+        if (this.options.checkAsHeader) {
+            col = 0;
+            rows = this.grid.rowTableHead.rows;
+        } else {
+            col = this.grid.columns.getIndexFromGrid(this.checkColumn.name);
+            rows = this.grid.gridTableBody.rows;
+        }
+
+        $A(rows).each(function(row, idx) {
+            var check = row.cells[col].getElement('input');
+            if ($defined(check)) {
+                var rowChecked = check.get('checked');
+                if (rowChecked !== checked) {
+                    this.selectRow(idx);
+                }
+            }
+        }, this);
+    }
+});
+// $Id: grid.prelighter.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * 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
+    },
+    /**
+     * 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.lighton = this.lighton.bind(this);
+        this.bound.lightoff = this.lightoff.bind(this);
+        this.bound.mouseleave = this.mouseleave.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('gridCellEnter', this.bound.lighton);
+        this.grid.addEvent('gridCellLeave', this.bound.lightoff);
+        this.grid.addEvent('gridMouseLeave', this.bound.mouseleave);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridCellEnter', this.bound.lighton);
+            this.grid.removeEvent('gridCellLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridMouseLeave', this.bound.mouseleave);
+        }
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning prelighting on.
+     * 
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning prelighting off.
+     * 
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    deactivate: function (opt) {
+        this.options[opt] = false;
+    },
+    /**
+     * Method: lighton
+     */
+    lighton : function (cell, list, grid) {
+        this.light(cell, list, grid, true);
+
+    },
+    /**
+     * Method: lightoff
+     */
+    lightoff : function (cell, list, grid) {
+        this.light(cell, list, grid, false);
+
+    },
+    /**
+     * Method: light
+     * dispatches the event to the various prelight methods.
+     */
+    light: function (cell, list, grid, on) {
+        var data = cell.retrieve('jxCellData');
+
+        if (this.options.cell) {
+            this.prelightCell(cell, on);
+        }
+        if (this.options.row) {
+            this.prelightRow(data.row, on);
+        }
+        if (this.options.column) {
+            if (this.grid.row.useHeaders()) {
+                this.prelightColumn(data.index - 1, on);
+            } else {
+                this.prelightColumn(data.index, on);
+            }
+        }
+        if (this.options.rowHeader) {
+            this.prelightRowHeader(data.row, on);
+        }
+        if (this.options.columnHeader) {
+            this.prelightColumnHeader(data.index - 1, on);
+        }
+    },
+
+    /**
+     * Method: prelightRowHeader
+     * apply the jxGridRowHeaderPrelight style to the header cell of a row.
+     * This removes the style from the previously pre-lit row header.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light the header cell of
+     */
+    prelightRowHeader : function (row, on) {
+        if ($defined(this.prelitRowHeader) && !on) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        } else if (on) {
+            this.prelitRowHeader = (row >= 0 && row < this.grid.rowTableHead.rows.length) ? this.grid.rowTableHead.rows[row].cells[0] : null;
+            if (this.prelitRowHeader) {
+                this.prelitRowHeader.addClass('jxGridRowHeaderPrelight');
+            }
+        }
+    },
+    /**
+     * Method: prelightColumnHeader
+     * apply the jxGridColumnHeaderPrelight style to the header cell of a column.
+     * This removes the style from the previously pre-lit column header.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light the header cell of
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumnHeader : function (col, on) {
+        if (this.grid.colTableBody.rows.length === 0) {
+            return;
+        }
+
+        if ($defined(this.prelitColumnHeader) && !on) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        } else if (on) {
+            this.prelitColumnHeader = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col] : null;
+            if (this.prelitColumnHeader) {
+                this.prelitColumnHeader.addClass('jxGridColumnHeaderPrelight');
+            }
+        }
+
+    },
+    /**
+     * Method: prelightRow
+     * apply the jxGridRowPrelight style to row.
+     * This removes the style from the previously pre-lit row.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightRow : function (row, on) {
+       if ($defined(this.prelitRow) && !on) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        } else if (on) {
+            this.prelitRow = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
+            if (this.prelitRow) {
+                this.prelitRow.addClass('jxGridRowPrelight');
+            }
+        }
+        this.prelightRowHeader(row, on);
+    },
+    /**
+     * Method: prelightColumn
+     * apply the jxGridColumnPrelight style to a column.
+     * This removes the style from the previously pre-lit column.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumn : function (col, on) {
+        if (col >= 0 && col < this.grid.gridTable.rows[0].cells.length) {
+            if ($defined(this.prelitColumn) && !on) {
+                for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                    this.grid.gridTable.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+                }
+            } else if (on) {
+                this.prelitColumn = col;
+                for (i = 0; i < this.grid.gridTable.rows.length; i++) {
+                    this.grid.gridTable.rows[i].cells[col].addClass('jxGridColumnPrelight');
+                }
+            }
+            this.prelightColumnHeader(col, on);
+        }
+    },
+    /**
+     * Method: prelightCell
+     * apply the jxGridCellPrelight style to a cell.
+     * This removes the style from the previously pre-lit cell.
+     *
+     * Parameters:
+     * cell - the cell to lighton/off
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightCell : function (cell, on) {
+        if ($defined(this.prelitCell) && !on) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        } else if (on) {
+            this.prelitCell = cell;
+            if (this.prelitCell) {
+                this.prelitCell.addClass('jxGridCellPrelight');
+            }
+        }
+    },
+    
+    mouseleave: function() {
+        //turn off all prelights when the mouse leaves the grid
+        if ($defined(this.prelitCell)) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        }
+        if ($defined(this.prelitColumn)) {
+            for (var i = 0; i < this.grid.gridTable.rows.length; i++) {
+                this.grid.gridTable.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+            }
+        }
+        if ($defined(this.prelitRow)) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        }
+        if ($defined(this.prelitColumnHeader)) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        }
+        if ($defined(this.prelitRowHeader)) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        }
+    }
+});
+// $Id: grid.sorter.js 809 2010-03-28 04:15:14Z jonlb at comcast.net $
+/**
+ * Class: Jx.Plugin.Sorter
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to sort the grid by a single column.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Sorter = new Class({
+
+    Family: 'Jx.Plugin.Grid.Sorter',
+    Extends : Jx.Plugin,
+    Binds: ['sort', 'addHeaderClass'],
+
+    /**
+     * Property: current
+     * refernce to the currently sorted column
+     */
+    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,
+    /**
+     * 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('gridCellSelect', this.sort);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridCellSelect', this.sort);
+        }
+        this.grid = null;
+    },
+    /**
+     * Method: sort
+     * called when a grid header is clicked.
+     *
+     * Parameters:
+     * cell - The cell clicked
+     */
+    sort : function (cell) {
+        var data = cell.retrieve('jxCellData');
+        if (data.colHeader) {
+            var column = data.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 = data.index - 1;
+                }
+
+                // The grid should be listening for the sortFinished event and
+                // will re-render the grid we will listen for the grid's
+                // doneCreateGrid event to add the header
+                this.grid.addEvent('doneCreateGrid', this.addHeaderClass);
+                //sort the store
+                var strategy = this.grid.getModel().getStrategy('sort');
+                if (strategy) {
+                  strategy.sort(this.current.name, null, this.direction);
+                }
+            }
+
+        }
+    },
+    /**
+     * 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.addHeaderClass);
+
+        //get header TD
+        var th = this.grid.colTable.rows[0].cells[this.currentGridIndex];
+        th.addClass('jxGridColumnSorted' + this.direction.capitalize());
+    }
+});
+// $Id: grid.resize.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * 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,
+    Binds: ['createHandles','removeHandles'],
+    options: {
+        /**
+         * Option: column
+         * set to true to make column widths resizeable
+         */
+        column: false,
+        /**
+         * Option: row
+         * set to true to make row heights resizeable
+         */
+        row: false,
+        /**
+         * Option: tooltip
+         * the tooltip to display for the draggable portion of the
+         * cell header, localized with MooTools.lang.get('Jx','plugin.resize').tooltip for default
+         */
+        tooltip: ''
+    },
+    /**
+     * Property: els
+     * the DOM elements by which the rows/columns are resized.
+     */
+    els: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * Property: drags
+     * the Drag instances
+     */
+    drags: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * 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('doneCreateGrid', this.createHandles);
+        this.grid.addEvent('beginCreateGrid', this.removeHandles);
+        this.createHandles();
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('doneCreateGrid', this.createHandles);
+            this.grid.removeEvent('beginCreateGrid', this.removeHandles);
+        }
+        this.grid = null;
+    },
+
+    activate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = true;
+        }
+        this.createHandles();
+    },
+    
+    deactivate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = false;
+        }
+        this.createHandles();
+    },
+    /**
+     * Method: removeHandles
+     * clean up any handles we created
+     */
+    removeHandles: function() {
+        ['column','row'].each(function(option) {
+          this.els[option].each(function(el) { el.dispose(); } );
+          this.els[option] = [];
+          this.drags[option].each(function(drag){ drag.detach(); });
+          this.drags[option] = [];
+        }, this);
+    },
+    /**
+     * Method: createHandles
+     * create handles that let the user drag to resize columns and rows
+     */
+    createHandles: function() {
+        this.removeHandles();
+        if (this.options.column && this.grid.columns.useHeaders()) {
+            var hf = this.grid.row.getRowHeaderColumn();
+            this.grid.columns.columns.each(function(col, idx) {
+                if (col.options.name != hf && 
+                    col.isResizable() && 
+                    col.domObj) {
+                    var el = new Element('div', {
+                        'class':'jxGridColumnResize',
+                        title: this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip),
+                        events: {
+                            dblclick: function() {
+                                col.options.renderMode = 'fit';
+                                col.options.width = 'auto';
+                                col.setWidth(col.getWidth(true));
+                            }
+                        }
+                    }).inject(col.domObj);
+                    el.store('col', col);
+                    this.els.column.push(el);
+                    this.drags.column.push(new Drag(el, {
+                        limit: {y:[0,0]},
+                        snap: 2,
+                        onBeforeStart: function(el) {
+                          var l = el.getPosition(el.parentNode).x.toInt();
+                          el.setStyles({
+                            left: l,
+                            right: null
+                          });
+                          
+                        },
+                        onStart: function(el) {
+                          var l = el.getPosition(el.parentNode).x.toInt();
+                          el.setStyles({
+                            left: l,
+                            right: null
+                          });
+                        },
+                        onDrag: function(el) {
+                            var col = el.retrieve('col');
+                            col.options.renderMode = 'fixed';
+                            var w = el.getPosition(el.parentNode).x.toInt();
+                            col.setWidth(w);
+                        },
+                        onComplete: function(el) {
+                          el.setStyle('left', null);
+                          col.grid.resizeRowsCols("rows");
+                        }
+                    }));
+                }
+            }, this);
+        }
+
+        //if (this.options.row && this.grid.row.useHeaders()) {}
+    },
+    /**
+     * Method: createText
+     * respond to a language change by updating the tooltip
+     */
+    changeText: function (lang) {
+      this.parent();
+      var txt = this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip);
+      ['column','row'].each(function(option) {
+        this.els[option].each(function(el) { el.set('title',txt); } );
+      }, this);
+    }
+});
+// $Id: grid.editor.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Plugin.Editor
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to enable inline editing within a cell
+ *
+ * Original selection code from Jx.Grid's original class
+ *
+ * License:
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Conrad Barthelmes.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Editor = new Class({
+
+    Extends : Jx.Plugin,
+    Binds: ['activate','deactivate','changeText'],
+
+    options : {
+      /**
+       * Option: enabled
+       * Determines if inline editing is avaiable
+       */
+      enabled : true,
+      /**
+       * Option: blurDelay
+       * Set the time in miliseconds when the inputfield/popup shall hide. When
+       * the user refocuses the input/popup within this time, the timeout will be cleared
+       *
+       * set to 'false' if no hiding on blur is wanted
+       */
+      blurDelay : 500,
+      /**
+       * Option: popup
+       *
+       * Definitions for a PopUp to use.
+       * - use        - determines whether to use a PopUp or simply the input
+       * - useLabel   - determines whether to use labels on top of the input.
+       *                Text will be the column header
+       * - useButtons - determines whether to use Submit and Cancel Buttons
+       * - buttonLabel.submit - Text for Submit Button, uses MooTools.lang.get('Jx', 'plugin.editor').submitButton for default
+       * - buttonLabel.cancel - Text for Cancel Button, uses MooTools.lang.get('Jx', 'plugin.editor').cancelButton for default
+       */
+      popup : {
+        use           : true,
+        useLabels     : false,
+        useButtons    : true,
+        button        : {
+          submit : {
+            label : '',
+            image : 'images/accept.png'
+          },
+          cancel : {
+            label : '',
+            image : 'images/cancel.png'
+          }
+        },
+        template: '<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'
+      },
+      /**
+       * Option {boolean} validate
+       * - set to true to have all editable input fields as mandatory field
+       *   if they don't have 'mandatory:true' in their colOptions
+       */
+      validate : true,
+      /**
+       * Option: {Array} fieldOptions with objects
+       * Contains objects with options for the Jx.Field instances to show up.
+       * Default options will be added automatically if custom options are entered.
+       *
+       * Preferences:
+       *   field             - Default * for all types or the name of the column in the model (Jx.Store)
+       *   type              - Input type to show (Text, Password, Textarea, Select, Checkbox)
+       *   options           - All Jx.Field options for this column. More options depend on what type you are using.
+       *                       See Jx.Form.[yourField] for details
+       *   validatorOptions: - See Jx.Plugin.Field.Validator Options for details
+       *                       will only be used if this.options.validate is set to true
+       */
+      fieldOptions : [
+        {
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        }
+      ],
+      /**
+       * Option: {Boolean} fieldFormatted
+       * Displays the cell value also inside the input field as formatted
+       */
+      fieldFormatted : true,
+      /**
+       * Option cellChangeFx
+       * set use to false if no highlighting effect is wanted.
+       *
+       * this is just an idea how successfully changing could be highlighed for the user
+       */
+      cellChangeFx : {
+        use     : true,
+        success : '#090',
+        error   : '#F00'
+      },
+      /**
+       * Option cellOutline
+       * shows an outline style to the currently active cell to make it easier to see
+       * which cell is active
+       */
+      cellOutline : {
+        use   : true,
+        style : '2px solid #88c3e7'
+      },
+      /**
+       * Option: useKeyboard
+       * Set to false if no keyboard support is needed
+       */
+      useKeyboard : true,
+      /**
+       * Option: keys
+       * Contains the event codes for several commands that can be used when
+       * a field is active. Syntax is the same like for the Mootools Keyboard Class
+       * http://mootools.net/docs/more/Interface/Keyboard
+       */
+      keys : {
+        'ctrl+shift+enter' : 'saveNGoUp',
+        'tab'              : 'saveNGoRight',
+        'ctrl+enter'       : 'saveNGoDown',
+        'shift+tab'        : 'saveNGoLeft',
+        'enter'            : 'saveNClose',
+        'ctrl+up'          : 'cancelNGoUp',
+        'ctrl+right'       : 'cancelNGoRight',
+        'ctrl+down'        : 'cancelNGoDown',
+        'ctrl+left'        : 'cancelNGoLeft',
+        'esc'              : 'cancelNClose',
+        'up'               : 'valueIncrement',
+        'down'             : 'valueDecrement'
+      },
+      /**
+       * Option: keyboardMethods
+       *
+       * can be used to overwrite existing keyboard methods that are used inside
+       * this.options.keys - also possible to add new ones.
+       * Functions are bound to the editor plugin when using 'this'
+       *
+       * example:
+       *  keys : {
+       *    'ctrl+u' : 'cancelNGoRightNDown'
+       *  },
+       *  keyboardMethods: {
+       *    'cancelNGoRightNDown' : function(ev){
+       *      ev.preventDefault();
+       *      this.getNextCellInRow(false);
+       *      this.getNextCellInCol(false);
+       *    }
+       *  }
+       */
+      keyboardMethods : {},
+      /**
+       * Option: keypressLoop
+       * loop through the grid when pressing TAB (or some other method that uses
+       * this.getNextCellInRow() or this.getPrevCellInRow()). If set to false,
+       * the input field/popup will not start at the opposite site of the grid
+       * Defaults to true
+       */
+      keypressLoop : true,
+      /**
+       * Option: linkClickListener
+       * disables all click events on links that are formatted with Jx.Formatter.Uri
+       * - otherwise the link will open directly instead of open the input editor)
+       * - hold [ctrl] to open the link in a new tab
+       */
+      linkClickListener : true
+    },
+    classes: ['jxGridEditorPopup', 'jxGridEditorPopupInnerWrapper'],
+    /**
+     * Property: activeCell
+     *
+     * Containing Objects:
+     *   field        : Reference to the Jx.Field instance that will be created
+     *   cell         : Reference to the cell inside the table 
+     *   span         : Reference to the Dom Element inside the selected cell of the grid
+     *   oldValue     : Old value of the cell from the grid's model
+     *   newValue     : Object with <data> and <error> for better validation possibilites
+     *   timeoutId    : TimeoutId if the focus blurs the input.
+     *   coords       : Coordinates of the selected cell
+     *   colOptions   : Reference to the column's option in which the cell is
+     *   fieldOptions : Reference to the field options of this column
+     */
+    activeCell : {
+      field       : null,
+      cell        : null,
+      span        : null,
+      oldValue    : null,
+      newValue    : { data: null, error: false },
+      timeoutId   : null,
+      coords      : {},
+      colOptions  : {},
+      fieldOptions: {}
+    },
+    /**
+     * Property : popup
+     *
+     * References to all contents within a popup (only 1 popup for 1 grid initialization)
+     *
+     * COMMENT: I don't know how deep we need to go into that.. innerWrapper and closeLink probably don't need
+     * own references.. I just made them here in case they are needed at some time..
+     *
+     * Containing Objects:
+     *   domObj         : Reference to the Dom Element of the popup (absolutely positioned)
+     *   innerWrapper   : Reference to the inner Wrapper inside the popup to provide relative positioning
+     *   closeIcon      : Reference to the Dom Element of a little [x] in the upper right to close it (not saving)
+     *   buttons        : References to all Jx.Buttons used inside the popup
+     *   buttons.submit : Reference to the Submit Button
+     *   buttons.cancel : Reference to the Cancel Button
+     */
+    popup : {
+      domObj       : null,
+      innerWarpper : null,
+      closeIcon    : null,
+      button       : {
+        submit : null,
+        cancel : null
+      }
+    },
+    /**
+     * Property: keyboard
+     * Instance of a Mootols Keyboard Class
+     */
+    keyboard : null,
+    /**
+     * Property keyboardMethods
+     * Editing and grid functions for keyboard functionality.
+     * Methods are defined and implemented inside this.attach() because of referencing troubles
+     */
+    keyboardMethods : {},
+    /**
+     * APIMethod: init
+     * construct a new instance of the plugin.  The plugin must be attached
+     * to a Jx.Grid instance to be useful though.
+     */
+    init: function() {
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * @var {Object} grid - Instance of Class Jx.Grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+        return;
+      }
+      this.grid = grid;
+
+      this.grid.addEvent('gridCellSelect', this.activate);
+      this.grid.addEvent('gridCellUnSelect', this.deactivate);
+
+      /*
+       * add default field options to the options in case some new options were entered
+       * to be still able to use them for the rest of the fields
+       */
+      if(this.getFieldOptionsByColName('*').field != '*') {
+        this.options.fieldOptions.unshift({
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        });
+      }
+
+      /**
+       * set the keyboard methods here to have a correct reference to the instance of
+       * the editor plugin
+       *
+       * @todo other names maybe? or even completely different way of handling the keyboard events?
+       * @todo more documentation than method name
+       */
+      var self = this;
+      this.keyboardMethods = {
+        saveNClose     : function(ev) {
+          if(self.activeCell.fieldOptions.type != 'Textarea' || (self.activeCell.fieldOptions.type == 'Textarea' && ev.key != 'enter')) {
+            self.deactivate()
+          }
+        },
+        saveNGoUp      : function(ev) {ev.preventDefault();self.getPrevCellInCol()},
+        saveNGoRight   : function(ev) {ev.preventDefault();self.getNextCellInRow()},
+        saveNGoDown    : function(ev) {ev.preventDefault();self.getNextCellInCol()},
+        saveNGoLeft    : function(ev) {ev.preventDefault();self.getPrevCellInRow()},
+        cancelNClose   : function(ev) {ev.preventDefault();self.deactivate(false)},
+        cancelNGoUp    : function(ev) {ev.preventDefault();self.getPrevCellInCol(false)},
+        cancelNGoRight : function(ev) {ev.preventDefault();self.getNextCellInRow(false)},
+        cancelNGoDown  : function(ev) {ev.preventDefault();self.getNextCellInCol(false)},
+        cancelNGoLeft  : function(ev) {ev.preventDefault();self.getPrevCellInRow(false)},
+        valueIncrement : function(ev) {ev.preventDefault();self.cellValueIncrement(true)},
+        valueDecrement : function(ev) {ev.preventDefault();self.cellValueIncrement(false)}
+      };
+      
+      var keyboardEvents = {};
+      for(var i in this.options.keys) {
+        if($defined(this.keyboardMethods[this.options.keys[i]])) {
+          keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+        }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+          keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+        }else if(Jx.type(this.options.keys[i]) == 'function') {
+          keyboardEvents[i] = this.options.keys[i].bind(self);
+        }else{
+          $defined(console) ? console.warn("keyboard method %o not defined", this.options.keys[i]) : false;
+        }
+      }
+
+      // initalize keyboard support but do NOT activate it (this is done inside this.activate()).
+      this.keyboard = new Keyboard({
+        events: keyboardEvents
+      });
+
+      this.addFormatterUriClickListener();
+    },
+    /**
+     * APIMethod: detach
+     * detaches from the grid
+     * 
+     * @return void
+     */
+    detach: function() {
+      if (this.grid) {
+        this.grid.removeEvent('gridClick', this.activate);
+      }
+      this.grid = null;
+      this.keyboard = null;
+    },
+    /**
+     * APIMethod: enable
+     * enables the grid 'externally'
+     *
+     * @return void
+     */
+    enable : function () {
+      this.options.enabled = true;
+    },
+    /**
+     * APIMethod: disable
+     * disables the grid 'externally'
+     *
+     * @var Boolean close - default true: also closes the currently open input/popup
+     * @var Boolean save - default false: also changes the currently open input/popup
+     * @return void
+     */
+    disable : function(close, save) {
+      close = $defined(close) ? close : true;
+      save = $defined(save) ? save : false;
+      if(close && this.activeCell.cell != null) {
+        this.deactivate(save);
+      }
+      this.options.enabled = false;
+    },
+    /**
+     * Method: activate
+     * activates the input field or breaks up if conditions are not fulfilled
+     *
+     * @todo Field validation
+     *
+     * Parameters:
+     * @var {Object} cell Table Element
+     * @return void
+     */
+    activate: function(cell) {
+      if(!this.options.enabled)
+        return;
+
+      var data  = cell.retrieve('jxCellData');
+      // @todo Rename Header too??
+      // return if a table header was clicked
+      if(($defined(data.colHeader) && data.colHeader) || ($defined(data.rowHeader) && data.rowHeader))
+        return;
+      var row   = data.row,
+          index = data.index;
+
+      clearTimeout(this.activeCell.timeoutId);
+
+      if(this.cellIsInGrid(row, index)) {
+
+        var colIndex   = this.grid.options.row.useHeaders ? index-1 : index;
+        var model      = this.grid.getModel(),
+            //cell       = this.grid.gridTableBody.rows[row].cells[col] ? this.grid.gridTableBody.rows[row].cells[col] : null,
+            colOptions = this.grid.columns.getByGridIndex(colIndex).options;
+        if (!cell || !colOptions.isEditable) {
+          return;
+        }
+        // if disabling a currently active one fails (mandatory for example) do not continue
+        if(this.activeCell.cell != null && this.deactivate() == false) {
+          return;
+        }
+
+        // set active record index to selected row
+        model.moveTo(row);
+        
+        // store properties of the active cell
+        this.activeCell = {
+          oldValue      : model.get(data.index),
+          newValue      : {data: null, error: false},
+          fieldOptions  : this.getFieldOptionsByColName(colOptions.name),
+          colOptions    : colOptions,
+          coords        : {row : row, index : index},
+          cell          : cell,
+          span          : cell.getElement('span.jxGridCellContent'),
+          validator     : null,
+          field         : null,
+          timeoutId     : null
+        }
+
+        // check if this column has special validation settings - otherwise use default from this.options.validate
+        if(!$defined(this.activeCell.colOptions.validate) || typeof(this.activeCell.colOptions.validate) != 'boolean') {
+          this.activeCell.colOptions.validate = this.options.validate;
+        }
+
+        var jxFieldOptions = $defined(this.activeCell.fieldOptions.options) ? this.activeCell.fieldOptions.options : {}
+
+        // check for different input field types
+        switch(this.activeCell.fieldOptions.type) {
+          case 'Text':
+          case 'Color':
+          case 'Password':
+          case 'File':
+            jxFieldOptions.value = this.activeCell.oldValue;
+            break;
+          case 'Textarea':
+            jxFieldOptions.value = this.activeCell.oldValue.replace(/<br \/>/gi, '\n');
+            break;
+          case 'Select':
+            // find out which visible value fits to the value inside <option>{value}</option> and set it to selected
+            var oldValue  = this.activeCell.oldValue.toString();
+            function setCombos(opts, oldValue) {
+              for(var i = 0, j = opts.length; i < j; i++) {
+                if(opts[i].value == oldValue) {
+                  opts[i].selected = true;
+                }else{
+                  opts[i].selected = false;
+                }
+              }
+              return opts;
+            }
+
+            if(jxFieldOptions.comboOpts) {
+              jxFieldOptions.comboOpts = setCombos(jxFieldOptions.comboOpts, oldValue);
+            }else if(jxFieldOptions.optGroups) {
+              var groups = jxFieldOptions.optGroups;
+              for(var k = 0, n = groups.length; k < n; k++) {
+                groups[k].options = setCombos(groups[k].options, oldValue);
+              }
+              jxFieldOptions.optGroups = groups;
+            }
+            break;
+          case 'Radio':
+          case 'Checkbox':
+          default:
+            $defined(console) ? console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.", this.activeCell.fieldOptions.type) : false;
+            return;
+            break;
+        }
+
+        // update the 'oldValue' to the formatted style, to compare the new value with the formatted one instead with the non-formatted-one
+        if(this.options.fieldFormatted && this.activeCell.colOptions.renderer.options.formatter != null) {
+          if(!$defined(this.activeCell.colOptions.fieldFormatted) || this.activeCell.colOptions.fieldFormatted == true ) {
+            jxFieldOptions.value = this.activeCell.colOptions.renderer.options.formatter.format(jxFieldOptions.value);
+            this.activeCell.oldValue = jxFieldOptions.value;
+          }
+        }
+
+        // create jx.field
+        this.activeCell.field = new Jx.Field[this.activeCell.fieldOptions.type.capitalize()](jxFieldOptions);
+        // create validator
+        if(this.options.validate && this.activeCell.colOptions.validate) {
+          this.activeCell.validator = new Jx.Plugin.Field.Validator(this.activeCell.fieldOptions.validatorOptions);
+          this.activeCell.validator.attach(this.activeCell.field);
+        }
+        this.setStyles(cell);
+
+        if(this.options.useKeyboard) {
+          this.keyboard.activate();
+        }
+
+        // convert a string to an integer if somebody entered a numeric value in quotes, if it failes: make false
+        if(typeof(this.options.blurDelay) == 'string') {
+          this.options.blurDelay = this.options.blurDelay.toInt() ? this.options.blurDelay.toInt() : false;
+        }
+
+        // add a onblur() and onfocus() event to the input field if enabled.
+        if(this.options.blurDelay !== false && typeof(this.options.blurDelay) == 'number') {
+          var self = this;
+          this.activeCell.field.field.addEvents({
+            // activate the timeout to close the input/poup
+            'blur' : function() {
+              // @todo For some reason, webkit does not clear the timeout correctly when navigating through the grid with keyboard
+              clearTimeout(self.activeCell.timeoutId);
+              self.activeCell.timeoutId = self.deactivate.delay(self.options.blurDelay);
+            },
+            // clear the timeout when the user focusses again
+            'focus' : function() {
+              clearTimeout(self.activeCell.timeoutId);
+            }, 
+            // clear the timeout when the user puts the mouse over the input
+            'mouseover' : function() {
+              clearTimeout(self.activeCell.timeoutId);
+            }
+          });
+          if(this.popup.domObj != null) {
+            this.popup.domObj.addEvent('mouseenter', function() {
+              clearTimeout(self.activeCell.timeoutId);
+            });
+          }
+        }
+
+        this.activeCell.field.field.focus();
+      }else{
+        if($defined(console)) {console.warn('out of grid %o',cell)}
+      }
+    }, 
+    /**
+     * APIMethod: deactivate
+     * hides the currently active field and stores the new entered data if the
+     * value has changed
+     *
+     * Parameters:
+     * @var {Boolean} save (Optional, default: true) - force aborting
+     * @return true if no data error occured, false if error (popup/input stays visible)
+     */
+    deactivate: function(save) {
+
+      clearTimeout(this.activeCell.timeoutId);
+      
+      if(this.activeCell.field !== null) {
+        save = $defined(save) ? save : true;
+
+        var newValue = {data : null, error : false};
+
+        // update the value in the model
+        if(save && this.activeCell.field.getValue().toString() != this.activeCell.oldValue.toString()) {
+          this.grid.model.moveTo(this.activeCell.coords.row);
+          /*
+           * @todo webkit shrinks the rows when the value is updated... but refreshing the grid
+           *       immidiately returns in a wrong calculating of the cell position (getCoordinates)
+           */
+          switch(this.activeCell.fieldOptions.type) {
+            case 'Select':
+              var index = this.activeCell.field.field.selectedIndex;
+              newValue.data = document.id(this.activeCell.field.field.options[index]).get('value');
+              break;
+            case 'Textarea':
+              newValue.data = this.activeCell.field.getValue().replace(/\n/gi, '<br />');
+              break;
+            default:
+              newValue.data = this.activeCell.field.getValue();
+              break;
+          }
+          if(save) {
+            this.activeCell.newValue.data = newValue.data;
+            // manually blur the field to activate the validator -> continues with this.terminate()
+            //this.activeCell.timeoutId = this.activeCell.field.field.blur.delay(50, this.activeCell.field.field);
+          }
+          // validation only if it should be saved!
+          if(this.activeCell.validator != null && !this.activeCell.validator.isValid()) {
+            newValue.error = true;
+            this.activeCell.field.field.focus.delay(50, this.activeCell.field.field);
+          }
+        }else{
+          this.activeCell.span.show();
+        }
+
+
+        if(save && newValue.data != null && newValue.error == false) {
+          this.grid.model.set(this.activeCell.coords.index, newValue.data);
+          this.addFormatterUriClickListener();
+        // else show error message and cell
+        }else if(newValue.error == true) {
+          this.activeCell.span.show();
+        }
+
+        // update reference to activeCell
+        if($defined(this.activeCell.coords.row) && $defined(this.activeCell.coords.index)) {
+          var colIndex = this.grid.options.row.useHeaders ? this.activeCell.coords.index-1 : this.activeCell.coords.index;
+          this.activeCell.cell = this.grid.gridTableBody.rows[this.activeCell.coords.row].cells[colIndex];
+        }
+
+        if(this.options.useKeyboard) {
+          this.activeCell.field.removeEvent('keypress', this.setKeyboard);
+        }
+
+        /**
+         * COMMENT: this is just an idea how changing a value could be visualized
+         * we could also pass an Fx.Tween element?
+         * the row could probably be highlighted as well?
+         */
+        if(this.options.cellChangeFx.use) {
+          var highlighter = new Fx.Tween(this.activeCell.cell, {
+            duration: 250,
+            onComplete: function(ev) {
+              this.element.removeProperty('style');
+            }
+          });
+          var currentCellBg = this.activeCell.cell.getStyle('background-color');
+          currentCellBg = currentCellBg == 'transparent' ? '#fff' : currentCellBg;
+          if(newValue.data != null && newValue.error == false) {
+            highlighter.start('background-color',this.options.cellChangeFx.success, currentCellBg);
+          }else if(newValue.error){
+            highlighter.start('background-color',this.options.cellChangeFx.error, currentCellBg);
+          }
+        }
+
+        // check for error and keep input field alive
+        if(newValue.error) {
+          if(this.options.cellChangeFx.use) {
+            this.activeCell.field.field.highlight(this.options.cellChangeFx.error);
+          }
+          this.activeCell.field.field.setStyle('border','1px solid '+this.options.cellChangeFx.error);
+          this.activeCell.field.field.focus();
+          return false;
+        // otherwise hide it
+        }else{
+          this.keyboard.deactivate();
+          this.unsetActiveField();
+          return true;
+        }
+      }
+    },
+    /**
+     * Method: setStyles
+     * 
+     * sets some styles for the Jx.Field elements...
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     * @return void
+     */
+    setStyles : function(cell) {
+      // popup
+      if(this.options.popup.use) {
+        if(this.options.popup.useLabels) {
+          this.activeCell.field.options.label = this.activeCell.colOptions.header;
+          this.activeCell.field.render();
+        }
+        var styles = {
+          field : {
+            'width'  : this.activeCell.field.type == 'Select' ?
+                         cell.getContentBoxSize().width + 5 + "px" :
+                         cell.getContentBoxSize().width - 14 + "px",
+            'margin' : 'auto 0'
+          }
+        };
+        this.activeCell.field.field.setStyles(styles.field);
+        this.showPopUp(cell);
+      // No popup
+      }else {
+        var size   = cell.getContentBoxSize(),
+            styles = {
+              domObj : {
+                position: 'absolute'
+              },
+              field : {
+                width : size.width + "px",
+                'margin-left' : 0
+              }
+            };
+
+        this.activeCell.field.domObj.setStyles(styles.domObj);
+        this.activeCell.field.field.setStyles(styles.field);
+       
+        this.activeCell.field.domObj.inject(document.body);
+        Jx.Widget.prototype.position(this.activeCell.field.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+
+        this.activeCell.span.hide();
+      }
+
+      // COMMENT: an outline of the cell helps identifying the currently active cell
+      if(this.options.cellOutline.use) {
+        cell.setStyle('outline', this.options.cellOutline.style);
+      }
+    },
+    /**
+     * Method: showPopUp
+     *
+     * Shows the PopUp of of the editor if it already exists, otherwise calls Method
+     * this.createPopUp
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    showPopUp : function(cell) {
+      if(this.popup.domObj != null) {
+        Jx.Widget.prototype.position(this.popup.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+        this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+        this.popup.domObj.show();
+        this.setPopUpButtons();
+        this.setPopUpStylesAfterRendering();
+      }else{
+        this.createPopUp(cell);
+      }
+    },
+    /**
+     * Method: createPopUp
+     *
+     * creates the popup for the requested cell.
+     *
+     * COMMENT: this could also be an jx.dialog..? if we use jx.dialog, maybe without a title element?
+     *          Maybe a jx.dialog is too much for this little thing?
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    createPopUp : function(cell) {
+      var coords = cell.getCoordinates(),
+          self      = this, popup  = null, innerWrapper = null,
+          closeIcon = null, submit = null, cancel       = null,
+          template  = Jx.Widget.prototype.processTemplate(this.options.popup.template, this.classes);
+
+      popup = template.jxGridEditorPopup;
+      
+      innerWrapper = template.jxGridEditorPopupInnerWrapper;
+      /**
+       * COMMENT: first positioning is always in the top left of the grid..
+       * don't know why
+       * manual positioning is needed..?
+       */
+      popup.setStyles({
+        'left' : coords.left+'px',
+        'top'  : coords.top +'px'
+      });
+      /*
+      Jx.Widget.prototype.position(popup, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+      });
+      */
+
+      this.popup.domObj         = popup;
+      this.popup.innerWrapper   = innerWrapper;
+      this.popup.closeIcon      = closeIcon;
+      this.setPopUpButtons();
+
+      this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+      this.popup.domObj.inject(document.body);
+
+      this.setPopUpStylesAfterRendering();
+    },
+    /**
+     * Method: setPopUpStylesAfterRendering
+     *
+     * - measures the widths of the buttons to set a new min-width for the popup
+     *   because custom labels could break the min-width and force a line-break
+     * - resets the size of the field to make it fit inside the popup (looks nicer)
+     *
+     * @return void
+     */
+    setPopUpStylesAfterRendering: function() {
+      if(this.options.popup.useButtons && this.popup.button.submit != null && this.popup.button.cancel != null) {
+        this.popup.domObj.setStyle('min-width', this.popup.button.submit.domObj.getSize().x + this.popup.button.cancel.domObj.getSize().x + "px");
+      }else{
+        if(this.popup.button.submit != null)
+          this.popup.button.submit.domObj.hide();
+        if(this.popup.button.cancel != null)
+          this.popup.button.cancel.domObj.hide();
+      }
+      this.activeCell.field.field.setStyle('width',
+        this.activeCell.field.type == 'Select' ?
+          this.popup.domObj.getSize().x - 7 + "px" :
+          this.popup.domObj.getSize().x - 17 + "px");
+    },
+    /**
+     * Method: setPopUpButtons
+     * creates the PopUp Buttons if enabled in options or deletes them if set to false
+     *
+     * @return void
+     */
+    setPopUpButtons : function() {
+      var self = this,
+          button = {
+            submit : null,
+            cancel : null
+          };
+      // check if buttons are needed, innerWrapper exists and no buttons already exist
+      if(this.options.popup.useButtons && this.popup.innerWrapper != null && this.popup.button.submit == null) {
+        button.submit = new Jx.Button({
+          label : this.options.popup.button.submit.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'submitButton'}) :
+                    this.getText(this.options.popup.button.submit.label),
+          image : this.options.popup.button.submit.image,
+          onClick: function() {
+            self.deactivate(true);
+          }
+        }).addTo(this.popup.innerWrapper);
+        button.cancel = new Jx.Button({
+          label : this.options.popup.button.cancel.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'cancelButton'}) :
+                    this.getText(this.options.popup.button.cancel.label),
+          image : this.options.popup.button.cancel.image,
+          onClick: function() {
+            self.deactivate(false);
+          }
+        }).addTo(this.popup.innerWrapper);
+      }else if(this.options.popup.useButtons && this.popup.button.submit != null) {
+        button = {
+          submit : this.popup.button.submit,
+          cancel : this.popup.button.cancel
+        };
+      // check if buttons are not needed and buttons already exist to remove them
+      }else if(this.options.popup.useButtons == false && this.popup.button.submit != null) {
+        this.popup.button.submit.cleanup();
+        this.popup.button.cancel.cleanup();
+      }
+
+      this.popup.button = button;
+    },
+    /**
+     * Method: unsetActiveField
+     * resets the activeField and hides the popup
+     *
+     * @return void
+     */
+    unsetActiveField: function() {
+      this.activeCell.field.destroy();
+      if(this.popup.domObj != null) {
+        this.popup.domObj.removeEvent('mouseenter');
+        this.popup.domObj.hide();
+      }
+
+      this.activeCell.cell.setStyle('outline', '0px');
+
+      this.activeCell = {
+        field         : null,
+        oldValue      : null,
+        newValue      : { data: null, error: false},
+        cell          : null,
+        span          : null,
+        timeoutId     : null,
+        //popup         : null,   // do not destroy the popup, it might be used again
+        colOptions    : {},
+        coords        : {},
+        fieldOptions  : {},
+        validator     : null
+      }
+    },
+    /**
+     * Method: unsetPopUp
+     * resets the popup manually to be able to use it with different settings
+     */
+    unsetPopUp : function() {
+      if(this.popup.domObj != null) {
+        this.popup.domObj.destroy();
+        this.popup.innerWrapper   = null;
+        this.popup.closeIcon      = null;
+        this.popup.button.submit = null;
+        this.popup.button.cancel = null;
+      }
+    },
+    /**
+     * APIMethod: getNextCellInRow
+     * activates the next cell in a row if it is editable
+     * otherwise the focus jumps to the next editable cell in the next row
+     * or starts at the beginning
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var nextCell = true, nextRow = true,
+            sumCols = this.grid.columns.columns.length,
+            jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)';
+        var i = 0;
+        do {
+          nextCell = i > 0 ? nextCell.getNext(jxCellClass) : this.activeCell.cell.getNext(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(nextCell == null) {
+            nextRow  = this.activeCell.cell.getParent('tr').getNext();
+            // check if this was the last row in the table
+            if(nextRow == null && this.options.keypressLoop) {
+              nextRow = this.activeCell.cell.getParent('tbody').getFirst();
+            }else if(nextRow == null && !this.options.keypressLoop){
+              return;
+            }
+            nextCell = nextRow.getFirst(jxCellClass);
+          }
+          var data  = nextCell.retrieve('jxCellData');
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(!data.col.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInRow
+     * activates the previous cell in a row if it is editable
+     * otherwise the focus jumps to the previous editable cell in the previous row
+     * or starts at the last cell in the last row at the end
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var prevCell, prevRow, i = 0,
+            sumCols = this.grid.columns.columns.length,
+            jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)';
+        do {
+          prevCell = i > 0 ? prevCell.getPrevious(jxCellClass) : this.activeCell.cell.getPrevious(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(prevCell == null) {
+            prevRow  = this.activeCell.cell.getParent('tr').getPrevious();
+            // check if this was the last row in the table
+            if(prevRow == null && this.options.keypressLoop) {
+              prevRow = this.activeCell.cell.getParent('tbody').getLast();
+            }else if(prevRow == null && !this.options.keypressLoop) {
+              return;
+            }
+            prevCell = prevRow.getLast(jxCellClass);
+          }
+          var data  = prevCell.retrieve('jxCellData'),
+              row   = data.row,
+              index = data.index;
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(!data.col.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(prevCell);
+      }
+    },
+    /**
+     * APIMethod: getNextCellInCol
+     * activates the next cell in a column under the currently active one
+     * if the active cell is in the last row, the first one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInCol : function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var nextRow, nextCell;
+        nextRow = this.activeCell.cell.getParent().getNext();
+        if(nextRow == null) {
+          nextRow = this.activeCell.cell.getParent('tbody').getFirst();
+        }
+        nextCell = nextRow.getElement('td.jxGridCol'+this.activeCell.coords.index);
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInCol
+     * activates the previous cell in a column above the currently active one
+     * if the active cell is in the first row, the last one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInCol : function(save) {
+      save = $defined(save) ? save : true;
+      if(this.activeCell.cell != null) {
+        var prevRow, prevCell;
+        prevRow = this.activeCell.cell.getParent().getPrevious();
+        if(prevRow == null) {
+          prevRow = this.activeCell.cell.getParent('tbody').getLast();
+        }
+        prevCell = prevRow.getElement('td.jxGridCol'+this.activeCell.coords.index);
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.grid.selection.select(prevCell);
+      }
+    },
+    /**
+     * Method: cellValueIncrement
+     * Whether increments or decrements the value of the active cell if the dataType is numeric
+     *
+     * Parameters
+     * @var {Boolean} bool
+     * @return void
+     */
+    cellValueIncrement : function(bool) {
+      var dataType = this.activeCell.colOptions.dataType,
+          valueNew = null;
+      switch(dataType) {
+        case 'numeric':
+        case 'currency':
+          valueNew = this.activeCell.field.getValue().toInt();
+          if(typeof(valueNew) == 'number') {
+            if(bool) {
+              valueNew++;
+            }else{
+              valueNew--;
+            }
+          }
+          break;
+        case 'date':
+          valueNew = Date.parse(this.activeCell.field.getValue());
+          if(valueNew instanceof Date) {
+            if(bool) {
+              valueNew.increment();
+            }else{
+              valueNew.decrement();
+            }
+            var formatter = new Jx.Formatter.Date();
+            valueNew = formatter.format(valueNew);
+          }
+          break;
+      }
+      if(valueNew != null) {
+        this.activeCell.field.setValue(valueNew);
+      }
+    },
+    /**
+     * Method: cellIsInGrid
+     * determins if the given coordinates are within the grid
+     *
+     * Parameters:
+     * @var {Integer} row
+     * @var {Integer} index
+     * @return {Boolean}
+     */
+    cellIsInGrid: function(row, index) {
+      if($defined(row) && $defined(index)) {
+        //console.log("Row %i - max Rows: %i, Col %i - max Cols %i", row, this.grid.gridTableBody.rows.length, index, this.grid.gridTableBody.rows[row].cells.length);
+        if( row >= 0 && index >= 0 &&
+            row <= this.grid.gridTableBody.rows.length &&
+            index <= this.grid.gridTableBody.rows[row].cells.length
+        ) {
+          return true;
+        }else{
+          return false;
+        }
+      }else{
+        return false;
+      }
+    },
+    /**
+     * APIMethod: getFieldOptionsByColName
+     * checks for the name of a column inside the fieldOptions and returns
+     * the object if found, otherwise the default options for the field
+     *
+     * Parameters:
+     * @var {String} colName
+     * @return {Object} default field options
+     */
+    getFieldOptionsByColName : function(colName) {
+      var fo = this.options.fieldOptions,
+          r  = this.options.fieldOptions[0];
+      for(var i = 0, j = fo.length; i < j; i++) {
+        if(fo[i].field == colName) {
+          r = fo[i];
+          break;
+        }
+      }
+      return r;
+    },
+    /**
+     * Method: addFormatterUriClickListener
+     *
+     * looks up for Jx.Formatter.Uri columns to disable the link and open the
+     * inline editor instead when CTRL is NOT pressed.
+     * set option linkClickListener to false to disable this
+     *
+     */
+    addFormatterUriClickListener : function() {
+      if(this.options.linkClickListener) {
+        // prevent a link from beeing opened if the editor should appear and the uri formatter is activated
+        var uriCols = [], tableCols, anchor;
+        // find out which columns are using a Jx.Formatter.Uri
+        this.grid.columns.columns.each(function(col,i) {
+          if(col.options.renderer.options.formatter != null && col.options.renderer.options.formatter instanceof Jx.Formatter.Uri) {
+            uriCols.push(i);
+          }
+        });
+        // add an event to all anchors inside these columns
+        this.grid.gridTable.getElements('tr').each(function(tr,i) {
+          tableCols = tr.getElements('td.jxGridCell');
+          for(var j = 0, k = uriCols.length; j < k; j++) {
+            anchor = tableCols[uriCols[j]-1].getElement('a');
+            if(anchor) {
+              anchor.removeEvent('click');
+              anchor.addEvent('click', function(ev) {
+                // open link if ctrl was clicked
+                if(!ev.control) {
+                  ev.preventDefault();
+                }
+              });
+            }
+          }
+        });
+      }
+    },
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.options.popup.use && this.options.popup.useButtons) {
+        if(this.popup.button.submit != null) {
+          this.popup.button.submit.cleanup();
+          this.popup.button.cancel.cleanup();
+          this.popup.button.submit = null;
+          this.popup.button.cancel = null;
+          this.setPopUpButtons();
+        }
+    	}
+    }
+}); 
+/**
+ * Namespace: Jx.Plugin.DataView
+ * The namespace for all dataview plugins
+ */
+Jx.Plugin.DataView = {};// $Id: slide.js 826 2010-03-31 18:46:16Z pagameba $
+/**
+ * Class: Jx.Slide
+ * Hides and shows an element without depending on a fixed width or height
+ *
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slide = new Class({
+    Family: 'Jx.Slide',
+    Implements: Jx.Object,
+    Binds: ['handleClick'],
+    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
+    },
+    /**
+     * Method: init
+     * sets up the slide
+     */
+    init: function () {
+
+        this.target = document.id(this.options.target);
+
+        this.target.set('tween', {onComplete: this.setDisplay.bind(this)});
+
+        if ($defined(this.options.trigger)) {
+            this.trigger = document.id(this.options.trigger);
+            this.trigger.addEvent('click', this.handleClick);
+        }
+
+        this.target.store('slider', this);
+
+    },
+    /**
+     * Method: 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 {
+            this.slide('out');
+        }
+    },
+    /**
+     * 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:
+     * dir - the direction to slide (either "in" or "out")
+     */
+    slide: function (dir) {
+        var h;
+        if (dir === 'in') {
+            h = this.target.retrieve(this.options.type);
+            this.target.setStyles({
+                overflow: 'hidden',
+                display: 'block'
+            });
+            this.target.setStyles(this.options.type, 0);
+            this.target.tween(this.options.type, h);
+        } else {
+            if (this.options.type === 'height') {
+                h = this.target.getMarginBoxSize().height;
+            } else {
+                h = this.target.getMarginBoxSize().width;
+            }
+            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
+    },
+    /**
+     * Property: headerState
+     * Hash that holds the open/closed state of each header
+     */
+    headerState: null,
+    init: function() {
+      this.headerState = new Hash();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this plugin to a dataview
+     */
+    attach: function (dataView) {
+        if (!$defined(dataView) && !(dataview instanceof Jx.Panel.DataView)) {
+            return;
+        }
+
+        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: onSlideIn
+     * Called when a group opens.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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: onSlideOut
+     * Called when a group closes.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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: plugin.field.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: field.validator.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * 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
+    },
+    /**
+     * Property: valid
+     * tells whether this field passed validation or not.
+     */
+    valid: null,
+    /**
+     * Property: errors
+     * array of errors found on this field
+     */
+    errors: null,
+    validators : null,
+    /**
+     * 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.errors = [];
+        this.validators = new Hash();
+        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;
+        }
+        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();
+        }
+        //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.set(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);
+    },
+    /**
+     * APIMethod: detach
+     */
+    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;
+    },
+
+    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('jxFieldSuccess').addClass('jxFieldError');
+            this.fireEvent('fieldValidationFailed', [this.field, this]);
+        } else {
+            this.field.domObj.removeClass('jxFieldError').addClass('jxFieldSuccess');
+            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('jxFieldError').removeClass('jxFieldSuccess');
+    },
+    /**
+     * APIMethod: getErrors
+     * USe this method to retrieve all of the errors noted for this field.
+     */
+    getErrors: function () {
+        return this.errors;
+    }
+
+
+});
+// $Id: plugin.form.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: form.validator.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Plugin.Form.Validator
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Form plugin for enforcing validation on the fields 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.Form.Validator = new Class({
+
+    Extends : Jx.Plugin,
+    name: 'Form.Validator',
+
+    options: {
+        /**
+         * Option: fields
+         * This will be key/value pairs for each of the fields as shown here:
+         * {
+         *     fieldID: {
+         *          ... options for Field.Validator plugin ...
+         *     },
+         *     fieldID: {...
+         *     }
+         * }
+         */
+        fields: null,
+
+        fieldDefaults: {
+            validateOnBlur: true,
+            validateOnChange: true
+        },
+
+        validateOnSubmit: true,
+
+        suspendSubmit: false
+    },
+    /**
+     * Property: errorMessagess
+     * element holding
+     */
+    errorMessage: null,
+    /**
+     * 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);
+    },
+    /**
+     * 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 = this.form.getField(key);
+            var p = new Jx.Plugin.Field.Validator(opts);
+            this.plugins.set(key, p);
+            p.attach(field);
+            p.addEvent('fieldValidationFailed', this.bound.failed);
+            p.addEvent('fieldValidationPassed', this.bound.passed);
+
+        }, this);
+
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.form) {
+            document.id(this.form).removeEvent('submit');
+        }
+        this.form = null;
+        this.plugins.each(function(plugin){
+            plugin.detach();
+            plugin = null;
+        },this);
+        this.plugins = null;
+    },
+    /**
+     * 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]);
+        }
+        return valid;
+    },
+    /**
+     * Method: fieldFailed
+     * Refires the fieldValidationFailed event from the field validators it contains
+     */
+    fieldFailed: function (field, validator) {
+        this.fireEvent('fieldValidationFailed', [field, validator]);
+    },
+    /**
+     * Method: fieldPassed
+     * 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;
+    }
+
+
+});
+/**
+ * Class: Jx.Plugin.Toolbar
+ * Toolbar plugin namespace
+ *
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer = {};/**
+ * Class: Jx.Plugin.ToolbarContainer.TabMenu
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * This plugin provides a menu of tabs in a toolbar (similar to the button in firefox at the end of the row of tabs).
+ * It is designed to be used only when the toolbar contains tabs and only when the container is allowed to scroll. Also,
+ * this plugin must be added directly to the Toolbar container. You can get a reference to the container for a
+ * <Jx.TabBox> by doing
+ *
+ * (code)
+ * var tabbox = new Jx.TabBox();
+ * var toolbarContainer = document.id(tabBox.tabBar).getParent('.jxBarContainer').retrieve('jxBarContainer');
+ * (end)
+ *
+ * You can then use the attach method to connect the plugin. Otherwise, you can add it via any normal means to a
+ * directly instantiated Container.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+
+Jx.Plugin.ToolbarContainer.TabMenu = new Class({
+
+    Family: 'Jx.Plugin.ToolbarContainer.TabMenu',
+    Extends: Jx.Plugin,
+
+    Binds: ['addButton'],
+
+    options: {
+    },
+    /**
+     * Property: tabs
+     * holds all of the tabs that we're tracking
+     */
+    tabs: [],
+
+    init: function () {
+        this.parent();
+    },
+
+    attach: function (toolbarContainer) {
+        this.parent(toolbarContainer);
+
+        this.container = toolbarContainer;
+
+        //we will only be used if the container is allowed to scroll
+        if (!this.container.options.scroll) {
+            return;
+        }
+
+        this.menu = new Jx.Menu({},{
+            buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'
+        }).addTo(this.container.controls,'bottom');
+        document.id(this.menu).addClass('jxTabMenuRevealer');
+        this.container.update();
+
+        //go through all of the existing tabs and add them to the menu
+        //grab the toolbar...
+        var tb = document.id(this.container).getElement('ul').retrieve('jxToolbar');
+        tb.list.each(function(item){
+            this.addButton(item);
+        },this);
+
+        //connect to the add event of the toolbar list to monitor the addition of buttons
+        tb.list.addEvent('add',this.addButton);
+    },
+
+    detach: function () {
+        this.parent();
+    },
+
+    addButton: function (item) {
+        var tab;
+        tab = (item instanceof Jx.Tab) ? item : document.id(item).getFirst().retrieve('jxTab');
+
+
+        var l = tab.getLabel();
+        if (!$defined(l)) {
+            l = '';
+        }
+        var mi = new Jx.Menu.Item({
+            label: l,
+            image: tab.options.image,
+            onClick: function() {
+                if (tab.isActive()) {
+                    this.container.scrollIntoView(tab);
+                } else {
+                    tab.setActive(true);
+                }
+            }.bind(this)
+        });
+
+        document.id(tab).store('menuItem', mi);
+
+        tab.addEvent('close', function() {
+            this.menu.remove(mi);
+        }.bind(this));
+
+        this.menu.add([mi]);
+    }
+});
+/**
+ * Class: Jx.Adaptor
+ * Base class for all adaptor implementations. Provides a place to locate all
+ * common code and the Jx.Adaptor namespace.  Since it extends <Jx.Plugin> all
+ * adaptors will be able to be used as plugins for their respective classes.
+ * Also as such, they must have the attach() and detach() methods.
+ * 
+ * Adaptors are specifically used to conform a <Jx.Store> to any one of 
+ * the different widgets (i.e. Jx.Tree, Jx.ListView, etc...) that could
+ * benefit from integration with the store. This approach was taken to minimize 
+ * data access code in the widgets themselves. Widgets should have no idea where 
+ * the data/items come from so that they will be usable in the broadest number
+ * of situations.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor = new Class({
+	
+	Family: 'Jx.Adaptor',
+	Extends: Jx.Plugin,
+	
+	name: 'Jx.Adaptor',
+
+	options: {
+        /**
+         * Option: template
+         * The text template to use in creating the items for this adaptor
+         */
+	    template: '',
+        /**
+         * Option: useTemplate
+         * Whether or not to use the text template above. Defaults to true.
+         */
+	    useTemplate: true,
+        /**
+         * Option: store
+         * The store to use with the adaptor.
+         */
+	    store: null
+	},
+    /**
+     * Property: columnsNeeded
+     * Will hold an array of the column names needed for processing the
+     * template
+     */
+	columnsNeeded: null,
+	
+	init: function () {
+	    this.parent();
+	    
+	    this.store = this.options.store;
+	    
+	    if (this.options.useTemplate && $defined(this.store.getColumns())) {
+	        this.columnsNeeded = this.store.parseTemplate(this.options.template);
+	    }
+	},
+	
+	attach: function (widget) {
+		this.parent(widget);
+		this.widget = widget;
+	},
+	
+	detach: function () {
+		this.parent();
+	}
+	
+});/**
+ * Class: Jx.Adaptor.Tree
+ * This base class is used to change a store (a flat list of records) into the
+ * data structure needed for a Jx.Tree. It will have 2 subclasses:
+ * <Jx.Adapter.Tree.Mptt> and <Jx.Adapter.Tree.Parent>.
+ * 
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree = new Class({
+    
+    Family: 'Jx.Adaptor.Tree',
+    Extends: Jx.Adaptor,
+    
+    Binds: ['fill','checkFolder'],
+    
+    options: {
+        /**
+         * Option: monitorFolders
+         * Determines if this adapter should use monitor the TreeFolder items in
+         * order to request any items they should contain if they are empty.
+         */
+        monitorFolders: false,
+        /**
+         * Option: startingNodeKey
+         * The store primary key to use as the node that we're requesting.
+         * Initially set to -1 to indicate that we're request the first set of
+         * data
+         */
+        startingNodeKey: -1,
+        /**
+         * Option: folderOptions
+         * A Hash containing the options for <Jx.TreeFolder>. Defaults to null.
+         */
+        folderOptions: null,
+        /**
+         * Option: itemOptions
+         * A Hash containing the options for <Jx.TreeItem>. Defaults to null.
+         */
+        itemOptions: null
+    },
+    /**
+     * Property: folders
+     * A Hash containing all of the <Jx.TreeFolders> in this tree.
+     */
+    folders: null,
+    /**
+     * Property: currentRecord
+     * An integer indicating the last position we were at in the store. Used to
+     * allow the adaptor to pick up rendering items after we request additional
+     * data.
+     */
+    currentRecord: -1,
+    init: function() {
+      this.folders = new Hash();
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this adaptor to a specific tree instance.
+     *
+     * Parameters:
+     * tree - an instance of <Jx.Tree>
+     */
+    attach: function (tree) {
+        this.parent(tree);
+        
+        this.tree = tree;
+        
+        if (this.options.monitorFolders) {
+            this.strategy = this.store.getStrategy('progressive');
+        
+            if (!$defined(this.strategy)) {
+                this.strategy = new Jx.Store.Strategy.Progressive({
+                    dropRecords: false,
+                    getPaginationParams: function () { return {}; }
+                });
+                this.store.addStrategy(this.strategy);
+            } else {
+                this.strategy.options.dropRecords = false;
+                this.strategy.options.getPaginationParams = function () { return {}; };
+            }
+            
+        }
+        
+        this.store.addEvent('storeDataLoaded', this.fill);
+        
+        
+    },
+    /**
+     * APIMethod: detach
+     * removes this adaptor from the current tree.
+     */
+    detach: function () {
+    	this.parent();
+    	this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+    /**
+     * APIMethod: firstLoad
+     * Method used to start the first store load.
+     */
+    firstLoad: function () {
+    	//initial store load
+    	this.busy = 'tree';
+    	this.tree.setBusy(true);
+        this.store.load({
+            node: this.options.startingNodeKey
+        });
+    },
+    
+    /**
+     * APIMethod: fill
+     * This function will start at this.currentRecord and add the remaining
+     * items to the tree. 
+     */
+    fill: function () {
+    	if (this.busy == 'tree') {
+    		this.tree.setBusy(false);
+    		this.busy = 'none';
+    	} else if (this.busy == 'folder') {
+    		this.busyFolder.setBusy(false);
+    		this.busy = 'none';
+    	}
+        var l = this.store.count() - 1;
+        for (var i = this.currentRecord + 1; i <= l; i++) {
+            var template = this.store.fillTemplate(i,this.options.template,this.columnsNeeded);
+
+            var item;
+            if (this.hasChildren(i)) {
+                //add as folder
+                var item = new Jx.TreeFolder($merge(this.options.folderOptions, {
+                    label: template
+                }));
+                
+                if (this.options.monitorFolders) {
+                	item.addEvent('disclosed', this.checkFolder);
+                }
+                
+                this.folders.set(i,item);
+            } else {
+                //add as item
+                var item = new Jx.TreeItem($merge(this.options.itemOptions, {
+                    label: template
+                }));
+            }
+            document.id(item).store('index', i);
+            document.id(item).store('jxAdaptor', this);
+            //check for a parent
+            if (this.hasParent(i)) {
+                //add as child of parent
+                var p = this.getParentIndex(i);
+                var folder = this.folders.get(p);
+                folder.add(item);
+            } else {
+                //otherwise add to the tree itself
+                this.tree.add(item);
+            }
+        }
+        this.currentRecord = l;
+    },
+    /**
+     * Method: checkFolder
+     * Called by the disclose event of the tree to determine if we need to
+     * request additional items for a branch of the tree.
+     */
+    checkFolder: function (folder) {
+        var items = folder.items();
+        if (!$defined(items) || items.length === 0) {
+            //get items via the store
+        	var index = document.id(folder).retrieve('index');
+        	var node = this.store.get('primaryKey', index);
+        	this.busyFolder = folder;
+        	this.busyFolder.setBusy(true);
+        	this.busy = 'folder';
+            this.store.load({
+                node: node
+            });
+        }
+    },
+    /**
+     * Method: hasChildren
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has any children.
+     */
+    hasChildren: $empty,
+    /**
+     * Method: hasParent
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has a parent node.
+     */
+    hasParent: $empty,
+    /**
+     * Method: getParentIndex
+     * Virtual method to be overridden by sublcasses. Determines the store index
+     * of the parent node.
+     */
+    getParentIndex: $empty
+    
+    
+    
+});/**
+ * Class: Jx.Adaptor.Tree.Mptt
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * This class requires an MPTT (Modified Preorder Tree Traversal) table. The MPTT
+ * has a 'left' and a 'right' column that indicates the order of nesting. For 
+ * more details see the sitepoint.com article at 
+ * http://articles.sitepoint.com/article/hierarchical-data-database
+ * 
+ * if useAjax option is set to true then this adapter will send an Ajax request
+ * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
+ * to request additional nodes.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Mptt = new Class({
+    
+
+    Family: 'Jx.Adaptor.Tree.Mptt',
+    Extends: Jx.Adaptor.Tree,
+    
+    name: 'tree.mptt',
+    
+    options: {
+        left: 'left',
+        right: 'right'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+        var l = this.store.get(this.options.left, index).toInt();
+        var r = this.store.get(this.options.right, index).toInt();
+        return (l + 1 !== r);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        var i = this.getParentIndex(index);
+        if ($defined(i)) {
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        var l = this.store.get(this.options.left, index).toInt();
+        var r = this.store.get(this.options.right, index).toInt();
+        for (var i = index-1; i >= 0; i--) {
+            var pl = this.store.get(this.options.left, i).toInt();
+            var pr = this.store.get(this.options.right, i).toInt();
+            if (pl < l && pr > r) {
+                return i;
+            }
+        }
+        return null;
+    }
+});/**
+ * Class: Jx.Adapter.Tree.Parent
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * Basically, the store needs to have a column that will indicate the
+ * parent of each row. The root(s) of the tree should be indicated by a "-1" 
+ * in this column. The name of the "parent" column is configurable in the 
+ * options.
+ * 
+ * if the monitorFolders option is set to true then this adapter will send
+ * an Ajax request to the server, through the store's strategy (should be
+ * Jx.Store.Strategy.Progressive) to request additional nodes. Also, a column
+ * indicating whether this is a folder needs to be set as there is no way to
+ * tell if a node has children without it.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Parent = new Class({
+    
+    Family: 'Jx.Adaptor.Tree.Parent',
+    Extends: Jx.Adaptor.Tree,
+    
+    options: {
+        parentColumn: 'parent',
+        folderColumn: 'folder'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+    	return this.store.get(this.options.folderColumn, index);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        if (this.store.get(this.options.parentColumn, index).toInt() !== -1) {
+            return true;
+        } 
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        //get the parent based on the index
+        var pk = this.store.get(this.options.parentColumn, index);
+        return this.store.findByColumn('primaryKey', pk);
+    }
+});/**
+ * Class: Jx.Adaptor.Combo
+ * The namespace for all combo adaptors
+ */
+Jx.Adaptor.Combo = {}
+Jx.Adaptor.Combo.Fill = new Class({
+
+    Family: 'Jx.Adaptor.Combo.Fill',
+    Extends: Jx.Adaptor,
+    name: 'combo.fill',
+    Binds: ['fill'],
+
+    /**
+     * Note: option.template is used for constructing the text for the label
+     */
+    options: {
+        /**
+         * Option: imagePathColumn
+         * points to a store column that holds the image information
+         * for the combo items.
+         */
+        imagePathColumn: null,
+        /**
+         * Option: imageClassColumn
+         * Points to a store column that holds the image class
+         * information for the combo items
+         */
+        imageClassColumn: null,
+        /**
+         * Option: selectedFn
+         * This should be a function that could be run to determine if
+         * an item should be selected. It will get passed the current store
+         * record as the only parameter. It should return either true or false.
+         */
+        selectedFn: null,
+        /**
+         * Option: noRepeats
+         * This option allows you to use any store even if it has duplicate
+         * values in it. With this option set to true the adaptor will keep
+         * track of all of teh labels it adds and will not add anything that's
+         * a duplicate.
+         */
+        noRepeats: false
+    },
+
+    labels: null,
+
+    init: function () {
+        this.parent();
+
+        if (this.options.noRepeat) {
+            this.labels = [];
+        }
+    },
+
+    attach: function (combo) {
+        this.parent(combo);
+
+        this.store.addEvent('storeDataLoaded', this.fill);
+        if (this.store.loaded) {
+            this.fill();
+        }
+    },
+
+    detach: function () {
+        this.parent();
+
+        this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+
+    fill: function () {
+        //empty the combo
+        this.widget.empty();
+        //reset the store and cycle through creating the objects
+        //to pass to combo.add()
+        this.store.first();
+        var items = [];
+        this.store.each(function(record){
+            var template = this.store.fillTemplate(record,this.options.template,this.columnsNeeded);
+            if (!this.options.noRepeat || (this.options.noRepeat && !this.labels.contains(template))) {
+                var selected = false;
+                if ($type(this.options.selectedFn) == 'function') {
+                    selected = this.options.selectedFn.run(record);
+                }
+                var obj = {
+                    label: template,
+                    image: record.get(this.options.imagePathColumn),
+                    imageClass: record.get(this.options.imageClassColumn),
+                    selected: selected
+                }
+                items.push(obj);
+
+                if (this.options.noRepeat) {
+                    this.labels.push(template);
+                }
+            }
+
+        },this);
+        //pass all of the objects at once
+        this.widget.add(items);
+    }
+});// $Id: context.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.Menu.Context
+ *
+ * Extends: Jx.Menu
+ *
+ * A <Jx.Menu> that has no button but can be opened at a specific
+ * browser location to implement context menus (for instance).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * TODO - add open/close events?
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Context = new Class({
+    Family: 'Jx.Menu.Context',
+    Extends: Jx.Menu,
+
+    parameters: ['id'],
+
+    /**
+     * APIMethod: render
+     * create a new context menu
+     */
+    render: function() {
+        this.id = document.id(this.options.id);
+        if (this.id) {
+            this.id.addEvent('contextmenu', this.show.bindWithEvent(this));
+        }
+        this.parent();
+    },
+    /**
+     * Method: show
+     * Show the context menu at the location of the mouse click
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    show : function(e) {
+        if (this.list.count() ==0) {
+            return;
+        }
+        
+        this.target = e.target;
+
+        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.setStyles({
+           width: null,
+           height: null
+         });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+        this.position(this.contentContainer, document.body, {
+            horizontal: [e.page.x + ' left'],
+            vertical: [e.page.y + ' top', e.page.y + ' bottom'],
+            offsets: this.chromeOffsets
+        });
+
+        this.contentContainer.setStyle('visibility','');
+        this.showChrome(this.contentContainer);
+
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keyup', this.bound.keypress);
+
+        e.stop();
+    }
+});// $Id: menu.separator.js 915 2010-05-23 13:44:48Z pagameba $
+/**
+ * Class: Jx.Menu.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A convenience class to create a visual separator in a menu.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Separator = new Class({
+    Family: 'Jx.Menu.Separator',
+    Extends: Jx.Widget,
+    /**
+     * Property: domObj
+     * {HTMLElement} the HTML element that the separator is contained
+     * within
+     */
+    domObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu>, <Jx.Menu.SubMenu>} the menu that the separator is in.
+     */
+    owner: null,
+    options: {
+        template: "<li class='jxMenuItemContainer jxMenuItem'><span class='jxMenuSeparator'>&nbsp;</span></li>"
+    },
+    classes: new Hash({
+        domObj: 'jxMenuItem'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of a menu separator
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the ownder of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: $empty,
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty
+});// $Id: submenu.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.Menu.SubMenu
+ *
+ * Extends: <Jx.Menu.Item>
+ *
+ * Implements: <Jx.AutoPosition>, <Jx.Chrome>
+ *
+ * A sub menu contains menu items within a main menu or another
+ * sub menu.
+ *
+ * The structure of a SubMenu is the same as a <Jx.Menu.Item> with
+ * an additional unordered list element appended to the container.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.SubMenu = new Class({
+    Family: 'Jx.Menu.SubMenu',
+    Extends: Jx.Menu.Item,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML container for the sub menu.
+     */
+    subDomObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu> or <Jx.SubMenu>} the menu or sub menu that this sub menu
+     * belongs
+     */
+    owner: null,
+    /**
+     * Property: visibleItem
+     * {<Jx.MenuItem>} the visible item within the menu
+     */
+    visibleItem: null,
+    /**
+     * Property: list
+     * {<Jx.List>} a list to manage menu items
+     */
+    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']
+        }
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.SubMenu
+     */
+    render: function() {
+        this.parent();
+        this.open = false;
+
+        this.menu = new Jx.Menu(null, {
+            position: this.options.position
+        });
+        this.menu.domObj = this.domObj;
+    },
+    cleanup: function() {
+      this.menu.domObj = null;
+      this.menu.destroy();
+      this.menu = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this sub menu
+     *
+     * Parameters:
+     * obj - {Object} the owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: show
+     * Show the sub menu
+     */
+    show: function() {
+        if (this.open || this.menu.list.count() == 0) {
+            return;
+        }
+        this.menu.show();
+        this.open = true;
+        // this.setActive(true);
+    },
+
+    eventInMenu: function(e) {
+        if (this.visibleItem &&
+            this.visibleItem.eventInMenu &&
+            this.visibleItem.eventInMenu(e)) {
+            return true;
+        }
+        return document.id(e.target).descendantOf(this.domObj) ||
+               this.menu.eventInMenu(e);
+    },
+
+    /**
+     * Method: hide
+     * Hide the sub menu
+     */
+    hide: function() {
+        if (!this.open) {
+            return;
+        }
+        this.open = false;
+        this.menu.hide();
+        this.visibleItem = null;
+    },
+    /**
+     * Method: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     * can be added by passing multiple arguments to this function.
+     */
+    add: function(item, position) {
+        this.menu.add(item, position, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a menu item from the menu
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to remove
+     */
+    remove: function(item) {
+        this.menu.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.menu.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the sub menu
+     */
+    empty: function() {
+      this.menu.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the sub menu
+     *
+     * Parameters:
+     * e - {Event} the event that triggered the menu being
+     * deactivated.
+     */
+    deactivate: function(e) {
+        if (this.owner) {
+            this.owner.deactivate(e);
+        }
+    },
+    /**
+     * Method: isActive
+     * Indicate if this sub menu is active
+     *
+     * Returns:
+     * {Boolean} true if the <Jx.Menu> that ultimately contains
+     * this sub menu is active, false otherwise.
+     */
+    isActive: function() {
+        if (this.owner) {
+            return this.owner.isActive();
+        } else {
+            return false;
+        }
+    },
+    /**
+     * Method: setActive
+     * Set the active state of the <Jx.Menu> that contains this sub menu
+     *
+     * Parameters:
+     * isActive - {Boolean} the new active state
+     */
+    setActive: function(isActive) {
+        if (this.owner && this.owner.setActive) {
+            this.owner.setActive(isActive);
+        }
+    },
+    /**
+     * Method: setVisibleItem
+     * Set a sub menu of this menu to be visible and hide the previously
+     * visible one.
+     *
+     * Parameters:
+     * obj - {<Jx.SubMenu>} the sub menu that should be visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    }
+});// $Id: snap.js 626 2009-11-20 13:22:22Z pagameba $
+/**
+ * Class: Jx.Splitter.Snap
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class to create an element that can snap a split panel open or
+ * closed.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+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
+     * clicked).
+     */
+    snap: null,
+    /**
+     * Property: element
+     * {HTMLElement} An element of the <Jx.Splitter> that gets controlled
+     * by this snap
+     */
+    element: null,
+    /**
+     * Property: splitter
+     * {<Jx.Splitter>} the splitter that this snap is associated with.
+     */
+    splitter: null,
+    /**
+     * Property: layout
+     * {String} track the layout of the splitter for convenience.
+     */
+    layout: 'vertical',
+    /**
+     * 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.
+     */
+    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.layout = this.splitter.options.layout;
+        var jxo = jxl.options;
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            this.originalSize = size.height;
+            this.minimumSize = jxo.minHeight ? jxo.minHeight : 0;
+        } else {
+            this.originalSize = size.width;
+            this.minimumSize = jxo.minWidth ? jxo.minWidth : 0;
+        }
+        this.events.each(function(eventName) {
+            this.snap.addEvent(eventName, this.toggleElement.bind(this));
+        }, this);
+    },
+
+    /**
+     * Method: toggleElement
+     * Snap the element open or closed.
+     */
+    toggleElement: function() {
+        var size = this.element.getContentBoxSize();
+        var newSize = {};
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                newSize.height = this.originalSize;
+            } else {
+                this.originalSize = size.height;
+                newSize.height = this.minimumSize;
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                newSize.width = this.originalSize;
+            } else {
+                this.originalSize = size.width;
+                newSize.width = this.minimumSize;
+            }
+        }
+        this.element.resize(newSize);
+        this.splitter.sizeChanged();
+    },
+
+    /**
+     * Method: sizeChanged
+     * Handle the size of the element changing to see if the
+     * toggle state has changed.
+     */
+    sizeChange: function() {
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        }
+    }
+});// $Id: tab.js 924 2010-05-26 16:03:06Z conrad.barthelmes $
+/**
+ * Class: Jx.Tab
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * actual tab itself) in order to be useful.  Alternately, you can use
+ * a <Jx.TabBox> which combines both into a single control at the cost of
+ * some flexibility in layout options.
+ *
+ * A tab is a <Jx.ContentLoader> and you can specify the initial content of
+ * the tab using any of the methods supported by
+ * <Jx.ContentLoader::loadContent>.  You can acccess the actual DOM element
+ * that contains the content (if you want to dynamically insert content
+ * for instance) via the <Jx.Tab::content> property.
+ *
+ * A tab is a button of type *toggle* which means that it emits the *up*
+ * and *down* events.
+ *
+ * Example:
+ * (code)
+ * var tab1 = new Jx.Tab({
+ *     label: 'tab 1',
+ *     content: 'content1',
+ *     onDown: function(tab) {
+ *         console.log('tab became active');
+ *     },
+ *     onUp: function(tab) {
+ *         console.log('tab became inactive');
+ *     }
+ * });
+ * (end)
+ *
+ *
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Tab = new Class({
+    Family: 'Jx.Tab',
+    Extends: Jx.Button,
+    /**
+     * Property: content
+     * {HTMLElement} The content area that is displayed when the tab is
+     * active.
+     */
+    content: null,
+
+    options: {
+        /* Option: toggleClass
+         * the CSS class to use for the button, 'jxTabToggle' by default
+         */
+        toggleClass: 'jxTabToggle',
+        /* Option: pressedClass
+         * the CSS class to use when the tab is pressed, 'jxTabPressed' by
+         * default
+         */
+        pressedClass: 'jxTabPressed',
+        /* Option: activeClass
+         * the CSS class to use when the tab is active, 'jxTabActive' by 
+         * default.
+         */
+        activeClass: 'jxTabActive',
+        /* Option: activeTabClass
+         * the CSS class to use on the content area of the active tab,
+         * 'tabContentActive' by default.
+         */
+        activeTabClass: 'tabContentActive',
+        /* Option: template
+         * the HTML template for a tab
+         */
+        template: '<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon" src="'+Jx.aPixel.src+'"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></span>',
+        /* Option: contentTemplate
+         * the HTML template for a tab's content area
+         */
+        contentTemplate: '<div class="tabContent"></div>',
+        /* Option: close
+         * {Boolean} can the tab be closed by the user?  False by default.
+         */
+        close: false,
+        /* Option: shouldClose
+         * {Mixed} when a tab is closeable, the shouldClose option is checked
+         * first to see if the tab should close.  You can provide a function
+         * for this option that can be used to return a boolean value.  This
+         * is useful if your tab contains something the user can edit and you
+         * want to see if they want to discard the changes before closing.
+         * The default value is true, meaning the tab will close immediately.
+         * (code)
+         * new Jx.Tab({
+         *   label: 'test close',
+         *   close: true,
+         *   shouldClose: function() {
+         *     return window.confirm('Are you sure?');
+         *   }
+         * });
+         * (end)
+         */
+        shouldClose: true
+    },
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxTabContainer',
+        domA: 'jxTab',
+        domImg: 'jxTabIcon',
+        domLabel: 'jxTabLabel',
+        domClose: 'jxTabClose',
+        content: 'tabContent'
+    }),
+
+    /**
+     * Method: render
+     * Create a new instance of Jx.Tab.  Any layout options passed are used
+     * to create a <Jx.Layout> for the tab content area.
+     */
+    render : function( ) {
+        this.options = $merge(this.options, {toggle:true});
+        this.parent();
+        this.domObj.store('jxTab', this);
+        this.processElements(this.options.contentTemplate, this.classes);
+        new Jx.Layout(this.content, this.options);
+        
+        // load content onDemand if needed
+        if(!this.options.loadOnDemand || this.options.active) {
+          this.loadContent(this.content);
+          // set active if needed
+          if(this.options.active) {
+            this.clicked();
+          }
+        }
+        this.addEvent('down', function(){
+            this.content.addClass(this.options.activeTabClass);
+        }.bind(this));
+        this.addEvent('up', function(){
+            this.content.removeClass(this.options.activeTabClass);
+        }.bind(this));
+
+        //remove the close button if necessary
+        if (this.domClose) {
+            if (this.options.close) {
+                this.domObj.addClass('jxTabClose');
+                this.domClose.addEvent('click', (function(){
+                  var shouldClose = true;
+                  if ($defined(this.options.shouldClose)) {
+                    if (typeof this.options.shouldClose == 'function') {
+                      shouldClose = this.options.shouldClose();
+                    } else {
+                      shouldClose = this.options.shouldClose;
+                    }
+                  }
+                  if (shouldClose) {
+                    this.fireEvent('close');
+                  }
+                }).bind(this));
+            } else {
+                this.domClose.dispose();
+            }
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     */
+    clicked : function(evt) {
+      if(this.options.enabled) {
+        // just set active when caching is enabled
+        if(this.contentIsLoaded && this.options.cacheContent) {
+          this.setActive(true);
+        // load on demand or reload content if caching is disabled
+        }else if(this.options.loadOnDemand || !this.options.cacheContent){
+          this.loadContent(this.content);
+          this.addEvent('contentLoaded', function(ev) {
+            //this.setBusy(false);
+            this.setActive(true);
+          }.bind(this));
+        }else{
+          this.setActive(true);
+        }
+      }
+    }
+});
+
+/* keep the old location temporarily */
+Jx.Button.Tab = new Class({
+  Extends: Jx.Tab,
+  init: function() {
+    if (console.warn) {
+      console.warn('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    } else {
+      console.log('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    }
+    this.parent();
+  }
+});// $Id: tabset.js 848 2010-04-16 12:43:31Z pagameba $
+/**
+ * Class: Jx.TabSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A TabSet manages a set of <Jx.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.Tab> that are to be managed
+ * as a set have to be added to both a TabSet and a <Jx.Toolbar>.  The content
+ * areas of the <Jx.Tab>s are sized to fit the content area that the TabSet
+ * is managing.
+ *
+ * Example:
+ * (code)
+ * var tabBar = new Jx.Toolbar('tabBar');
+ * var tabSet = new Jx.TabSet('tabArea');
+ *
+ * var tab1 = new Jx.Tab('tab 1', {contentID: 'content1'});
+ * var tab2 = new Jx.Tab('tab 2', {contentID: 'content2'});
+ * var tab3 = new Jx.Tab('tab 3', {contentID: 'content3'});
+ * var tab4 = new Jx.Tab('tab 4', {contentURL: 'test_content.html'});
+ *
+ * tabSet.add(t1, t2, t3, t4);
+ * tabBar.add(t1, t2, t3, t4);
+ * (end)
+ *
+ * Events:
+ * tabChange - the current tab has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabSet = new Class({
+    Family: 'Jx.TabSet',
+    Extends: Jx.Object,
+    /**
+     * Property: tabs
+     * {Array} array of tabs that are managed by this tab set
+     */
+    tabs: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} The HTML element that represents this tab set in the DOM.
+     * The content areas of each tab are sized to fill the domObj.
+     */
+    domObj : null,
+    /**
+     * 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.
+     */
+    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 = document.id(this.options.domObj);
+        if (!this.domObj.hasClass('jxTabSetContainer')) {
+            this.domObj.addClass('jxTabSetContainer');
+        }
+        this.setActiveTabFn = this.setActiveTab.bind(this);
+    },
+    /**
+     * Method: resizeTabBox
+     * Resize the tab set content area and propogate the changes to
+     * each of the tabs managed by the tab set.
+     */
+    resizeTabBox: function() {
+        if (this.activeTab && this.activeTab.content.resize) {
+            this.activeTab.content.resize({forceResize: true});
+        }
+    },
+
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab set.  More
+     * than one tab can be added by passing extra parameters to this method.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(tab) {
+            if (tab instanceof Jx.Tab) {
+                tab.addEvent('down',this.setActiveTabFn);
+                tab.tabSet = this;
+                this.domObj.appendChild(tab.content);
+                this.tabs.push(tab);
+                if ((!this.activeTab || tab.options.active) && tab.options.enabled) {
+                    tab.options.active = false;
+                    tab.setActive(true);
+                }
+            }
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from this TabSet.  Note that it is the caller's responsibility
+     * to remove the tab from the <Jx.Toolbar>.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove: function(tab) {
+        if (tab instanceof Jx.Tab && this.tabs.indexOf(tab) != -1) {
+            this.tabs.erase(tab);
+            if (this.activeTab == tab) {
+                if (this.tabs.length) {
+                    this.tabs[0].setActive(true);
+                }
+            }
+            tab.removeEvent('down',this.setActiveTabFn);
+            tab.content.dispose();
+        }
+    },
+    /**
+     * Method: setActiveTab
+     * Set the active tab to the one passed to this method
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to make active.
+     */
+    setActiveTab: function(tab) {
+        if (this.activeTab && this.activeTab != tab) {
+            this.activeTab.setActive(false);
+        }
+        this.activeTab = tab;
+        if (this.activeTab.content.resize) {
+          this.activeTab.content.resize({forceResize: true});
+        }
+        this.fireEvent('tabChange', [this, tab]);
+    }
+});
+
+
+
+// $Id: tabbox.js 626 2009-11-20 13:22:22Z pagameba $
+/**
+ * Class: Jx.TabBox
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * a TabBox, then tabs only have to be added to the TabBox rather than to
+ * both a <Jx.TabSet> and a <Jx.Toolbar>.
+ *
+ * Example:
+ * (code)
+ * var tabBox = new Jx.TabBox('subTabArea', 'top');
+ *
+ * var tab1 = new Jx.Button.Tab('Tab 1', {contentID: 'content4'});
+ * var tab2 = new Jx.Button.Tab('Tab 2', {contentID: 'content5'});
+ *
+ * tabBox.add(tab1, tab2);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabBox = new Class({
+    Family: 'Jx.TabBox',
+    Extends: Jx.Widget,
+    options: {
+        /* Option: parent
+         * a DOM element to add the tab box to
+         */
+        parent: null,
+        /* Option: position
+         * the position of the tab bar in the box, one of 'top', 'right',
+         * 'bottom' or 'left'.  Top by default.
+         */
+        position: 'top',
+        /* Option: height
+         * a fixed height in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        height: null,
+        /* Option: width
+         * a fixed width in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        width: null,
+        /* Option: scroll
+         * should the tab bar scroll its tabs if there are too many to fit
+         * in the toolbar, true by default
+         */
+        scroll:true
+    },
+
+    /**
+     * Property: tabBar
+     * {<Jx.Toolbar>} the toolbar for this tab box.
+     */
+    tabBar: null,
+    /**
+     * Property: tabSet
+     * {<Jx.TabSet>} the tab set for this tab box.
+     */
+    tabSet: null,
+    /**
+     * APIMethod: render
+     * Create a new instance of a TabBox.
+     */
+    render : function() {
+        this.parent();
+        this.tabBar = new Jx.Toolbar({
+            position: this.options.position,
+            scroll: this.options.scroll
+        });
+        this.panel = new Jx.Panel({
+            toolbars: [this.tabBar],
+            hideTitle: true,
+            height: this.options.height,
+            width: this.options.width
+        });
+        this.panel.domObj.addClass('jxTabBox');
+        this.tabSet = new Jx.TabSet(this.panel.content);
+        this.tabSet.addEvent('tabChange', function(tabSet, tab) {
+            this.showItem(tab);
+        }.bind(this.tabBar));
+        this.domObj = this.panel.domObj;
+        /* when the panel changes size, the tab set needs to update
+         * the content areas.
+         */
+         this.panel.addEvent('sizeChange', (function() {
+             this.tabSet.resizeTabBox();
+             this.tabBar.domObj.getParent('.jxBarContainer').retrieve('jxBarContainer').update();
+             this.tabBar.domObj.getParent('.jxBarContainer').addClass('jxTabBar'+this.options.position.capitalize());
+         }).bind(this));
+        /* when tabs are added or removed, we might need to layout
+         * the panel if the toolbar is or becomes empty
+         */
+        this.tabBar.addEvents({
+            add: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this),
+            remove: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this)
+        });
+        /* trigger an initial resize when first added to the DOM */
+        this.addEvent('addTo', function() {
+            this.domObj.resize({forceResize: true});
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabBox.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab box.  More
+     * than one tab can be added by passing extra parameters to this method.
+     * Unlike <Jx.TabSet>, tabs do not have to be added to a separate
+     * <Jx.Toolbar>.
+     */
+    add : function() {
+        this.tabBar.add.apply(this.tabBar, arguments);
+        this.tabSet.add.apply(this.tabSet, arguments);
+        $A(arguments).flatten().each(function(tab){
+            tab.addEvents({
+                close: (function(){
+                    this.tabBar.remove(tab);
+                    this.tabSet.remove(tab);
+                }).bind(this)
+            });
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove : function(tab) {
+        this.tabBar.remove(tab);
+        this.tabSet.remove(tab);
+    }
+});
+// $Id: toolbar.separator.js 626 2009-11-20 13:22:22Z pagameba $
+/**
+ * Class: Jx.Toolbar.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class that represents a visual separator in a <Jx.Toolbar>
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar.Separator = new Class({
+    Family: 'Jx.Toolbar.Separator',
+    Extends: Jx.Widget,
+    /**
+     * APIMethod: render
+     * Create a new Jx.Toolbar.Separator
+     */
+    render: function() {
+        this.domObj = new Element('li', {'class':'jxToolItem'});
+        this.domSpan = new Element('span', {'class':'jxBarSeparator'});
+        this.domObj.appendChild(this.domSpan);
+    }
+});
+// $Id: tree.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.Tree
+ *
+ * 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'],
+    pluginNamespace: 'Tree',
+    /**
+     * APIProperty: selection
+     * {<Jx.Selection>} the selection object for this tree.
+     */
+    selection: null,
+    /**
+     * Property: ownsSelection
+     * {Boolean} indicates if this object created the <Jx.Selection> object
+     * or not.  If true then the selection object will be destroyed when the
+     * tree is destroyed, otherwise the selection object will not be
+     * destroyed.
+     */
+    ownsSelection: false,
+    /**
+     * Property: list
+     * {<Jx.List>} the list object is used to manage the DOM elements of the
+     * items added to the tree.
+     */
+    list: null,
+    dirty: true,
+    /**
+     * APIProperty: domObj
+     * {HTMLElement} the DOM element that contains the visual representation
+     * of the tree.
+     */
+    domObj: null,
+    options: {
+        /**
+         * Option: select
+         * {Boolean} are items in the tree selectable?  See <Jx.Selection>
+         * for other options relating to selections that can be set here.
+         */
+        select: true,
+        /**
+         * Option: template
+         * the default HTML template for a tree can be overridden
+         */
+        template: '<ul class="jxTreeRoot"></ul>'
+    },
+    /**
+     * APIProperty: classes
+     * {Hash} a hash of property to CSS class names for extracting references
+     * to DOM elements from the supplied templates.  Requires
+     * domObj element, anything else is optional.
+     */
+    classes: new Hash({domObj: 'jxTreeRoot'}),
+    
+    frozen: false,
+    
+    /**
+     * APIMethod: render
+     * Render the Jx.Tree.
+     */
+    render: function() {
+        this.parent();
+        if ($defined(this.options.container) &&
+            document.id(this.options.container)) {
+            this.domObj = this.options.container;
+        }
+
+        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.bound.select = function(item) {
+            this.fireEvent('select', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.unselect = function(item) {
+            this.fireEvent('unselect', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.onAdd = function(item) {this.update();}.bind(this);
+        this.bound.onRemove = function(item) {this.update();}.bind(this);
+
+        if (this.selection && this.ownsSelection) {
+            this.selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+
+        this.list = new Jx.List(this.domObj, {
+                hover: true,
+                press: true,
+                select: true,
+                onAdd: this.bound.onAdd,
+                onRemove: this.bound.onRemove
+            }, this.selection);
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * APIMethod: freeze
+     * stop the tree from processing updates every time something is added or
+     * removed.  Used for bulk changes, call thaw() when done updating.  Note
+     * the tree will still display the changes but it will delay potentially
+     * expensive recursion across the entire tree on every change just to
+     * update visual styles.
+     */
+    freeze: function() { this.frozen = true; },
+    /**
+     * APIMethod: thaw
+     * unfreeze the tree and recursively update styles
+     */
+    thaw: function() { this.frozen = false; this.update(true); },
+    
+    setDirty: function(state) {
+      this.dirty = state;
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+
+    /**
+     * APIMethod: add
+     * add one or more items to the tree at a particular position in the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        if ($type(item) == 'array') {
+            item.each(function(what){ this.add(what, position); }.bind(this) );
+            return;
+        }
+        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);
+        item.owner = this;
+        this.list.add(item, position);
+        this.setDirty(true);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        item.removeEvents('add');
+        item.removeEvents('remove');
+        item.removeEvents('disclose');
+        item.owner = null;
+        this.list.remove(item);
+        item.setSelection(null);
+        this.setDirty(true);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        item.owner = null;
+        withItem.owner = this;
+        this.list.replace(item, withItem);
+        withItem.setSelection(this.selection);
+        item.setSelection(null);
+        this.setDirty(true);
+        return this;
+    },
+
+    /**
+     * Method: cleanup
+     * Clean up a Jx.Tree instance
+     */
+    cleanup: function() {
+        // stop updates when removing existing items.
+        this.freeze();
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+            this.selection.destroy();
+            this.selection = null;
+        }
+        this.list.removeEvents({
+          remove: this.bound.onRemove,
+          add: this.bound.onAdd
+        });
+        this.list.destroy();
+        this.list = null;
+        this.bound.select = null;
+        this.bound.unselect = null;
+        this.bound.onRemove = null;
+        this.bound.onAdd = null;
+        this.parent();
+    },
+    
+    /**
+     * 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?
+     */
+    update: function(shouldDescend, isLast) {
+        // since the memory leak cleanup, it seems that update gets called
+        // from the bound onRemove event after the list has been cleaned
+        // up.  I suspect that there is a delayed function call for IE in
+        // event handling (or some such thing) PS
+        if (!this.list) return;
+        if (this.frozen) return;
+        
+        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);
+            }
+        });
+        this.setDirty(false);
+    },
+
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.list.items().map(function(item) {
+            return item.retrieve('jxTreeItem');
+        });
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this tree and any folders in it
+     */
+    empty: function() {
+        this.list.items().each(function(item){
+          var f = item.retrieve('jxTreeItem');
+          if (f && f instanceof Jx.TreeFolder) {
+            f.empty();
+          }
+          if (f && f instanceof Jx.TreeItem) {
+            this.remove(f);
+            f.destroy();
+          }
+        }, this);
+        this.setDirty(true);
+    },
+
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return false;
+        }
+        //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.getLabel() == name) {
+                if (path.length > 0) {
+                    var folder = item.retrieve('jxTreeFolder');
+                    if (folder) {
+                        result = folder.findChild(path);
+                    }
+                } else {
+                    result = treeItem;
+                }
+            }
+            return result;
+        });
+        return result;
+    },
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object to be used by this tree.  Used primarily
+     * by <Jx.TreeFolder> to propogate a single selection object throughout a
+     * tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining
+     */
+    setSelection: function(selection) {
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents(this.bound);
+            this.selection.destroy();
+            this.ownsSelection = false;
+        }
+        this.selection = selection;
+        this.list.setSelection(selection);
+        this.list.each(function(item) {
+            item.retrieve('jxTreeItem').setSelection(selection);
+        });
+        return this;
+    }
+});
+
+// $Id: treeitem.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.TreeItem
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * is responsible for changing the style of the selected item in the tree
+ * and for tracking selection if that is important.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - triggered when the tree item is clicked
+ *
+ * Implements:
+ * Events - MooTools Class.Extras
+ * Options - MooTools Class.Extras
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeItem = new Class ({
+    Family: 'Jx.TreeItem',
+    Extends: Jx.Widget,
+    selection: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} a reference to the HTML element that is the TreeItem
+     * in the DOM
+     */
+    domObj : null,
+    /**
+     * Property: owner
+     * {Object} the folder or tree that this item belongs to
+     */
+    owner: null,
+    options: {
+        /* Option: label
+         * {String} the label to display for the TreeItem
+         */
+        label: '',
+        /* Option: contextMenu
+         * {<Jx.ContextMenu>} the context menu to trigger if there
+         * is a right click on the node
+         */
+        contextMenu: null,
+        /* Option: enabled
+         * {Boolean} the initial state of the TreeItem.  If the
+         * TreeItem is not enabled, it cannot be clicked.
+         */
+        enabled: true,
+        selectable: true,
+        /* Option: image
+         * {String} URL to an image to use as the icon next to the
+         * label of this TreeItem
+         */
+        image: null,
+        /* Option: imageClass
+         * {String} CSS class to apply to the image, useful for using CSS
+         * sprites
+         */
+        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>',
+        busyMask: {
+          message: null
+        }
+    },
+    classes: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel'
+    }),
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeItem with the associated options
+     */
+    render : function() {
+        this.parent();
+
+        this.domObj = this.elements.get('jxTreeContainer');
+        this.domObj.store('jxTreeItem', this);
+        this.domA.store('jxTreeItem', this);
+        if (this.options.contextMenu) {
+          this.domA.store('jxContextMenu', this.options.contextMenu);
+        }
+        /* the target for jxPressed, jxSelected, jxHover classes */
+        this.domObj.store('jxListTarget', this.domA);
+
+        if (!this.options.selectable) {
+            this.domObj.addClass('jxUnselectable');
+        }
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        if (!this.options.enabled) {
+            this.domObj.addClass('jxDisabled');
+        }
+
+        if (this.options.image && this.domIcon) {
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            if (this.options.imageClass) {
+                this.domIcon.addClass(this.options.imageClass);
+            }
+
+        }
+
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        if (this.domA) {
+            this.domA.addEvents({
+                click: this.click.bind(this),
+                dblclick: this.dblclick.bind(this),
+                drag: function(e) { e.stop(); }
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if ($defined(this.options.enabled)) {
+            this.enable(this.options.enabled, true);
+        }
+    },
+    
+    setDirty: function(state) {
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+    
+    /**
+     * Method: finalize
+     * Clean up the TreeItem and remove all DOM references
+     */
+    finalize: function() { this.destroy(); },
+    /**
+     * Method: finalizeItem
+     * Clean up the TreeItem and remove all DOM references
+     */
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeItem');
+      this.domA.eliminate('jxTreeItem');
+      this.domA.eliminate('jxContextMenu');
+      this.domObj.eliminate('jxListTarget');
+      this.domObj.eliminate('jxListTargetItem');
+      this.domA.removeEvents();
+      this.owner = null;
+      this.selection = null;
+      this.parent();
+    },
+    /**
+     * Method: update
+     * Update the CSS of the TreeItem's DOM element in case it has changed
+     * position
+     *
+     * Parameters:
+     * isLast - {Boolean} is the item the last one or not?
+     */
+    update : function(isLast) {
+        if (isLast) {
+            this.domObj.addClass(this.options.lastLeafClass);
+        } else {
+            this.domObj.removeClass(this.options.lastLeafClass);
+        }
+    },
+    click: function() {
+        if (this.options.enabled) {
+            this.fireEvent('click', this);
+        }
+    },
+    dblclick: function() {
+        if (this.options.enabled) {
+            this.fireEvent('dblclick', this);
+        }
+    },
+    /**
+     * Method: select
+     * Select a tree node.
+     */
+    select: function() {
+        if (this.selection && this.options.enabled) {
+            this.selection.select(this.domA);
+        }
+    },
+
+    /**
+     * Method: getLabel
+     * Get the label associated with a TreeItem
+     *
+     * Returns:
+     * {String} the name
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+
+    /**
+     * Method: setLabel
+     * set the label of a tree item
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html',this.getText(label));
+            this.setDirty(true);
+        }
+    },
+
+    setImage: function(url, imageClass) {
+        if (this.domIcon && $defined(url)) {
+            this.options.image = url;
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            this.setDirty(true);
+        }
+        if (this.domIcon && $defined(imageClass)) {
+            this.domIcon.removeClass(this.options.imageClass);
+            this.options.imageClass = imageClass;
+            this.domIcon.addClass(imageClass);
+            this.setDirty(true);
+        }
+    },
+    enable: function(state, force) {
+        if (this.options.enabled != state || force) {
+            this.options.enabled = state;
+            if (this.options.enabled) {
+                this.domObj.removeClass('jxDisabled');
+                this.fireEvent('enabled', this);
+            } else {
+                this.domObj.addClass('jxDisabled');
+                this.fireEvent('disabled', this);
+                if (this.selection) {
+                    this.selection.unselect(document.id(this));
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: propertyChanged
+     * A property of an object has changed, synchronize the state of the
+     * TreeItem with the state of the object
+     *
+     * Parameters:
+     * obj - {Object} the object whose state has changed
+     */
+    propertyChanged : function(obj) {
+        this.options.enabled = obj.isEnabled();
+        if (this.options.enabled) {
+            this.domObj.removeClass('jxDisabled');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    setSelection: function(selection){
+        this.selection = selection;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        this.domImg.addClass(this.options.busyClass)
+      } else {
+        if (this.options.busyClass) {
+          this.domImg.removeClass(this.options.busyClass);
+        }
+      }
+    },
+    changeText : function(lang) {
+      this.parent();
+      this.setLabel(this.options.label);
+    }
+});
+// $Id: treefolder.js 912 2010-05-21 21:33:08Z pagameba $
+/**
+ * Class: Jx.TreeFolder
+ *
+ * A Jx.TreeFolder is an item in a tree that can contain other items.  It is
+ * expandable and collapsible.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Extends:
+ * <Jx.TreeItem>
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeFolder = new Class({
+    Family: 'Jx.TreeFolder',
+    Extends: Jx.TreeItem,
+    /**
+     * Property: tree
+     * {<Jx.Tree>} a Jx.Tree instance for managing the folder contents
+     */
+    tree : null,
+    
+    options: {
+        /* Option: open
+         * is the folder open?  false by default.
+         */
+        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: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel',
+        domTree: 'jxTree'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeFolder
+     */
+    render : function() {
+        this.parent();
+        this.domObj.store('jxTreeFolder', this);
+
+        this.bound.toggle = this.toggle.bind(this);
+
+        this.addEvents({
+            click: this.bound.toggle,
+            dblclick: this.bound.toggle
+        });
+
+        if (this.domImg) {
+            this.domImg.addEvent('click', this.bound.toggle);
+        }
+
+        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.domTree);
+        if (this.options.open) {
+            this.expand();
+        } else {
+            this.collapse();
+        }
+
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeFolder');
+      this.removeEvents({
+        click: this.bound.toggle,
+        dblclick: this.bound.toggle
+      });
+      if (this.domImg) {
+        this.domImg.removeEvent('click', this.bound.toggle);
+      }
+      this.bound.toggle = null;
+      this.tree.destroy();
+      this.tree = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * add one or more items to the folder at a particular position in the
+     * folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        this.tree.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the folder item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        this.tree.remove(item);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        this.tree.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.tree.items();
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this folder and any folders in it
+     */
+    empty: function() {
+        this.tree.empty();
+    },
+    /**
+     * 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?
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    update: function(shouldDescend,isLast) {
+        /* avoid update if not attached to tree yet */
+        if (!this.domObj.parentNode) return;
+        
+        if (this.tree.dirty) {
+          if (!$defined(isLast)) {
+              isLast = this.domObj.hasClass('jxTreeBranchLastOpen') ||
+                       this.domObj.hasClass('jxTreeBranchLastClosed');
+          }
+
+          ['jxTreeBranchOpen','jxTreeBranchLastOpen','jxTreeBranchClosed',
+          'jxTreeBranchLastClosed'].each(function(c){
+              this.removeClass(c);
+          }, this.domObj);
+
+          var c = 'jxTreeBranch';
+          c += isLast ? 'Last' : '';
+          c += this.options.open ? 'Open' : 'Closed';
+          this.domObj.addClass(c);
+        }
+
+        this.tree.update(shouldDescend, isLast);
+    },
+    /**
+     * APIMethod: toggle
+     * toggle the state of the folder between open and closed
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    toggle: function() {
+        if (this.options.enabled) {
+            if (this.options.open) {
+                this.collapse();
+            } else {
+                this.expand();
+            }
+        }
+        return this;
+    },
+    /**
+     * APIMethod: expand
+     * Expands the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    expand : function() {
+        this.options.open = true;
+        document.id(this.tree).setStyle('display', 'block');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: collapse
+     * Collapses the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    collapse : function() {
+        this.options.open = false;
+        document.id(this.tree).setStyle('display', 'none');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return this;
+        } else {
+            return this.tree.findChild(path);
+        }
+    },
+    /**
+     * Method: setSelection
+     * sets the <Jx.Selection> object to be used by this folder.  Used
+     * to propogate a single selection object throughout a tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    setSelection: function(selection) {
+        this.tree.setSelection(selection);
+        return this;
+    }
+});// $Id: slider.js 727 2010-03-04 14:04:04Z pagameba $
+/**
+ * Class: Jx.Slider
+ * This class wraps the mootools-more slider class to make it more Jx friendly
+ *
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slider = new Class({
+    Family: 'Jx.Slider',
+    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
+    },
+    classes: new Hash({
+        domObj: 'jxSliderContainer',
+        knob: 'jxSliderKnob'
+    }),
+    slider: null,
+    knob: null,
+    sliderOpts: null,
+    /**
+     * 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.
+     */
+    render: function () {
+        this.parent();
+        
+        /** 
+         * Not sure why this is here...
+         */
+        /**
+        if (this.domObj) {
+            return;
+        }
+        **/
+
+        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 moving and 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);
+    },
+    /**
+     * APIMethod: set
+     * set the value of the slider
+     */
+    set: function(value) {
+      this.slider.set(value);
+    }
+});// $Id: notice.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Notice
+ *
+ * Extends: <Jx.ListItem>
+ *
+ * Events:
+ * 
+ * MooTools.lang Keys:
+ * - notice.closeTip
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notice = new Class({
+
+    Family: 'Jx.Notice',
+    Extends: Jx.ListItem,
+
+    options: {
+        /**
+         * Option: fx
+         * the effect to use on the notice when it is shown and hidden,
+         * 'fade' by default
+         */
+        fx: 'fade',
+        /**
+         * Option: chrome
+         * {Boolean} should the notice be displayed with chrome or not,
+         * default is false
+         */
+        chrome: false,
+        /**
+         * Option: enabled
+         * {Boolean} default is false
+         */
+        enabled: true,
+        /**
+         * Option: template
+         * {String} the HTML template of a notice
+         */
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        /**
+         * Option: klass
+         * {String} css class to add to the notice
+         */
+        klass: ''
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeItemContainer',
+        domItem: 'jxNoticeItem',
+        domContent: 'jxNotice',
+        domClose: 'jxNoticeClose'
+    }),
+
+    /**
+     * Method: render
+     */
+    render: function () {
+        this.parent();
+        
+        if (this.options.klass) {
+            this.domObj.addClass(this.options.klass);
+        }
+        if (this.domClose) {
+            this.domClose.addEvent('click', this.close.bind(this));
+        }
+    },
+    /**
+     * APIMethod: close
+     * close the notice
+     */
+    close: function() {
+        this.fireEvent('close', this);
+    },
+    /**
+     * APIMethod: show
+     * show the notice
+     */
+    show: function(el, onComplete) {
+        if (this.options.chrome) {
+            this.showChrome();
+        }
+        if (this.options.fx) {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        } else {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        }
+    },
+    /**
+     * APIMethod: hide
+     * hide the notice
+     */
+    hide: function(onComplete) {
+        if (this.options.chrome) {
+            this.hideChrome();
+        }
+        if (this.options.fx) {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        } else {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        }
+    },
+
+    changeText : function(lang) {
+        this.parent();
+        //this.render();
+        //this.processElements(this.options.template, this.classes);
+    }
+});
+/**
+ * Class: Jx.Notice.Information
+ * A <Jx.Notice> subclass useful for displaying informational messages
+ */
+Jx.Notice.Information = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeInformation'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying success messages
+ */
+Jx.Notice.Success = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeSuccess'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying warning messages
+ */
+Jx.Notice.Warning = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeWarning'
+    }
+});
+/**
+ * Class: Jx.Notice.Error
+ * A <Jx.Notice> subclass useful for displaying error messages
+ */
+Jx.Notice.Error = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeError'
+    }
+});
+// $Id: notifier.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Notifier
+ *
+ * Extends: <Jx.ListView>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier = new Class({
+    
+    Family: 'Jx.Notifier',
+    Extends: Jx.ListView,
+    
+    options: {
+        /**
+         * Option: parent
+         * The parent this notifier is to be placed in. If not specified, it
+         * will be placed in the body of the document.
+         */
+        parent: null,
+        /**
+         * Option: template
+         * This is the template for the notification container itself, not the
+         * actual notice. The actual notice is below in the class property 
+         * noticeTemplate.
+         */
+        template: '<div class="jxNoticeListContainer"><ul class="jxNoticeList"></ul></div>',
+        /**
+         * Option: listOptions
+         * An object holding custom options for the internal Jx.List instance.
+         */
+        listOptions: { }
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeListContainer',
+        listObj: 'jxNoticeList'
+    }),
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        
+        if (!$defined(this.options.parent)) {
+            this.options.parent = document.body;
+        }
+        document.id(this.options.parent).adopt(this.domObj);
+        
+        this.addEvent('postRender', function() {
+            if (Jx.type(this.options.items) == 'array') {
+                this.options.items.each(function(item){
+                    this.add(item);
+                },this);
+            }
+        }.bind(this));
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function (notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.addEvent('close', this.remove.bind(this));
+        notice.show(this.listObj);
+    },
+    
+    /**
+     * APIMethod: remove
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to remove
+     */
+    remove: function (notice) {
+        if (this.domObj.hasChild(notice)) {
+            notice.removeEvents('close');
+            notice.hide();
+        }
+    }
+});// $Id: notifier.float.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Notice.Float
+ * A floating notice area for displaying notices, notices get chrome if
+ * the notifier has chrome
+ *
+ * Extends: <Jx.Notifier>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier.Float = new Class({
+    
+    Family: 'Jx.Notifier.Float',
+    Extends: Jx.Notifier,
+    
+    options: {
+        /**
+         * Option: chrome
+         * {Boolean} should the notifier have chrome - default true
+         */
+        chrome: true,
+        /**
+         * Option: fx
+         * {String} the effect to use when showing and hiding the notifier,
+         * default is null
+         */
+        fx: null,
+        /**
+         * Option: width
+         * {Integer} the width in pixels of the notifier, default is 250
+         */
+        width: 250,
+        /**
+         * Option: position
+         * {Object} position options to use with <Jx.Widget::position>
+         * for positioning the Notifier
+         */
+        position: {
+            horizontal: 'center center',
+            vertical: 'top top'
+        }
+    },
+
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.domObj.setStyle('position','absolute');
+        if ($defined(this.options.width)) {
+            this.domObj.setStyle('width',this.options.width);
+        }
+        this.position(this.domObj, 
+                      this.options.parent,
+                      this.options.position);
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function(notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.options.chrome = this.options.chrome;
+        this.parent(notice);
+    }
+});// $Id: scrollbar.js 776 2010-03-22 14:35:16Z pagameba $
+/**
+ * Class: Jx.Scrollbar
+ * Creates a custom scrollbar either vertically or horizontally (determined by
+ * options). These scrollbars are designed to be styled entirely through CSS.
+ * 
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ * 
+ * Based in part on 'Mootools CSS Styled Scrollbar' on
+ * http://solutoire.com/2008/03/10/mootools-css-styled-scrollbar/
+ */
+Jx.Scrollbar = new Class({
+    
+    Family: 'Jx.Scrollbar',
+    
+    Extends: Jx.Widget,
+    
+    Binds: ['scrollIt'],
+    
+    options: {
+        /**
+         * Option: direction
+         * Determines which bars are visible. Valid options are 'horizontal'
+         * or 'vertical'
+         */
+        direction: 'vertical',
+        /**
+         * Option: useMouseWheel
+         * Whether to allow the mouse wheel to move the content. Defaults 
+         * to true.
+         */
+        useMouseWheel: true,
+        /**
+         * Option: useScrollers
+         * Whether to show the scrollers. Defaults to true.
+         */
+        useScrollers: true,
+        /**
+         * Option: scrollerInterval
+         * The amount to scroll the content when using the scrollers. 
+         * useScrollers option must be true. Default is 50 (px).
+         */
+        scrollerInterval: 50,
+        /**
+         * Option: template
+         * the HTML template for a scrollbar
+         */
+        template: '<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'
+    },
+    
+    classes: new Hash({
+        domObj: 'jxScrollbarContainer',
+        scrollLeft: 'jxScrollLeft',
+        scrollRight: 'jxScrollRight',
+        sliderHolder: 'jxSlider'
+    }),
+    
+    el: null,
+    //element is the element we want to scroll. 
+    parameters: ['element', 'options'],
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.el = document.id(this.options.element);
+        if (this.el) {
+            this.el.addClass('jxHas'+this.options.direction.capitalize()+'Scrollbar');
+            
+            //wrap content to make scroll work correctly
+            var children = this.el.getChildren();
+            this.wrapper = new Element('div',{
+                'class': 'jxScrollbarChildWrapper'
+            });
+            
+            /**
+             * the wrapper needs the same settings as the original container
+             * specifically, the width and height
+             */ 
+            this.wrapper.setStyles({
+                width: this.el.getStyle('width'),
+                height: this.el.getStyle('height')
+            });
+            
+            children.inject(this.wrapper);
+            this.wrapper.inject(this.el);
+            
+            this.domObj.inject(this.el);
+            
+            var scrollSize = this.wrapper.getScrollSize();
+            var size = this.wrapper.getContentBoxSize();
+            this.steps = this.options.direction==='horizontal'?scrollSize.x-size.width:scrollSize.y-size.height;
+            this.slider = new Jx.Slider({
+                snap: false,
+                min: 0,
+                max: this.steps,
+                step: 1,
+                mode: this.options.direction,
+                onChange: this.scrollIt
+                
+            });
+            
+            if (!this.options.useScrollers) {
+                this.scrollLeft.dispose();
+                this.scrollRight.dispose();
+                //set size of the sliderHolder
+                if (this.options.direction === 'horizontal') {
+                    this.sliderHolder.setStyle('width','100%');
+                } else {
+                    this.sliderHolder.setStyle('height', '100%');
+                }
+                
+            } else {
+                this.scrollLeft.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                this.scrollRight.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                //set size of the sliderHolder
+                var holderSize, scrollerRightSize, scrollerLeftSize;
+                if (this.options.direction === 'horizontal') {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().width;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().width;
+                    holderSize = size.width - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('width', holderSize + 'px');
+                } else {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().height;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().height;
+                    holderSize = size.height - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('height', holderSize + 'px');
+                }
+            }
+            document.id(this.slider).inject(this.sliderHolder);
+            
+            //allows mouse wheel to function
+            if (this.options.useMouseWheel) {
+                $$(this.el, this.domObj).addEvent('mousewheel', function(e){
+                    e = new Event(e).stop();
+                    var step = this.slider.slider.step - e.wheel * 30;
+                    this.slider.slider.set(step);
+                }.bind(this));
+            }
+            
+            //stop slider if we leave the window
+            document.id(document.body).addEvent('mouseleave', function(){ 
+                this.slider.slider.drag.stop();
+            }.bind(this));
+
+            this.slider.start();
+        }
+    },
+    
+    /**
+     * Method: scrollIt
+     * scroll the content in response to the slider being moved.
+     */
+    scrollIt: function (step) {
+        var x = this.options.direction==='horizontal'?step:0;
+        var y = this.options.direction==='horizontal'?0:step;
+        this.wrapper.scrollTo(x,y);
+    }
+}); // $Id: formatter.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Formatter
+ *
+ * Extends: <Jx.Object>
+ *
+ * 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({
+    Family: 'Jx.Formatter',
+    Extends: Jx.Object,
+
+    /**
+     * APIMethod: format
+     * Empty method that must be overridden by subclasses to provide
+     * the needed formatting functionality.
+     */
+    format: $empty
+});// $Id: number.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.number'.decimalSeparator
+ * - 'formatter.number'.thousandsSeparator
+ *
+ * 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: 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);
+
+        //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.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                } else if (i >= left) {
+                    j++;
+                    if (j === 3 && i !== l - 1) {
+                        ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                        j = 0;
+                    }
+                }
+
+            }
+        } else {
+            ret = parts[0];
+        }
+
+        if (dec) {
+            ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'decimalSeparator'}) + parts[1];
+        }
+        if (neg && this.options.useParens) {
+            ret = "(" + ret + ")";
+        } else if (neg && !this.options.useParens) {
+            ret = "-" + ret;
+        }
+
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});// $Id: currency.js 908 2010-05-19 21:12:06Z conrad.barthelmes $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.currency'.sign
+ *
+ * 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: {},
+    /**
+     * APIMethod: format
+     * Takes a number and formats it as currency.
+     *
+     * Parameters:
+     * value - the number to format
+     */
+    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.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value.substring(1, value.length);
+        } else {
+            ret = this.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value;
+        }
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});// $Id: date.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+        var d = Date.parse(value);
+        return d.format(this.options.format);
+    }
+});// $Id: uri.js 767 2010-03-17 19:35:02Z pagameba $
+/**
+ * Class: Jx.Formatter.URI
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats URIs using the mootools-more's
+ * URI extensions. See the -more docs for details of
+ * supported formats for parsing and formatting.
+ * 
+ * @url http://mootools.net/docs/more/Native/URI
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Uri = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {
+        /**
+         * Option: format
+         * The format to use. See the mootools-more URI options
+         * to use within a {pattern}
+         *   {string} will call the URI.toString() method
+         */
+        format: '<a href="{string}" target="_blank">{host}</a>'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+      var uri        = new URI(value),
+          uriContent = {},
+          pattern    = new Array(),
+          patternTmp = this.options.format.match(/\\?\{([^{}]+)\}/g);
+
+      // remove bracktes
+      patternTmp.each(function(e) {
+        pattern.push(e.slice(1, e.length-1));
+      });
+
+      // build object that contains replacements
+      for(var i = 0, j = pattern.length; i < j; i++) {
+        switch(pattern[i]) {
+          case 'string':
+            uriContent[pattern[i]] = uri.toString();
+            break;
+          default:
+            uriContent[pattern[i]] = uri.get(pattern[i]);
+            break;
+        }
+      }
+      return this.options.format.substitute(uriContent);
+    }
+});// $Id: boolean.js 892 2010-05-06 21:06:26Z conrad.barthelmes $
+/**
+ * Class: Jx.Formatter.Boolean
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats boolean values. You supply the
+ * text values for true and false in the options.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * MooTools.lang Keys:
+ * - 'formatter.boolean'.true
+ * - 'formatter.boolean'.false
+ * 
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Boolean = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {},
+    /**
+     * APIMethod: format
+     * Takes a value, determines boolean equivalent and
+     * displays the appropriate text value.
+     *
+     * 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.getText({set:'Jx',key:'formatter.boolean',value:'true'}) : this.getText({set:'Jx',key:'formatter.boolean',value:'false'});
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+
+});// $Id: phone.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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 {
+                ret = ret + sep + v.substring(0, 3) + sep;
+            }
+            v = v.substring(3);
+        }
+        //do the rest of the number
+        ret = ret + v.substring(0, 3) + sep + v.substring(3);
+        return ret;
+    }
+});// $Id: checkbox.js 859 2010-04-20 21:34:00Z jonlb at comcast.net $
+/**
+ * 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 : '<span class="jxInputContainer"><input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * Whether this field is checked or not
+         */
+        checked : false,
+
+        labelSeparator: ''
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    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.id(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");
+            }
+        }
+
+        // add click event to the label to toggle the checkbox
+        if(this.label) {
+          this.label.addEvent('click', function(ev) {
+            this.setValue(this.getValue() != null ? false : true)
+          }.bind(this));
+        }
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - Whether the box shouldbe checked or not. "checked" or "true" if it should be checked.
+     */
+    setValue : function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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.
+     */
+    reset : function () {
+        if (this.options.checked) {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
+        }
+    },
+
+    getChecked: function () {
+        return this.field.get("checked");
+    }
+
+});
+// $Id: radio.js 888 2010-05-04 20:01:16Z conrad.barthelmes $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * whether this radio button is checked or not
+         */
+        checked: false,
+
+        labelSeparator: ''
+    },
+    /**
+     * 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.id(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");
+            }
+        }
+
+        // add click event to toggle the radio buttons
+        this.label.addEvent('click', function(ev) {
+          this.field.checked ? this.setValue(false) : this.setValue(true);
+        }.bind(this));
+
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to, "checked" it should be checked.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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');
+        }
+    }
+
+});
+
+
+
+
+// $Id: select.js 931 2010-05-28 08:28:09Z conrad.barthelmes $
+/**
+ * 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)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ *
+ */
+
+Jx.Field.Select = new Class({
+
+    Extends: Jx.Field,
+
+    options: {
+        /**
+         * Option: multiple
+         * {Boolean} optional, defaults to false.  If true, then the select
+         * will support multi-select
+         */
+        mulitple: false,
+        /**
+         * Option: size
+         * {Integer} optional, defaults to 1.  If set, then this specifies
+         * the number of rows of the select that are visible
+         */
+        size: 1,
+        /**
+         * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * Indictes this type of field.
+     */
+    type: 'Select',
+
+    /**
+     * APIMethod: render
+     * Creates a select field.
+     */
+    render: function () {
+        this.parent();
+        this.field.addEvent('change', function() {this.fireEvent('change', this);}.bind(this));
+        if ($defined(this.options.multiple)) {
+          this.field.set('multiple', this.options.multiple);
+        }
+        if ($defined(this.options.size)) {
+          this.field.set('size', this.options.size);
+        }
+        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': this.getText(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) {
+                this.addOption(item);
+            }, this);
+        }
+    },
+
+    /**
+     * Method: addOption
+     * add an option to the select list
+     *
+     * Parameters:
+     * item - The option to add.
+     * position (optional) - an integer index or the string 'top'.
+     *                     - default is to add at the bottom.
+     */
+    addOption: function (item, position) {
+        var opt = new Element('option', {
+            'value': item.value,
+            'html': this.getText(item.text)
+        });
+        if ($defined(item.selected) && item.selected) {
+            opt.set("selected", "selected");
+        }
+        var where = 'bottom';
+        var field = this.field;
+        if ($defined(position)) {
+            if (Jx.type(position) == 'integer' &&
+                (position >= 0  && position < field.options.length)) {
+                field = this.field.options[position];
+                where = 'before';
+            } else if (position == 'top') {
+                where = 'top';
+            }
+
+        }
+        opt.inject(field, where);
+    },
+
+    /**
+     * Method: removeOption
+     * removes an option from the select list
+     *
+     * Parameters:
+     *  item - The option to remove.
+     */
+    removeOption: function (item) {
+        //TBD
+    },
+    /**
+     * Method: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            //loop through the options and set the one that matches v
+            $$(this.field.options).each(function (opt) {
+                if (opt.get('value') === v) {
+                    document.id(opt).set("selected", true);
+                }
+            }, this);
+        }
+    },
+
+    /**
+     * Method: getValue
+     * Returns the current value of the field.
+     */
+    getValue: function () {
+        var index = this.field.selectedIndex;
+        //check for a set "value" attribute. If not there return the text
+        if (index > -1) {
+            var ret = this.field.options[index].get("value");
+            if (!$defined(ret)) {
+                ret = this.field.options[index].get("text");
+            }
+            return ret;
+        }
+    },
+    
+    /**
+     * APIMethod: empty
+     * Empties all options from this select
+     */
+    empty: function () {
+        if ($defined(this.field.options)) {
+            $A(this.field.options).each(function (option) {
+                this.field.remove(option);
+            }, this);
+        }
+    }
+});// $Id: textarea.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * The type of field this is.
+     */
+    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 ($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: buttonClass
+         * choose the actual Jx.Button subclass to create for this form
+         * field.  The default is to create a basic Jx.Button.  To create
+         * a different kind of button, pass the class to this option, for
+         * instance:
+         * (code)
+         * buttonClass: Jx.Button.Color
+         * (end)
+         */
+        buttonClass: Jx.Button,
+        
+        /**
+         * Option: buttonOptions
+         */
+        buttonOptions: {},
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span></span>'
+    },
+    
+    button: null,
+    
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Button',
+
+    processTemplate: function(template, classes, container) {
+        var h = this.parent(template, classes, container);
+        this.button = new this.options.buttonClass(this.options.buttonOptions);
+        this.button.addEvent('click', function(){
+          this.fireEvent('click');
+        }.bind(this));
+        var c = h.get('jxInputButton');
+        if (c) {
+            this.button.domObj.replaces(c);
+        }
+        this.button.setEnabled(!this.options.disabled);
+        return h;
+    },
+    
+    click: function() {
+        this.button.clicked();
+    },
+    
+    enable: function() {
+      this.parent();
+      this.button.setEnabled(true);
+    },
+    
+    disable: function() {
+      this.parent();
+      this.button.setEnabled(false);
+    }
+});// $Id: jxcombo.js 932 2010-05-28 14:02:48Z pagameba $
+/**
+ * Class: Jx.Field.Combo
+ *
+ * Extends: <Jx.Field>
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - 
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Combo = new Class({
+    Family: 'Jx.Field.Combo',
+    Extends: Jx.Field,
+    pluginNamespace: 'Combo',
+
+    options: {
+        buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+        /* Option: template
+         */
+         template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'
+     },
+     
+     type: 'Combo',
+     
+    /**
+     * APIMethod: render
+     * create a new instance of Jx.Field.Combo
+     */
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+        
+        var button = new Jx.Button({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon'
+        }).addTo(this.revealer);
+
+        this.menu = new Jx.Menu();
+        this.menu.button = button;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.buttonSet = new Jx.ButtonSet({
+            onChange: (function(set) {
+                var button = set.activeButton;
+                var l = button.options.label;
+                if (l == '&nbsp;') {
+                    l = '';
+                }
+                this.setLabel(l);
+                var img = button.options.image;
+                if (img.indexOf('a_pixel') != -1) {
+                    img = '';
+                }
+                this.setImage(img, button.options.imageClass);
+
+                this.fireEvent('change', this);
+            }).bind(this)
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+        var that = this;
+        button.addEvent('click', function(e) {
+            if (this.list.count() === 0) {
+                return;
+            }
+            if (!button.options.enabled) {
+                return;
+            }
+            this.contentContainer.setStyle('visibility','hidden');
+            this.contentContainer.setStyle('display','block');
+            document.id(document.body).adopt(this.contentContainer);
+            /* we have to size the container for IE to render the chrome correctly
+             * but just in the menu/sub menu case - there is some horrible peekaboo
+             * bug in IE related to ULs that we just couldn't figure out
+             */
+            this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+            this.showChrome(this.contentContainer);
+
+            this.position(this.contentContainer, that.field, {
+                horizontal: ['left left', 'right right'],
+                vertical: ['bottom top', 'top bottom'],
+                offsets: this.chromeOffsets
+            });
+
+            this.contentContainer.setStyle('visibility','');
+
+            document.addEvent('mousedown', this.bound.hide);
+            document.addEvent('keyup', this.bound.keypress);
+
+            this.fireEvent('show', this);
+        }.bindWithEvent(this.menu));
+
+        this.menu.addEvents({
+            'show': (function() {
+                //this.setActive(true);
+            }).bind(this),
+            'hide': (function() {
+                //this.setActive(false);
+            }).bind(this)
+        });
+        
+        this.addEvent('change', function(){
+          window.console ? console.log('on change detected') : false;
+        })
+    },
+    
+    setLabel: function(label) {
+      if ($defined(this.field)) {
+        this.field.value = this.getText(label);
+      }
+    },
+    
+    setImage: function(url, imageClass) {
+      if ($defined(this.icon)) {
+        this.icon.setStyle('background-image', 'url('+url+')');
+        this.icon.setStyle('background-repeat', 'no-repeat');
+
+        if (this.options.imageClass) {
+            this.icon.removeClass(this.options.imageClass);
+        }
+        if (imageClass) {
+            this.options.imageClass = imageClass;
+            this.icon.addClass(imageClass);
+            this.icon.setStyle('background-position','');
+        } else {
+            this.options.imageClass = null;
+            this.icon.setStyle('background-position','center center');
+        }
+      }
+      if (!url) {
+        this.wrapper.addClass('jxInputIconHidden');
+      } else {
+        this.wrapper.removeClass('jxInputIconHidden');
+      }
+    },
+
+    /**
+     * Method: valueChanged
+     * invoked when the current value is changed
+     */
+    valueChanged: function() {
+        this.fireEvent('change', this);
+    },
+
+    setValue: function(value) {
+        this.field.set('value', value);
+        this.buttonSet.buttons.each(function(button){
+          button.setActive(button.options.label === value);
+        },this);
+    },
+
+    /**
+     * Method: onKeyPress
+     * Handle the user pressing a key by looking for an ENTER key to set the
+     * value.
+     *
+     * Parameters:
+     * e - {Event} the keypress event
+     */
+    onKeyPress: function(e) {
+        if (e.key == 'enter') {
+            this.valueChanged();
+        }
+    },
+
+    /**
+     * Method: add
+     * add a new item to the pick list
+     *
+     * Parameters:
+     * options - {Object} object with properties suitable to be passed to
+     * a <Jx.Menu.Item.Options> object.  More than one options object can be
+     * passed, comma separated or in an array.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(opt) {
+            var button = new Jx.Menu.Item($merge(opt,{
+                toggle: true
+            }));
+            this.menu.add(button);
+            this.buttonSet.add(button);
+            if (opt.selected) {
+              this.buttonSet.setActiveButton(button);
+            }
+        }, this);
+    },
+
+    /**
+     * Method: remove
+     * Remove the item at the given index.  Not implemented.
+     *
+     * Parameters:
+     * idx - {Mixed} the item to remove by reference or by index.
+     */
+    remove: function(idx) {
+      var item;
+      if ($type(idx) == 'number' && idx < this.buttonSet.buttons.length) {
+        item = this.buttonSet.buttons[idx];
+      } else if ($type(idx) == 'string'){
+        this.buttonSet.buttons.some(function(button){
+            if (button.options.label === idx) {
+                item = button;
+                return true;
+            }
+            return false;
+        },this);
+      }
+      if (item) {
+        this.buttonSet.remove(item);
+        this.menu.remove(item);
+      }
+    },
+    /**
+     * APIMethod: empty
+     * remove all values from the combo
+     */
+    empty: function() {
+      this.menu.empty();
+      this.buttonSet.empty();
+      this.setLabel('');
+      this.setImage(Jx.aPixel.src);
+    }
+});// $Id: password.js 649 2009-11-30 22:19:48Z pagameba $
+/**
+ * Class: Jx.Field.Password
+ *
+ * Extends: <Jx.Field.Text>
+ *
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel" ></label><input class="jxInputPassword" type="password" name="{name}"/><span class="jxInputTag"></span></span>'
+    },
+
+    type: 'Password'
+});/**
+ * Class: Jx.Field.Color
+ *
+ * Extends: <Jx.Field>
+ *
+ * This class provides a Jx.Field.Text in combination with a Jx.Button.Color
+ * to have a Colorpicker with an input field.
+ *
+ * License:
+ * Copyright (c) 2010, Paul Spener, Fred Warnock, Conrad Barthelmes
+ *
+ * This file is licensed under an MIT style license
+ */
+  Jx.Field.Color = new Class({
+    Extends: Jx.Field,
+    Binds: ['changed','hide','keyup','changeText'],
+    type: 'Color',
+    options: {
+      buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+      /**
+       * Option: template
+       * The template used to render this field
+       */
+      template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',
+      /**
+       * Option: showOnHover
+       * {Boolean} show the color palette when hovering over the input, default 
+       * is false
+       */
+      showOnHover: false,
+      /**
+       *  Option: showDelay
+       *  set time in milliseconds when to show the color field on mouseenter
+       */
+      showDelay: 250,
+      /**
+       * Option: errorMsg
+       * error message for the validator.
+       */
+      errorMsg: 'Invalid Web-Color',
+      /**
+       * Option: color
+       * a color to initialize the field with, defaults to #000000
+       * (black) if not specified.
+       */
+      color: '#000000'
+
+    },
+    button: null,
+    validator: null,
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+
+      var self = this;
+      if (!Jx.Field.Color.ColorPalette) {
+          Jx.Field.Color.ColorPalette = new Jx.ColorPalette(this.options);
+      }
+      this.button = new Jx.Button.Flyout({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon',
+          positionElement: this.field,
+          onBeforeOpen: function() {
+            if (Jx.Field.Color.ColorPalette.currentButton) {
+                Jx.Field.Color.ColorPalette.currentButton.hide();
+            }
+            Jx.Field.Color.ColorPalette.currentButton = this;
+            Jx.Field.Color.ColorPalette.addEvent('change', self.changed);
+            Jx.Field.Color.ColorPalette.addEvent('click', self.hide);
+            this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);
+            Jx.Field.Color.ColorPalette.domObj.setStyle('display', 'block');
+          },
+          onOpen: function() {
+            /* setting these before causes an update problem when clicking on
+             * a second color button when another one is open - the color
+             * wasn't updating properly
+             */
+            Jx.Field.Color.ColorPalette.options.color = self.options.color;
+            Jx.Field.Color.ColorPalette.updateSelected();
+          }
+        }).addTo(this.revealer);
+
+      this.validator = new Jx.Plugin.Field.Validator({
+        validators: [{
+            validatorClass: 'colorHex',
+            validator: {
+              name: 'colorValidator',
+              options: {
+                validateOnChange: false,
+                errorMsg: self.options.errorMsg,
+                test: function(field,props) {
+                  try {
+                    var c = field.get('value').hexToRgb(true);
+                    if(c == null) return false;
+                    for(var i = 0; i < 3; i++) {
+                      if(c[i].toString() == 'NaN') {
+                        return false;
+                      }
+                    }
+                  }catch(e) {
+                    return false;
+                  }
+                  c = c.rgbToHex().toUpperCase();
+                  self.setColor(c);
+                  return true;
+                }
+              }
+            }
+        }],
+        validateOnBlur: true,
+        validateOnChange: true
+      });
+      this.validator.attach(this);
+      this.field.addEvent('keyup', this.onKeyUp.bind(this));
+      if (this.options.showOnHover) {
+        this.field.addEvent('mouseenter', function(ev) {
+          self.button.clicked.delay(self.options.showDelay, self.button);
+        });
+      }
+      this.setValue(this.options.color);
+      this.icon.setStyle('background-color', this.options.color);
+      //this.addEvent('change', self.changed);
+    },
+    /*
+     * Method: onKeyUp
+     *
+     * listens to the keyup event and validates the input for a hex color
+     *
+     */
+    onKeyUp : function(ev) {
+      var color = this.getValue();
+      if (color.substring(0,1) == '#') {
+          color = color.substring(1);
+      }
+      if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+          this.options.color = '#' +color.toUpperCase();
+          this.setColor(this.options.color);
+      }
+    },
+    setColor: function(c) {
+        this.options.color = c;
+        this.setValue(c);
+        this.icon.setStyle('background-color', c);
+    },
+    changed: function() {
+        var c = Jx.Field.Color.ColorPalette.options.color;
+        this.setColor(c);
+    },
+    hide: function() {
+        this.button.setActive(false);
+        Jx.Field.Color.ColorPalette.removeEvent('change', this.changed);
+        Jx.Field.Color.ColorPalette.removeEvent('click', this.hide);
+
+        this.button.hide();
+        Jx.Field.Color.ColorPalette.currentButton = null;
+    },
+    changeText: function(lang) {
+      this.parent();
+    }
+  });

Added: trunk/lib/jxLib/jxlib.uncompressed.js
===================================================================
--- trunk/lib/jxLib/jxlib.uncompressed.js	                        (rev 0)
+++ trunk/lib/jxLib/jxlib.uncompressed.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,42686 @@
+/******************************************************************************
+ * 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, 3.0alpha
+ * Copyright (c) 2006-2008, DM Solutions Group Inc. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *****************************************************************************/
+/*
+---
+
+script: Core.js
+
+description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+
+authors: The MooTools production team (http://mootools.net/developers/)
+
+inspiration:
+- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+
+provides: [MooTools, Native, Hash.base, Array.each, $util]
+
+...
+*/
+
+var MooTools = {
+	'version': '1.2.5dev',
+	'build': '%build%'
+};
+
+var Native = function(options){
+	options = options || {};
+	var name = options.name;
+	var legacy = options.legacy;
+	var protect = options.protect;
+	var methods = options.implement;
+	var generics = options.generics;
+	var initialize = options.initialize;
+	var afterImplement = options.afterImplement || function(){};
+	var object = initialize || legacy;
+	generics = generics !== false;
+
+	object.constructor = Native;
+	object.$family = {name: 'native'};
+	if (legacy && initialize) object.prototype = legacy.prototype;
+	object.prototype.constructor = object;
+
+	if (name){
+		var family = name.toLowerCase();
+		object.prototype.$family = {name: family};
+		Native.typize(object, family);
+	}
+
+	var add = function(obj, name, method, force){
+		if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
+		if (generics) Native.genericize(obj, name, protect);
+		afterImplement.call(obj, name, method);
+		return obj;
+	};
+
+	object.alias = function(a1, a2, a3){
+		if (typeof a1 == 'string'){
+			var pa1 = this.prototype[a1];
+			if ((a1 = pa1)) return add(this, a2, a1, a3);
+		}
+		for (var a in a1) this.alias(a, a1[a], a2);
+		return this;
+	};
+
+	object.implement = function(a1, a2, a3){
+		if (typeof a1 == 'string') return add(this, a1, a2, a3);
+		for (var p in a1) add(this, p, a1[p], a2);
+		return this;
+	};
+
+	if (methods) object.implement(methods);
+
+	return object;
+};
+
+Native.genericize = function(object, property, check){
+	if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
+		var args = Array.prototype.slice.call(arguments);
+		return object.prototype[property].apply(args.shift(), args);
+	};
+};
+
+Native.implement = function(objects, properties){
+	for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
+};
+
+Native.typize = function(object, family){
+	if (!object.type) object.type = function(item){
+		return ($type(item) === family);
+	};
+};
+
+(function(){
+	var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
+	for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
+
+	var types = {'boolean': Boolean, 'native': Native, 'object': Object};
+	for (var t in types) Native.typize(types[t], t);
+
+	var generics = {
+		'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
+		'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
+	};
+	for (var g in generics){
+		for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
+	}
+})();
+
+var Hash = new Native({
+
+	name: 'Hash',
+
+	initialize: function(object){
+		if ($type(object) == 'hash') object = $unlink(object.getClean());
+		for (var key in object) this[key] = object[key];
+		return this;
+	}
+
+});
+
+Hash.implement({
+
+	forEach: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
+		}
+	},
+
+	getClean: function(){
+		var clean = {};
+		for (var key in this){
+			if (this.hasOwnProperty(key)) clean[key] = this[key];
+		}
+		return clean;
+	},
+
+	getLength: function(){
+		var length = 0;
+		for (var key in this){
+			if (this.hasOwnProperty(key)) length++;
+		}
+		return length;
+	}
+
+});
+
+Hash.alias('forEach', 'each');
+
+Array.implement({
+
+	forEach: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
+	}
+
+});
+
+Array.alias('forEach', 'each');
+
+function $A(iterable){
+	if (iterable.item){
+		var l = iterable.length, array = new Array(l);
+		while (l--) array[l] = iterable[l];
+		return array;
+	}
+	return Array.prototype.slice.call(iterable);
+};
+
+function $arguments(i){
+	return function(){
+		return arguments[i];
+	};
+};
+
+function $chk(obj){
+	return !!(obj || obj === 0);
+};
+
+function $clear(timer){
+	clearTimeout(timer);
+	clearInterval(timer);
+	return null;
+};
+
+function $defined(obj){
+	return (obj != undefined);
+};
+
+function $each(iterable, fn, bind){
+	var type = $type(iterable);
+	((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
+};
+
+function $empty(){};
+
+function $extend(original, extended){
+	for (var key in (extended || {})) original[key] = extended[key];
+	return original;
+};
+
+function $H(object){
+	return new Hash(object);
+};
+
+function $lambda(value){
+	return ($type(value) == 'function') ? value : function(){
+		return value;
+	};
+};
+
+function $merge(){
+	var args = Array.slice(arguments);
+	args.unshift({});
+	return $mixin.apply(null, args);
+};
+
+function $mixin(mix){
+	for (var i = 1, l = arguments.length; i < l; i++){
+		var object = arguments[i];
+		if ($type(object) != 'object') continue;
+		for (var key in object){
+			var op = object[key], mp = mix[key];
+			mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
+		}
+	}
+	return mix;
+};
+
+function $pick(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		if (arguments[i] != undefined) return arguments[i];
+	}
+	return null;
+};
+
+function $random(min, max){
+	return Math.floor(Math.random() * (max - min + 1) + min);
+};
+
+function $splat(obj){
+	var type = $type(obj);
+	return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
+};
+
+var $time = Date.now || function(){
+	return +new Date;
+};
+
+function $try(){
+	for (var i = 0, l = arguments.length; i < l; i++){
+		try {
+			return arguments[i]();
+		} catch(e){}
+	}
+	return null;
+};
+
+function $type(obj){
+	if (obj == undefined) return false;
+	if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
+	if (obj.nodeName){
+		switch (obj.nodeType){
+			case 1: return 'element';
+			case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
+		}
+	} else if (typeof obj.length == 'number'){
+		if (obj.callee) return 'arguments';
+		else if (obj.item) return 'collection';
+	}
+	return typeof obj;
+};
+
+function $unlink(object){
+	var unlinked;
+	switch ($type(object)){
+		case 'object':
+			unlinked = {};
+			for (var p in object) unlinked[p] = $unlink(object[p]);
+		break;
+		case 'hash':
+			unlinked = new Hash(object);
+		break;
+		case 'array':
+			unlinked = [];
+			for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+		break;
+		default: return object;
+	}
+	return unlinked;
+};
+/*
+---
+
+script: Browser.js
+
+description: The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+license: MIT-style license.
+
+requires: 
+- /Native
+- /$util
+
+provides: [Browser, Window, Document, $exec]
+
+...
+*/
+
+var Browser = $merge({
+
+	Engine: {name: 'unknown', version: 0},
+
+	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
+
+	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
+
+	Plugins: {},
+
+	Engines: {
+
+		presto: function(){
+			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
+		},
+
+		trident: function(){
+			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
+		},
+
+		webkit: function(){
+			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
+		},
+
+		gecko: function(){
+			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
+		}
+
+	}
+
+}, Browser || {});
+
+Browser.Platform[Browser.Platform.name] = true;
+
+Browser.detect = function(){
+
+	for (var engine in this.Engines){
+		var version = this.Engines[engine]();
+		if (version){
+			this.Engine = {name: engine, version: version};
+			this.Engine[engine] = this.Engine[engine + version] = true;
+			break;
+		}
+	}
+
+	return {name: engine, version: version};
+
+};
+
+Browser.detect();
+
+Browser.Request = function(){
+	return $try(function(){
+		return new XMLHttpRequest();
+	}, function(){
+		return new ActiveXObject('MSXML2.XMLHTTP');
+	}, function(){
+		return new ActiveXObject('Microsoft.XMLHTTP');
+	});
+};
+
+Browser.Features.xhr = !!(Browser.Request());
+
+Browser.Plugins.Flash = (function(){
+	var version = ($try(function(){
+		return navigator.plugins['Shockwave Flash'].description;
+	}, function(){
+		return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+	}) || '0 r0').match(/\d+/g);
+	return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
+})();
+
+function $exec(text){
+	if (!text) return text;
+	if (window.execScript){
+		window.execScript(text);
+	} else {
+		var script = document.createElement('script');
+		script.setAttribute('type', 'text/javascript');
+		script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
+		document.head.appendChild(script);
+		document.head.removeChild(script);
+	}
+	return text;
+};
+
+Native.UID = 1;
+
+var $uid = (Browser.Engine.trident) ? function(item){
+	return (item.uid || (item.uid = [Native.UID++]))[0];
+} : function(item){
+	return item.uid || (item.uid = Native.UID++);
+};
+
+var Window = new Native({
+
+	name: 'Window',
+
+	legacy: (Browser.Engine.trident) ? null: window.Window,
+
+	initialize: function(win){
+		$uid(win);
+		if (!win.Element){
+			win.Element = $empty;
+			if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
+			win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
+		}
+		win.document.window = win;
+		return $extend(win, Window.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		window[property] = Window.Prototype[property] = value;
+	}
+
+});
+
+Window.Prototype = {$family: {name: 'window'}};
+
+new Window(window);
+
+var Document = new Native({
+
+	name: 'Document',
+
+	legacy: (Browser.Engine.trident) ? null: window.Document,
+
+	initialize: function(doc){
+		$uid(doc);
+		doc.head = doc.getElementsByTagName('head')[0];
+		doc.html = doc.getElementsByTagName('html')[0];
+		if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
+			doc.execCommand("BackgroundImageCache", false, true);
+		});
+		if (Browser.Engine.trident) doc.window.attachEvent('onunload', function(){
+			doc.window.detachEvent('onunload', arguments.callee);
+			doc.head = doc.html = doc.window = null;
+		});
+		return $extend(doc, Document.Prototype);
+	},
+
+	afterImplement: function(property, value){
+		document[property] = Document.Prototype[property] = value;
+	}
+
+});
+
+Document.Prototype = {$family: {name: 'document'}};
+
+new Document(document);
+/*
+---
+
+script: Array.js
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Array.each
+
+provides: [Array]
+
+...
+*/
+
+Array.implement({
+
+	every: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (!fn.call(bind, this[i], i, this)) return false;
+		}
+		return true;
+	},
+
+	filter: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) results.push(this[i]);
+		}
+		return results;
+	},
+
+	clean: function(){
+		return this.filter($defined);
+	},
+
+	indexOf: function(item, from){
+		var len = this.length;
+		for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+			if (this[i] === item) return i;
+		}
+		return -1;
+	},
+
+	map: function(fn, bind){
+		var results = [];
+		for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
+		return results;
+	},
+
+	some: function(fn, bind){
+		for (var i = 0, l = this.length; i < l; i++){
+			if (fn.call(bind, this[i], i, this)) return true;
+		}
+		return false;
+	},
+
+	associate: function(keys){
+		var obj = {}, length = Math.min(this.length, keys.length);
+		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+		return obj;
+	},
+
+	link: function(object){
+		var result = {};
+		for (var i = 0, l = this.length; i < l; i++){
+			for (var key in object){
+				if (object[key](this[i])){
+					result[key] = this[i];
+					delete object[key];
+					break;
+				}
+			}
+		}
+		return result;
+	},
+
+	contains: function(item, from){
+		return this.indexOf(item, from) != -1;
+	},
+
+	extend: function(array){
+		for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
+		return this;
+	},
+	
+	getLast: function(){
+		return (this.length) ? this[this.length - 1] : null;
+	},
+
+	getRandom: function(){
+		return (this.length) ? this[$random(0, this.length - 1)] : null;
+	},
+
+	include: function(item){
+		if (!this.contains(item)) this.push(item);
+		return this;
+	},
+
+	combine: function(array){
+		for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+		return this;
+	},
+
+	erase: function(item){
+		for (var i = this.length; i--; i){
+			if (this[i] === item) this.splice(i, 1);
+		}
+		return this;
+	},
+
+	empty: function(){
+		this.length = 0;
+		return this;
+	},
+
+	flatten: function(){
+		var array = [];
+		for (var i = 0, l = this.length; i < l; i++){
+			var type = $type(this[i]);
+			if (!type) continue;
+			array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
+		}
+		return array;
+	},
+
+	hexToRgb: function(array){
+		if (this.length != 3) return null;
+		var rgb = this.map(function(value){
+			if (value.length == 1) value += value;
+			return value.toInt(16);
+		});
+		return (array) ? rgb : 'rgb(' + rgb + ')';
+	},
+
+	rgbToHex: function(array){
+		if (this.length < 3) return null;
+		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+		var hex = [];
+		for (var i = 0; i < 3; i++){
+			var bit = (this[i] - 0).toString(16);
+			hex.push((bit.length == 1) ? '0' + bit : bit);
+		}
+		return (array) ? hex : '#' + hex.join('');
+	}
+
+});
+/*
+---
+
+script: Function.js
+
+description: Contains Function Prototypes like create, bind, pass, and delay.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Function]
+
+...
+*/
+
+Function.implement({
+
+	extend: function(properties){
+		for (var property in properties) this[property] = properties[property];
+		return this;
+	},
+
+	create: function(options){
+		var self = this;
+		options = options || {};
+		return function(event){
+			var args = options.arguments;
+			args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+			if (options.event) args = [event || window.event].extend(args);
+			var returns = function(){
+				return self.apply(options.bind || null, args);
+			};
+			if (options.delay) return setTimeout(returns, options.delay);
+			if (options.periodical) return setInterval(returns, options.periodical);
+			if (options.attempt) return $try(returns);
+			return returns();
+		};
+	},
+
+	run: function(args, bind){
+		return this.apply(bind, $splat(args));
+	},
+
+	pass: function(args, bind){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bind: function(bind, args){
+		return this.create({bind: bind, arguments: args});
+	},
+
+	bindWithEvent: function(bind, args){
+		return this.create({bind: bind, arguments: args, event: true});
+	},
+
+	attempt: function(args, bind){
+		return this.create({bind: bind, arguments: args, attempt: true})();
+	},
+
+	delay: function(delay, bind, args){
+		return this.create({bind: bind, arguments: args, delay: delay})();
+	},
+
+	periodical: function(periodical, bind, args){
+		return this.create({bind: bind, arguments: args, periodical: periodical})();
+	}
+
+});
+/*
+---
+
+script: Number.js
+
+description: Contains Number Prototypes like limit, round, times, and ceil.
+
+license: MIT-style license.
+
+requires:
+- /Native
+- /$util
+
+provides: [Number]
+
+...
+*/
+
+Number.implement({
+
+	limit: function(min, max){
+		return Math.min(max, Math.max(min, this));
+	},
+
+	round: function(precision){
+		precision = Math.pow(10, precision || 0);
+		return Math.round(this * precision) / precision;
+	},
+
+	times: function(fn, bind){
+		for (var i = 0; i < this; i++) fn.call(bind, i, this);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	}
+
+});
+
+Number.alias('times', 'each');
+
+(function(math){
+	var methods = {};
+	math.each(function(name){
+		if (!Number[name]) methods[name] = function(){
+			return Math[name].apply(null, [this].concat($A(arguments)));
+		};
+	});
+	Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+/*
+---
+
+script: String.js
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires:
+- /Native
+
+provides: [String]
+
+...
+*/
+
+String.implement({
+
+	test: function(regex, params){
+		return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
+	},
+
+	contains: function(string, separator){
+		return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+	},
+
+	trim: function(){
+		return this.replace(/^\s+|\s+$/g, '');
+	},
+
+	clean: function(){
+		return this.replace(/\s+/g, ' ').trim();
+	},
+
+	camelCase: function(){
+		return this.replace(/-\D/g, function(match){
+			return match.charAt(1).toUpperCase();
+		});
+	},
+
+	hyphenate: function(){
+		return this.replace(/[A-Z]/g, function(match){
+			return ('-' + match.charAt(0).toLowerCase());
+		});
+	},
+
+	capitalize: function(){
+		return this.replace(/\b[a-z]/g, function(match){
+			return match.toUpperCase();
+		});
+	},
+
+	escapeRegExp: function(){
+		return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+	},
+
+	toInt: function(base){
+		return parseInt(this, base || 10);
+	},
+
+	toFloat: function(){
+		return parseFloat(this);
+	},
+
+	hexToRgb: function(array){
+		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+		return (hex) ? hex.slice(1).hexToRgb(array) : null;
+	},
+
+	rgbToHex: function(array){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHex(array) : null;
+	},
+
+	stripScripts: function(option){
+		var scripts = '';
+		var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
+			scripts += arguments[1] + '\n';
+			return '';
+		});
+		if (option === true) $exec(scripts);
+		else if ($type(option) == 'function') option(scripts, text);
+		return text;
+	},
+
+	substitute: function(object, regexp){
+		return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+			if (match.charAt(0) == '\\') return match.slice(1);
+			return (object[name] != undefined) ? object[name] : '';
+		});
+	}
+
+});
+/*
+---
+
+script: Hash.js
+
+description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.
+
+license: MIT-style license.
+
+requires:
+- /Hash.base
+
+provides: [Hash]
+
+...
+*/
+
+Hash.implement({
+
+	has: Object.prototype.hasOwnProperty,
+
+	keyOf: function(value){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && this[key] === value) return key;
+		}
+		return null;
+	},
+
+	hasValue: function(value){
+		return (Hash.keyOf(this, value) !== null);
+	},
+
+	extend: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.set(this, key, value);
+		}, this);
+		return this;
+	},
+
+	combine: function(properties){
+		Hash.each(properties || {}, function(value, key){
+			Hash.include(this, key, value);
+		}, this);
+		return this;
+	},
+
+	erase: function(key){
+		if (this.hasOwnProperty(key)) delete this[key];
+		return this;
+	},
+
+	get: function(key){
+		return (this.hasOwnProperty(key)) ? this[key] : null;
+	},
+
+	set: function(key, value){
+		if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
+		return this;
+	},
+
+	empty: function(){
+		Hash.each(this, function(value, key){
+			delete this[key];
+		}, this);
+		return this;
+	},
+
+	include: function(key, value){
+		if (this[key] == undefined) this[key] = value;
+		return this;
+	},
+
+	map: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			results.set(key, fn.call(bind, value, key, this));
+		}, this);
+		return results;
+	},
+
+	filter: function(fn, bind){
+		var results = new Hash;
+		Hash.each(this, function(value, key){
+			if (fn.call(bind, value, key, this)) results.set(key, value);
+		}, this);
+		return results;
+	},
+
+	every: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
+		}
+		return true;
+	},
+
+	some: function(fn, bind){
+		for (var key in this){
+			if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
+		}
+		return false;
+	},
+
+	getKeys: function(){
+		var keys = [];
+		Hash.each(this, function(value, key){
+			keys.push(key);
+		});
+		return keys;
+	},
+
+	getValues: function(){
+		var values = [];
+		Hash.each(this, function(value){
+			values.push(value);
+		});
+		return values;
+	},
+
+	toQueryString: function(base){
+		var queryString = [];
+		Hash.each(this, function(value, key){
+			if (base) key = base + '[' + key + ']';
+			var result;
+			switch ($type(value)){
+				case 'object': result = Hash.toQueryString(value, key); break;
+				case 'array':
+					var qs = {};
+					value.each(function(val, i){
+						qs[i] = val;
+					});
+					result = Hash.toQueryString(qs, key);
+				break;
+				default: result = key + '=' + encodeURIComponent(value);
+			}
+			if (value != undefined) queryString.push(result);
+		});
+
+		return queryString.join('&');
+	}
+
+});
+
+Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});
+/*
+---
+
+script: Event.js
+
+description: Contains the Event Class, to make the event object cross-browser.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Hash
+- /Array
+- /Function
+- /String
+
+provides: [Event]
+
+...
+*/
+
+var Event = new Native({
+
+	name: 'Event',
+
+	initialize: function(event, win){
+		win = win || window;
+		var doc = win.document;
+		event = event || win.event;
+		if (event.$extended) return event;
+		this.$extended = true;
+		var type = event.type;
+		var target = event.target || event.srcElement;
+		while (target && target.nodeType == 3) target = target.parentNode;
+
+		if (type.test(/key/)){
+			var code = event.which || event.keyCode;
+			var key = Event.Keys.keyOf(code);
+			if (type == 'keydown'){
+				var fKey = code - 111;
+				if (fKey > 0 && fKey < 13) key = 'f' + fKey;
+			}
+			key = key || String.fromCharCode(code).toLowerCase();
+		} else if (type.match(/(click|mouse|menu)/i)){
+			doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+			var page = {
+				x: event.pageX || event.clientX + doc.scrollLeft,
+				y: event.pageY || event.clientY + doc.scrollTop
+			};
+			var client = {
+				x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
+				y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
+			};
+			if (type.match(/DOMMouseScroll|mousewheel/)){
+				var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
+			}
+			var rightClick = (event.which == 3) || (event.button == 2);
+			var related = null;
+			if (type.match(/over|out/)){
+				switch (type){
+					case 'mouseover': related = event.relatedTarget || event.fromElement; break;
+					case 'mouseout': related = event.relatedTarget || event.toElement;
+				}
+				if (!(function(){
+					while (related && related.nodeType == 3) related = related.parentNode;
+					return true;
+				}).create({attempt: Browser.Engine.gecko})()) related = false;
+			}
+		}
+
+		return $extend(this, {
+			event: event,
+			type: type,
+
+			page: page,
+			client: client,
+			rightClick: rightClick,
+
+			wheel: wheel,
+
+			relatedTarget: related,
+			target: target,
+
+			code: code,
+			key: key,
+
+			shift: event.shiftKey,
+			control: event.ctrlKey,
+			alt: event.altKey,
+			meta: event.metaKey
+		});
+	}
+
+});
+
+Event.Keys = new Hash({
+	'enter': 13,
+	'up': 38,
+	'down': 40,
+	'left': 37,
+	'right': 39,
+	'esc': 27,
+	'space': 32,
+	'backspace': 8,
+	'tab': 9,
+	'delete': 46
+});
+
+Event.implement({
+
+	stop: function(){
+		return this.stopPropagation().preventDefault();
+	},
+
+	stopPropagation: function(){
+		if (this.event.stopPropagation) this.event.stopPropagation();
+		else this.event.cancelBubble = true;
+		return this;
+	},
+
+	preventDefault: function(){
+		if (this.event.preventDefault) this.event.preventDefault();
+		else this.event.returnValue = false;
+		return this;
+	}
+
+});
+/*
+---
+
+script: Class.js
+
+description: Contains the Class Function for easily creating, extending, and implementing reusable Classes.
+
+license: MIT-style license.
+
+requires:
+- /$util
+- /Native
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Class]
+
+...
+*/
+
+function Class(params){
+	
+	if (params instanceof Function) params = {initialize: params};
+	
+	var newClass = function(){
+		Object.reset(this);
+		if (newClass._prototyping) return this;
+		this._current = $empty;
+		var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
+		delete this._current; delete this.caller;
+		return value;
+	}.extend(this);
+	
+	newClass.implement(params);
+	
+	newClass.constructor = Class;
+	newClass.prototype.constructor = newClass;
+
+	return newClass;
+
+};
+
+Function.prototype.protect = function(){
+	this._protected = true;
+	return this;
+};
+
+Object.reset = function(object, key){
+		
+	if (key == null){
+		for (var p in object) Object.reset(object, p);
+		return object;
+	}
+	
+	delete object[key];
+	
+	switch ($type(object[key])){
+		case 'object':
+			var F = function(){};
+			F.prototype = object[key];
+			var i = new F;
+			object[key] = Object.reset(i);
+		break;
+		case 'array': object[key] = $unlink(object[key]); break;
+	}
+	
+	return object;
+	
+};
+
+new Native({name: 'Class', initialize: Class}).extend({
+
+	instantiate: function(F){
+		F._prototyping = true;
+		var proto = new F;
+		delete F._prototyping;
+		return proto;
+	},
+	
+	wrap: function(self, key, method){
+		if (method._origin) method = method._origin;
+		
+		return function(){
+			if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
+			var caller = this.caller, current = this._current;
+			this.caller = current; this._current = arguments.callee;
+			var result = method.apply(this, arguments);
+			this._current = current; this.caller = caller;
+			return result;
+		}.extend({_owner: self, _origin: method, _name: key});
+
+	}
+	
+});
+
+Class.implement({
+	
+	implement: function(key, value){
+		
+		if ($type(key) == 'object'){
+			for (var p in key) this.implement(p, key[p]);
+			return this;
+		}
+		
+		var mutator = Class.Mutators[key];
+		
+		if (mutator){
+			value = mutator.call(this, value);
+			if (value == null) return this;
+		}
+		
+		var proto = this.prototype;
+
+		switch ($type(value)){
+			
+			case 'function':
+				if (value._hidden) return this;
+				proto[key] = Class.wrap(this, key, value);
+			break;
+			
+			case 'object':
+				var previous = proto[key];
+				if ($type(previous) == 'object') $mixin(previous, value);
+				else proto[key] = $unlink(value);
+			break;
+			
+			case 'array':
+				proto[key] = $unlink(value);
+			break;
+			
+			default: proto[key] = value;
+
+		}
+		
+		return this;
+
+	}
+	
+});
+
+Class.Mutators = {
+	
+	Extends: function(parent){
+
+		this.parent = parent;
+		this.prototype = Class.instantiate(parent);
+
+		this.implement('parent', function(){
+			var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
+			if (!previous) throw new Error('The method "' + name + '" has no parent.');
+			return previous.apply(this, arguments);
+		}.protect());
+
+	},
+
+	Implements: function(items){
+		$splat(items).each(function(item){
+			if (item instanceof Function) item = Class.instantiate(item);
+			this.implement(item);
+		}, this);
+
+	}
+	
+};
+/*
+---
+
+script: Class.Extras.js
+
+description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.
+
+license: MIT-style license.
+
+requires:
+- /Class
+
+provides: [Chain, Events, Options]
+
+...
+*/
+
+var Chain = new Class({
+
+	$chain: [],
+
+	chain: function(){
+		this.$chain.extend(Array.flatten(arguments));
+		return this;
+	},
+
+	callChain: function(){
+		return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
+	},
+
+	clearChain: function(){
+		this.$chain.empty();
+		return this;
+	}
+
+});
+
+var Events = new Class({
+
+	$events: {},
+
+	addEvent: function(type, fn, internal){
+		type = Events.removeOn(type);
+		if (fn != $empty){
+			this.$events[type] = this.$events[type] || [];
+			this.$events[type].include(fn);
+			if (internal) fn.internal = true;
+		}
+		return this;
+	},
+
+	addEvents: function(events){
+		for (var type in events) this.addEvent(type, events[type]);
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		type = Events.removeOn(type);
+		if (!this.$events || !this.$events[type]) return this;
+		this.$events[type].each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		type = Events.removeOn(type);
+		if (!this.$events[type]) return this;
+		if (!fn.internal) this.$events[type].erase(fn);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		if (events) events = Events.removeOn(events);
+		for (type in this.$events){
+			if (events && events != type) continue;
+			var fns = this.$events[type];
+			for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
+		}
+		return this;
+	}
+
+});
+
+Events.removeOn = function(string){
+	return string.replace(/^on([A-Z])/, function(full, first){
+		return first.toLowerCase();
+	});
+};
+
+var Options = new Class({
+
+	setOptions: function(){
+		this.options = $merge.run([this.options].extend(arguments));
+		if (!this.addEvent) return this;
+		for (var option in this.options){
+			if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
+			this.addEvent(option, this.options[option]);
+			delete this.options[option];
+		}
+		return this;
+	}
+
+});
+/*
+---
+
+script: Element.js
+
+description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements.
+
+license: MIT-style license.
+
+requires:
+- /Window
+- /Document
+- /Array
+- /String
+- /Function
+- /Number
+- /Hash
+
+provides: [Element, Elements, $, $$, Iframe]
+
+...
+*/
+
+var Element = new Native({
+
+	name: 'Element',
+
+	legacy: window.Element,
+
+	initialize: function(tag, props){
+		var konstructor = Element.Constructors.get(tag);
+		if (konstructor) return konstructor(props);
+		if (typeof tag == 'string') return document.newElement(tag, props);
+		return document.id(tag).set(props);
+	},
+
+	afterImplement: function(key, value){
+		Element.Prototype[key] = value;
+		if (Array[key]) return;
+		Elements.implement(key, function(){
+			var items = [], elements = true;
+			for (var i = 0, j = this.length; i < j; i++){
+				var returns = this[i][key].apply(this[i], arguments);
+				items.push(returns);
+				if (elements) elements = ($type(returns) == 'element');
+			}
+			return (elements) ? new Elements(items) : items;
+		});
+	}
+
+});
+
+Element.Prototype = {$family: {name: 'element'}};
+
+Element.Constructors = new Hash;
+
+var IFrame = new Native({
+
+	name: 'IFrame',
+
+	generics: false,
+
+	initialize: function(){
+		var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
+		var props = params.properties || {};
+		var iframe = document.id(params.iframe);
+		var onload = props.onload || $empty;
+		delete props.onload;
+		props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
+		iframe = new Element(iframe || 'iframe', props);
+		var onFrameLoad = function(){
+			var host = $try(function(){
+				return iframe.contentWindow.location.host;
+			});
+			if (!host || host == window.location.host){
+				var win = new Window(iframe.contentWindow);
+				new Document(iframe.contentWindow.document);
+				$extend(win.Element.prototype, Element.Prototype);
+			}
+			onload.call(iframe.contentWindow, iframe.contentWindow.document);
+		};
+		var contentWindow = $try(function(){
+			return iframe.contentWindow;
+		});
+		((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
+		return iframe;
+	}
+
+});
+
+var Elements = new Native({
+
+	initialize: function(elements, options){
+		options = $extend({ddup: true, cash: true}, options);
+		elements = elements || [];
+		if (options.ddup || options.cash){
+			var uniques = {}, returned = [];
+			for (var i = 0, l = elements.length; i < l; i++){
+				var el = document.id(elements[i], !options.cash);
+				if (options.ddup){
+					if (uniques[el.uid]) continue;
+					uniques[el.uid] = true;
+				}
+				if (el) returned.push(el);
+			}
+			elements = returned;
+		}
+		return (options.cash) ? $extend(elements, this) : elements;
+	}
+
+});
+
+Elements.implement({
+
+	filter: function(filter, bind){
+		if (!filter) return this;
+		return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
+			return item.match(filter);
+		} : filter, bind));
+	}
+
+});
+
+Document.implement({
+
+	newElement: function(tag, props){
+		if (Browser.Engine.trident && props){
+			['name', 'type', 'checked'].each(function(attribute){
+				if (!props[attribute]) return;
+				tag += ' ' + attribute + '="' + props[attribute] + '"';
+				if (attribute != 'checked') delete props[attribute];
+			});
+			tag = '<' + tag + '>';
+		}
+		return document.id(this.createElement(tag)).set(props);
+	},
+
+	newTextNode: function(text){
+		return this.createTextNode(text);
+	},
+
+	getDocument: function(){
+		return this;
+	},
+
+	getWindow: function(){
+		return this.window;
+	},
+	
+	id: (function(){
+		
+		var types = {
+
+			string: function(id, nocash, doc){
+				id = doc.getElementById(id);
+				return (id) ? types.element(id, nocash) : null;
+			},
+			
+			element: function(el, nocash){
+				$uid(el);
+				if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
+					var proto = Element.Prototype;
+					for (var p in proto) el[p] = proto[p];
+				};
+				return el;
+			},
+			
+			object: function(obj, nocash, doc){
+				if (obj.toElement) return types.element(obj.toElement(doc), nocash);
+				return null;
+			}
+			
+		};
+
+		types.textnode = types.whitespace = types.window = types.document = $arguments(0);
+		
+		return function(el, nocash, doc){
+			if (el && el.$family && el.uid) return el;
+			var type = $type(el);
+			return (types[type]) ? types[type](el, nocash, doc || document) : null;
+		};
+
+	})()
+
+});
+
+if (window.$ == null) Window.implement({
+	$: function(el, nc){
+		return document.id(el, nc, this.document);
+	}
+});
+
+Window.implement({
+
+	$$: function(selector){
+		if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
+		var elements = [];
+		var args = Array.flatten(arguments);
+		for (var i = 0, l = args.length; i < l; i++){
+			var item = args[i];
+			switch ($type(item)){
+				case 'element': elements.push(item); break;
+				case 'string': elements.extend(this.document.getElements(item, true));
+			}
+		}
+		return new Elements(elements);
+	},
+
+	getDocument: function(){
+		return this.document;
+	},
+
+	getWindow: function(){
+		return this;
+	}
+
+});
+
+Native.implement([Element, Document], {
+
+	getElement: function(selector, nocash){
+		return document.id(this.getElements(selector, true)[0] || null, nocash);
+	},
+
+	getElements: function(tags, nocash){
+		tags = tags.split(',');
+		var elements = [];
+		var ddup = (tags.length > 1);
+		tags.each(function(tag){
+			var partial = this.getElementsByTagName(tag.trim());
+			(ddup) ? elements.extend(partial) : elements = partial;
+		}, this);
+		return new Elements(elements, {ddup: ddup, cash: !nocash});
+	}
+
+});
+
+(function(){
+
+var collected = {}, storage = {};
+var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};
+
+var get = function(uid){
+	return (storage[uid] || (storage[uid] = {}));
+};
+
+var clean = function(item, retain){
+	if (!item) return;
+	var uid = item.uid;
+	if (retain !== true) retain = false;
+	if (Browser.Engine.trident){
+		if (item.clearAttributes){
+			var clone = retain && item.cloneNode(false);
+			item.clearAttributes();
+			if (clone) item.mergeAttributes(clone);
+		} else if (item.removeEvents){
+			item.removeEvents();
+		}
+		if ((/object/i).test(item.tagName)){
+			for (var p in item){
+				if (typeof item[p] == 'function') item[p] = $empty;
+			}
+			Element.dispose(item);
+		}
+	}	
+	if (!uid) return;
+	collected[uid] = storage[uid] = null;
+};
+
+var purge = function(){
+	Hash.each(collected, clean);
+	if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
+	if (window.CollectGarbage) CollectGarbage();
+	collected = storage = null;
+};
+
+var walk = function(element, walk, start, match, all, nocash){
+	var el = element[start || walk];
+	var elements = [];
+	while (el){
+		if (el.nodeType == 1 && (!match || Element.match(el, match))){
+			if (!all) return document.id(el, nocash);
+			elements.push(el);
+		}
+		el = el[walk];
+	}
+	return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
+};
+
+var attributes = {
+	'html': 'innerHTML',
+	'class': 'className',
+	'for': 'htmlFor',
+	'defaultValue': 'defaultValue',
+	'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
+};
+var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
+var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];
+
+bools = bools.associate(bools);
+
+Hash.extend(attributes, bools);
+Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));
+
+var inserters = {
+
+	before: function(context, element){
+		if (element.parentNode) element.parentNode.insertBefore(context, element);
+	},
+
+	after: function(context, element){
+		if (!element.parentNode) return;
+		var next = element.nextSibling;
+		(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
+	},
+
+	bottom: function(context, element){
+		element.appendChild(context);
+	},
+
+	top: function(context, element){
+		var first = element.firstChild;
+		(first) ? element.insertBefore(context, first) : element.appendChild(context);
+	}
+
+};
+
+inserters.inside = inserters.bottom;
+
+Hash.each(inserters, function(inserter, where){
+
+	where = where.capitalize();
+
+	Element.implement('inject' + where, function(el){
+		inserter(this, document.id(el, true));
+		return this;
+	});
+
+	Element.implement('grab' + where, function(el){
+		inserter(document.id(el, true), this);
+		return this;
+	});
+
+});
+
+Element.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				var property = Element.Properties.get(prop);
+				(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		var property = Element.Properties.get(prop);
+		return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
+	},
+
+	erase: function(prop){
+		var property = Element.Properties.get(prop);
+		(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
+		return this;
+	},
+
+	setProperty: function(attribute, value){
+		var key = attributes[attribute];
+		if (value == undefined) return this.removeProperty(attribute);
+		if (key && bools[attribute]) value = !!value;
+		(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
+		return this;
+	},
+
+	setProperties: function(attributes){
+		for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
+		return this;
+	},
+
+	getProperty: function(attribute){
+		var key = attributes[attribute];
+		var value = (key) ? this[key] : this.getAttribute(attribute, 2);
+		return (bools[attribute]) ? !!value : (key) ? value : value || null;
+	},
+
+	getProperties: function(){
+		var args = $A(arguments);
+		return args.map(this.getProperty, this).associate(args);
+	},
+
+	removeProperty: function(attribute){
+		var key = attributes[attribute];
+		(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
+		return this;
+	},
+
+	removeProperties: function(){
+		Array.each(arguments, this.removeProperty, this);
+		return this;
+	},
+
+	hasClass: function(className){
+		return this.className.contains(className, ' ');
+	},
+
+	addClass: function(className){
+		if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
+		return this;
+	},
+
+	removeClass: function(className){
+		this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
+		return this;
+	},
+
+	toggleClass: function(className){
+		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+	},
+
+	adopt: function(){
+		Array.flatten(arguments).each(function(element){
+			element = document.id(element, true);
+			if (element) this.appendChild(element);
+		}, this);
+		return this;
+	},
+
+	appendText: function(text, where){
+		return this.grab(this.getDocument().newTextNode(text), where);
+	},
+
+	grab: function(el, where){
+		inserters[where || 'bottom'](document.id(el, true), this);
+		return this;
+	},
+
+	inject: function(el, where){
+		inserters[where || 'bottom'](this, document.id(el, true));
+		return this;
+	},
+
+	replaces: function(el){
+		el = document.id(el, true);
+		el.parentNode.replaceChild(this, el);
+		return this;
+	},
+
+	wraps: function(el, where){
+		el = document.id(el, true);
+		return this.replaces(el).grab(el, where);
+	},
+
+	getPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, false, nocash);
+	},
+
+	getAllPrevious: function(match, nocash){
+		return walk(this, 'previousSibling', null, match, true, nocash);
+	},
+
+	getNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, false, nocash);
+	},
+
+	getAllNext: function(match, nocash){
+		return walk(this, 'nextSibling', null, match, true, nocash);
+	},
+
+	getFirst: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
+	},
+
+	getLast: function(match, nocash){
+		return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
+	},
+
+	getParent: function(match, nocash){
+		return walk(this, 'parentNode', null, match, false, nocash);
+	},
+
+	getParents: function(match, nocash){
+		return walk(this, 'parentNode', null, match, true, nocash);
+	},
+	
+	getSiblings: function(match, nocash){
+		return this.getParent().getChildren(match, nocash).erase(this);
+	},
+
+	getChildren: function(match, nocash){
+		return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
+	},
+
+	getWindow: function(){
+		return this.ownerDocument.window;
+	},
+
+	getDocument: function(){
+		return this.ownerDocument;
+	},
+
+	getElementById: function(id, nocash){
+		var el = this.ownerDocument.getElementById(id);
+		if (!el) return null;
+		for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
+			if (!parent) return null;
+		}
+		return document.id(el, nocash);
+	},
+
+	getSelected: function(){
+		return new Elements($A(this.options).filter(function(option){
+			return option.selected;
+		}));
+	},
+
+	getComputedStyle: function(property){
+		if (this.currentStyle) return this.currentStyle[property.camelCase()];
+		var computed = this.getDocument().defaultView.getComputedStyle(this, null);
+		return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
+	},
+
+	toQueryString: function(){
+		var queryString = [];
+		this.getElements('input, select, textarea', true).each(function(el){
+			if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
+			var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
+				return opt.value;
+			}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
+			$splat(value).each(function(val){
+				if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
+			});
+		});
+		return queryString.join('&');
+	},
+
+	clone: function(contents, keepid){
+		contents = contents !== false;
+		var clone = this.cloneNode(contents);
+		var clean = function(node, element){
+			if (!keepid) node.removeAttribute('id');
+			if (Browser.Engine.trident){
+				node.clearAttributes();
+				node.mergeAttributes(element);
+				node.removeAttribute('uid');
+				if (node.options){
+					var no = node.options, eo = element.options;
+					for (var j = no.length; j--;) no[j].selected = eo[j].selected;
+				}
+			}
+			var prop = props[element.tagName.toLowerCase()];
+			if (prop && element[prop]) node[prop] = element[prop];
+		};
+
+		if (contents){
+			var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
+			for (var i = ce.length; i--;) clean(ce[i], te[i]);
+		}
+
+		clean(clone, this);
+		return document.id(clone);
+	},
+
+	destroy: function(){
+		Element.empty(this);
+		Element.dispose(this);
+		clean(this, true);
+		return null;
+	},
+
+	empty: function(){
+		$A(this.childNodes).each(function(node){
+			Element.destroy(node);
+		});
+		return this;
+	},
+
+	dispose: function(){
+		return (this.parentNode) ? this.parentNode.removeChild(this) : this;
+	},
+
+	hasChild: function(el){
+		el = document.id(el, true);
+		if (!el) return false;
+		if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
+		return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
+	},
+
+	match: function(tag){
+		return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
+	}
+
+});
+
+Native.implement([Element, Window, Document], {
+
+	addListener: function(type, fn){
+		if (type == 'unload'){
+			var old = fn, self = this;
+			fn = function(){
+				self.removeListener('unload', fn);
+				old();
+			};
+		} else {
+			collected[this.uid] = this;
+		}
+		if (this.addEventListener) this.addEventListener(type, fn, false);
+		else this.attachEvent('on' + type, fn);
+		return this;
+	},
+
+	removeListener: function(type, fn){
+		if (this.removeEventListener) this.removeEventListener(type, fn, false);
+		else this.detachEvent('on' + type, fn);
+		return this;
+	},
+
+	retrieve: function(property, dflt){
+		var storage = get(this.uid), prop = storage[property];
+		if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
+		return $pick(prop);
+	},
+
+	store: function(property, value){
+		var storage = get(this.uid);
+		storage[property] = value;
+		return this;
+	},
+
+	eliminate: function(property){
+		var storage = get(this.uid);
+		delete storage[property];
+		return this;
+	}
+
+});
+
+window.addListener('unload', purge);
+
+})();
+
+Element.Properties = new Hash;
+
+Element.Properties.style = {
+
+	set: function(style){
+		this.style.cssText = style;
+	},
+
+	get: function(){
+		return this.style.cssText;
+	},
+
+	erase: function(){
+		this.style.cssText = '';
+	}
+
+};
+
+Element.Properties.tag = {
+
+	get: function(){
+		return this.tagName.toLowerCase();
+	}
+
+};
+
+Element.Properties.html = (function(){
+	var wrapper = document.createElement('div');
+
+	var translations = {
+		table: [1, '<table>', '</table>'],
+		select: [1, '<select>', '</select>'],
+		tbody: [2, '<table><tbody>', '</tbody></table>'],
+		tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
+	};
+	translations.thead = translations.tfoot = translations.tbody;
+
+	var html = {
+		set: function(){
+			var html = Array.flatten(arguments).join('');
+			var wrap = Browser.Engine.trident && translations[this.get('tag')];
+			if (wrap){
+				var first = wrapper;
+				first.innerHTML = wrap[1] + html + wrap[2];
+				for (var i = wrap[0]; i--;) first = first.firstChild;
+				this.empty().adopt(first.childNodes);
+			} else {
+				this.innerHTML = html;
+			}
+		}
+	};
+
+	html.erase = html.set;
+
+	return html;
+})();
+
+if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
+	get: function(){
+		if (this.innerText) return this.innerText;
+		var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
+		var text = temp.innerText;
+		temp.destroy();
+		return text;
+	}
+};
+/*
+---
+
+script: Element.Event.js
+
+description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events.
+
+license: MIT-style license.
+
+requires: 
+- /Element
+- /Event
+
+provides: [Element.Event]
+
+...
+*/
+
+Element.Properties.events = {set: function(events){
+	this.addEvents(events);
+}};
+
+Native.implement([Element, Window, Document], {
+
+	addEvent: function(type, fn){
+		var events = this.retrieve('events', {});
+		events[type] = events[type] || {'keys': [], 'values': []};
+		if (events[type].keys.contains(fn)) return this;
+		events[type].keys.push(fn);
+		var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
+		if (custom){
+			if (custom.onAdd) custom.onAdd.call(this, fn);
+			if (custom.condition){
+				condition = function(event){
+					if (custom.condition.call(this, event)) return fn.call(this, event);
+					return true;
+				};
+			}
+			realType = custom.base || realType;
+		}
+		var defn = function(){
+			return fn.call(self);
+		};
+		var nativeEvent = Element.NativeEvents[realType];
+		if (nativeEvent){
+			if (nativeEvent == 2){
+				defn = function(event){
+					event = new Event(event, self.getWindow());
+					if (condition.call(self, event) === false) event.stop();
+				};
+			}
+			this.addListener(realType, defn);
+		}
+		events[type].values.push(defn);
+		return this;
+	},
+
+	removeEvent: function(type, fn){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		var pos = events[type].keys.indexOf(fn);
+		if (pos == -1) return this;
+		events[type].keys.splice(pos, 1);
+		var value = events[type].values.splice(pos, 1)[0];
+		var custom = Element.Events.get(type);
+		if (custom){
+			if (custom.onRemove) custom.onRemove.call(this, fn);
+			type = custom.base || type;
+		}
+		return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
+	},
+
+	addEvents: function(events){
+		for (var event in events) this.addEvent(event, events[event]);
+		return this;
+	},
+
+	removeEvents: function(events){
+		var type;
+		if ($type(events) == 'object'){
+			for (type in events) this.removeEvent(type, events[type]);
+			return this;
+		}
+		var attached = this.retrieve('events');
+		if (!attached) return this;
+		if (!events){
+			for (type in attached) this.removeEvents(type);
+			this.eliminate('events');
+		} else if (attached[events]){
+			while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
+			attached[events] = null;
+		}
+		return this;
+	},
+
+	fireEvent: function(type, args, delay){
+		var events = this.retrieve('events');
+		if (!events || !events[type]) return this;
+		events[type].keys.each(function(fn){
+			fn.create({'bind': this, 'delay': delay, 'arguments': args})();
+		}, this);
+		return this;
+	},
+
+	cloneEvents: function(from, type){
+		from = document.id(from);
+		var fevents = from.retrieve('events');
+		if (!fevents) return this;
+		if (!type){
+			for (var evType in fevents) this.cloneEvents(from, evType);
+		} else if (fevents[type]){
+			fevents[type].keys.each(function(fn){
+				this.addEvent(type, fn);
+			}, this);
+		}
+		return this;
+	}
+
+});
+
+Element.NativeEvents = {
+	click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
+	mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
+	mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
+	keydown: 2, keypress: 2, keyup: 2, //keyboard
+	focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
+	load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
+	error: 1, abort: 1, scroll: 1 //misc
+};
+
+(function(){
+
+var $check = function(event){
+	var related = event.relatedTarget;
+	if (related == undefined) return true;
+	if (related === false) return false;
+	return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
+};
+
+Element.Events = new Hash({
+
+	mouseenter: {
+		base: 'mouseover',
+		condition: $check
+	},
+
+	mouseleave: {
+		base: 'mouseout',
+		condition: $check
+	},
+
+	mousewheel: {
+		base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
+	}
+
+});
+
+})();
+/*
+---
+
+script: Element.Style.js
+
+description: Contains methods for interacting with the styles of Elements in a fashionable way.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Element.Style]
+
+...
+*/
+
+Element.Properties.styles = {set: function(styles){
+	this.setStyles(styles);
+}};
+
+Element.Properties.opacity = {
+
+	set: function(opacity, novisibility){
+		if (!novisibility){
+			if (opacity == 0){
+				if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
+			} else {
+				if (this.style.visibility != 'visible') this.style.visibility = 'visible';
+			}
+		}
+		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
+		if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
+		this.style.opacity = opacity;
+		this.store('opacity', opacity);
+	},
+
+	get: function(){
+		return this.retrieve('opacity', 1);
+	}
+
+};
+
+Element.implement({
+
+	setOpacity: function(value){
+		return this.set('opacity', value, true);
+	},
+
+	getOpacity: function(){
+		return this.get('opacity');
+	},
+
+	setStyle: function(property, value){
+		switch (property){
+			case 'opacity': return this.set('opacity', parseFloat(value));
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		if ($type(value) != 'string'){
+			var map = (Element.Styles.get(property) || '@').split(' ');
+			value = $splat(value).map(function(val, i){
+				if (!map[i]) return '';
+				return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
+			}).join(' ');
+		} else if (value == String(Number(value))){
+			value = Math.round(value);
+		}
+		this.style[property] = value;
+		return this;
+	},
+
+	getStyle: function(property){
+		switch (property){
+			case 'opacity': return this.get('opacity');
+			case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
+		}
+		property = property.camelCase();
+		var result = this.style[property];
+		if (!$chk(result)){
+			result = [];
+			for (var style in Element.ShortStyles){
+				if (property != style) continue;
+				for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
+				return result.join(' ');
+			}
+			result = this.getComputedStyle(property);
+		}
+		if (result){
+			result = String(result);
+			var color = result.match(/rgba?\([\d\s,]+\)/);
+			if (color) result = result.replace(color[0], color[0].rgbToHex());
+		}
+		if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
+			if (property.test(/^(height|width)$/)){
+				var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
+				values.each(function(value){
+					size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
+				}, this);
+				return this['offset' + property.capitalize()] - size + 'px';
+			}
+			if ((Browser.Engine.presto) && String(result).test('px')) return result;
+			if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
+		}
+		return result;
+	},
+
+	setStyles: function(styles){
+		for (var style in styles) this.setStyle(style, styles[style]);
+		return this;
+	},
+
+	getStyles: function(){
+		var result = {};
+		Array.flatten(arguments).each(function(key){
+			result[key] = this.getStyle(key);
+		}, this);
+		return result;
+	}
+
+});
+
+Element.Styles = new Hash({
+	left: '@px', top: '@px', bottom: '@px', right: '@px',
+	width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
+	backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
+	fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
+	margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
+	borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
+	zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
+});
+
+Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};
+
+['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
+	var Short = Element.ShortStyles;
+	var All = Element.Styles;
+	['margin', 'padding'].each(function(style){
+		var sd = style + direction;
+		Short[style][sd] = All[sd] = '@px';
+	});
+	var bd = 'border' + direction;
+	Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
+	var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
+	Short[bd] = {};
+	Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
+	Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
+	Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
+});
+/*
+---
+
+script: Element.Dimensions.js
+
+description: Contains methods to work with size, scroll, or positioning of Elements and the window object.
+
+license: MIT-style license.
+
+credits:
+- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
+- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
+
+requires:
+- /Element
+
+provides: [Element.Dimensions]
+
+...
+*/
+
+(function(){
+
+Element.implement({
+
+	scrollTo: function(x, y){
+		if (isBody(this)){
+			this.getWindow().scrollTo(x, y);
+		} else {
+			this.scrollLeft = x;
+			this.scrollTop = y;
+		}
+		return this;
+	},
+
+	getSize: function(){
+		if (isBody(this)) return this.getWindow().getSize();
+		return {x: this.offsetWidth, y: this.offsetHeight};
+	},
+
+	getScrollSize: function(){
+		if (isBody(this)) return this.getWindow().getScrollSize();
+		return {x: this.scrollWidth, y: this.scrollHeight};
+	},
+
+	getScroll: function(){
+		if (isBody(this)) return this.getWindow().getScroll();
+		return {x: this.scrollLeft, y: this.scrollTop};
+	},
+
+	getScrolls: function(){
+		var element = this, position = {x: 0, y: 0};
+		while (element && !isBody(element)){
+			position.x += element.scrollLeft;
+			position.y += element.scrollTop;
+			element = element.parentNode;
+		}
+		return position;
+	},
+
+	getOffsetParent: function(){
+		var element = this;
+		if (isBody(element)) return null;
+		if (!Browser.Engine.trident) return element.offsetParent;
+		while ((element = element.parentNode) && !isBody(element)){
+			if (styleString(element, 'position') != 'static') return element;
+		}
+		return null;
+	},
+
+	getOffsets: function(){
+		if (this.getBoundingClientRect){
+			var bound = this.getBoundingClientRect(),
+				html = document.id(this.getDocument().documentElement),
+				htmlScroll = html.getScroll(),
+				elemScrolls = this.getScrolls(),
+				elemScroll = this.getScroll(),
+				isFixed = (styleString(this, 'position') == 'fixed');
+
+			return {
+				x: bound.left.toInt() + elemScrolls.x - elemScroll.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft,
+				y: bound.top.toInt()  + elemScrolls.y - elemScroll.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop
+			};
+		}
+
+		var element = this, position = {x: 0, y: 0};
+		if (isBody(this)) return position;
+
+		while (element && !isBody(element)){
+			position.x += element.offsetLeft;
+			position.y += element.offsetTop;
+
+			if (Browser.Engine.gecko){
+				if (!borderBox(element)){
+					position.x += leftBorder(element);
+					position.y += topBorder(element);
+				}
+				var parent = element.parentNode;
+				if (parent && styleString(parent, 'overflow') != 'visible'){
+					position.x += leftBorder(parent);
+					position.y += topBorder(parent);
+				}
+			} else if (element != this && Browser.Engine.webkit){
+				position.x += leftBorder(element);
+				position.y += topBorder(element);
+			}
+
+			element = element.offsetParent;
+		}
+		if (Browser.Engine.gecko && !borderBox(this)){
+			position.x -= leftBorder(this);
+			position.y -= topBorder(this);
+		}
+		return position;
+	},
+
+	getPosition: function(relative){
+		if (isBody(this)) return {x: 0, y: 0};
+		var offset = this.getOffsets(),
+				scroll = this.getScrolls();
+		var position = {
+			x: offset.x - scroll.x,
+			y: offset.y - scroll.y
+		};
+		var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
+		return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
+	},
+
+	getCoordinates: function(element){
+		if (isBody(this)) return this.getWindow().getCoordinates();
+		var position = this.getPosition(element),
+				size = this.getSize();
+		var obj = {
+			left: position.x,
+			top: position.y,
+			width: size.x,
+			height: size.y
+		};
+		obj.right = obj.left + obj.width;
+		obj.bottom = obj.top + obj.height;
+		return obj;
+	},
+
+	computePosition: function(obj){
+		return {
+			left: obj.x - styleNumber(this, 'margin-left'),
+			top: obj.y - styleNumber(this, 'margin-top')
+		};
+	},
+
+	setPosition: function(obj){
+		return this.setStyles(this.computePosition(obj));
+	}
+
+});
+
+
+Native.implement([Document, Window], {
+
+	getSize: function(){
+		if (Browser.Engine.presto || Browser.Engine.webkit){
+			var win = this.getWindow();
+			return {x: win.innerWidth, y: win.innerHeight};
+		}
+		var doc = getCompatElement(this);
+		return {x: doc.clientWidth, y: doc.clientHeight};
+	},
+
+	getScroll: function(){
+		var win = this.getWindow(), doc = getCompatElement(this);
+		return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
+	},
+
+	getScrollSize: function(){
+		var doc = getCompatElement(this), min = this.getSize();
+		return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
+	},
+
+	getPosition: function(){
+		return {x: 0, y: 0};
+	},
+
+	getCoordinates: function(){
+		var size = this.getSize();
+		return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
+	}
+
+});
+
+// private methods
+
+var styleString = Element.getComputedStyle;
+
+function styleNumber(element, style){
+	return styleString(element, style).toInt() || 0;
+};
+
+function borderBox(element){
+	return styleString(element, '-moz-box-sizing') == 'border-box';
+};
+
+function topBorder(element){
+	return styleNumber(element, 'border-top-width');
+};
+
+function leftBorder(element){
+	return styleNumber(element, 'border-left-width');
+};
+
+function isBody(element){
+	return (/^(?:body|html)$/i).test(element.tagName);
+};
+
+function getCompatElement(element){
+	var doc = element.getDocument();
+	return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
+};
+
+})();
+
+//aliases
+Element.alias('setPosition', 'position'); //compatability
+
+Native.implement([Window, Document, Element], {
+
+	getHeight: function(){
+		return this.getSize().y;
+	},
+
+	getWidth: function(){
+		return this.getSize().x;
+	},
+
+	getScrollTop: function(){
+		return this.getScroll().y;
+	},
+
+	getScrollLeft: function(){
+		return this.getScroll().x;
+	},
+
+	getScrollHeight: function(){
+		return this.getScrollSize().y;
+	},
+
+	getScrollWidth: function(){
+		return this.getScrollSize().x;
+	},
+
+	getTop: function(){
+		return this.getPosition().y;
+	},
+
+	getLeft: function(){
+		return this.getPosition().x;
+	}
+
+});
+/*
+---
+
+script: Selectors.js
+
+description: Adds advanced CSS-style querying capabilities for targeting HTML Elements. Includes pseudo selectors.
+
+license: MIT-style license.
+
+requires:
+- /Element
+
+provides: [Selectors]
+
+...
+*/
+
+Native.implement([Document, Element], {
+
+	getElements: function(expression, nocash){
+		expression = expression.split(',');
+		var items, local = {};
+		for (var i = 0, l = expression.length; i < l; i++){
+			var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
+			if (i != 0 && elements.item) elements = $A(elements);
+			items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
+		}
+		return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
+	}
+
+});
+
+Element.implement({
+
+	match: function(selector){
+		if (!selector || (selector == this)) return true;
+		var tagid = Selectors.Utils.parseTagAndID(selector);
+		var tag = tagid[0], id = tagid[1];
+		if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
+		var parsed = Selectors.Utils.parseSelector(selector);
+		return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
+	}
+
+});
+
+var Selectors = {Cache: {nth: {}, parsed: {}}};
+
+Selectors.RegExps = {
+	id: (/#([\w-]+)/),
+	tag: (/^(\w+|\*)/),
+	quick: (/^(\w+|\*)$/),
+	splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
+	combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
+};
+
+Selectors.Utils = {
+
+	chk: function(item, uniques){
+		if (!uniques) return true;
+		var uid = $uid(item);
+		if (!uniques[uid]) return uniques[uid] = true;
+		return false;
+	},
+
+	parseNthArgument: function(argument){
+		if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
+		var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
+		if (!parsed) return false;
+		var inta = parseInt(parsed[1], 10);
+		var a = (inta || inta === 0) ? inta : 1;
+		var special = parsed[2] || false;
+		var b = parseInt(parsed[3], 10) || 0;
+		if (a != 0){
+			b--;
+			while (b < 1) b += a;
+			while (b >= a) b -= a;
+		} else {
+			a = b;
+			special = 'index';
+		}
+		switch (special){
+			case 'n': parsed = {a: a, b: b, special: 'n'}; break;
+			case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
+			case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
+			case 'first': parsed = {a: 0, special: 'index'}; break;
+			case 'last': parsed = {special: 'last-child'}; break;
+			case 'only': parsed = {special: 'only-child'}; break;
+			default: parsed = {a: (a - 1), special: 'index'};
+		}
+
+		return Selectors.Cache.nth[argument] = parsed;
+	},
+
+	parseSelector: function(selector){
+		if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
+		var m, parsed = {classes: [], pseudos: [], attributes: []};
+		while ((m = Selectors.RegExps.combined.exec(selector))){
+			var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
+			if (cn){
+				parsed.classes.push(cn);
+			} else if (pn){
+				var parser = Selectors.Pseudo.get(pn);
+				if (parser) parsed.pseudos.push({parser: parser, argument: pa});
+				else parsed.attributes.push({name: pn, operator: '=', value: pa});
+			} else if (an){
+				parsed.attributes.push({name: an, operator: ao, value: av});
+			}
+		}
+		if (!parsed.classes.length) delete parsed.classes;
+		if (!parsed.attributes.length) delete parsed.attributes;
+		if (!parsed.pseudos.length) delete parsed.pseudos;
+		if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
+		return Selectors.Cache.parsed[selector] = parsed;
+	},
+
+	parseTagAndID: function(selector){
+		var tag = selector.match(Selectors.RegExps.tag);
+		var id = selector.match(Selectors.RegExps.id);
+		return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
+	},
+
+	filter: function(item, parsed, local){
+		var i;
+		if (parsed.classes){
+			for (i = parsed.classes.length; i--; i){
+				var cn = parsed.classes[i];
+				if (!Selectors.Filters.byClass(item, cn)) return false;
+			}
+		}
+		if (parsed.attributes){
+			for (i = parsed.attributes.length; i--; i){
+				var att = parsed.attributes[i];
+				if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
+			}
+		}
+		if (parsed.pseudos){
+			for (i = parsed.pseudos.length; i--; i){
+				var psd = parsed.pseudos[i];
+				if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
+			}
+		}
+		return true;
+	},
+
+	getByTagAndID: function(ctx, tag, id){
+		if (id){
+			var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
+			return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
+		} else {
+			return ctx.getElementsByTagName(tag);
+		}
+	},
+
+	search: function(self, expression, local){
+		var splitters = [];
+
+		var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
+			splitters.push(m1);
+			return ':)' + m2;
+		}).split(':)');
+
+		var items, filtered, item;
+
+		for (var i = 0, l = selectors.length; i < l; i++){
+
+			var selector = selectors[i];
+
+			if (i == 0 && Selectors.RegExps.quick.test(selector)){
+				items = self.getElementsByTagName(selector);
+				continue;
+			}
+
+			var splitter = splitters[i - 1];
+
+			var tagid = Selectors.Utils.parseTagAndID(selector);
+			var tag = tagid[0], id = tagid[1];
+
+			if (i == 0){
+				items = Selectors.Utils.getByTagAndID(self, tag, id);
+			} else {
+				var uniques = {}, found = [];
+				for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
+				items = found;
+			}
+
+			var parsed = Selectors.Utils.parseSelector(selector);
+
+			if (parsed){
+				filtered = [];
+				for (var m = 0, n = items.length; m < n; m++){
+					item = items[m];
+					if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
+				}
+				items = filtered;
+			}
+
+		}
+
+		return items;
+
+	}
+
+};
+
+Selectors.Getters = {
+
+	' ': function(found, self, tag, id, uniques){
+		var items = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = items.length; i < l; i++){
+			var item = items[i];
+			if (Selectors.Utils.chk(item, uniques)) found.push(item);
+		}
+		return found;
+	},
+
+	'>': function(found, self, tag, id, uniques){
+		var children = Selectors.Utils.getByTagAndID(self, tag, id);
+		for (var i = 0, l = children.length; i < l; i++){
+			var child = children[i];
+			if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
+		}
+		return found;
+	},
+
+	'+': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+				break;
+			}
+		}
+		return found;
+	},
+
+	'~': function(found, self, tag, id, uniques){
+		while ((self = self.nextSibling)){
+			if (self.nodeType == 1){
+				if (!Selectors.Utils.chk(self, uniques)) break;
+				if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
+			}
+		}
+		return found;
+	}
+
+};
+
+Selectors.Filters = {
+
+	byTag: function(self, tag){
+		return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
+	},
+
+	byID: function(self, id){
+		return (!id || (self.id && self.id == id));
+	},
+
+	byClass: function(self, klass){
+		return (self.className && self.className.contains && self.className.contains(klass, ' '));
+	},
+
+	byPseudo: function(self, parser, argument, local){
+		return parser.call(self, argument, local);
+	},
+
+	byAttribute: function(self, name, operator, value){
+		var result = Element.prototype.getProperty.call(self, name);
+		if (!result) return (operator == '!=');
+		if (!operator || value == undefined) return true;
+		switch (operator){
+			case '=': return (result == value);
+			case '*=': return (result.contains(value));
+			case '^=': return (result.substr(0, value.length) == value);
+			case '$=': return (result.substr(result.length - value.length) == value);
+			case '!=': return (result != value);
+			case '~=': return result.contains(value, ' ');
+			case '|=': return result.contains(value, '-');
+		}
+		return false;
+	}
+
+};
+
+Selectors.Pseudo = new Hash({
+
+	// w3c pseudo selectors
+
+	checked: function(){
+		return this.checked;
+	},
+	
+	empty: function(){
+		return !(this.innerText || this.textContent || '').length;
+	},
+
+	not: function(selector){
+		return !Element.match(this, selector);
+	},
+
+	contains: function(text){
+		return (this.innerText || this.textContent || '').contains(text);
+	},
+
+	'first-child': function(){
+		return Selectors.Pseudo.index.call(this, 0);
+	},
+
+	'last-child': function(){
+		var element = this;
+		while ((element = element.nextSibling)){
+			if (element.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'only-child': function(){
+		var prev = this;
+		while ((prev = prev.previousSibling)){
+			if (prev.nodeType == 1) return false;
+		}
+		var next = this;
+		while ((next = next.nextSibling)){
+			if (next.nodeType == 1) return false;
+		}
+		return true;
+	},
+
+	'nth-child': function(argument, local){
+		argument = (argument == undefined) ? 'n' : argument;
+		var parsed = Selectors.Utils.parseNthArgument(argument);
+		if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
+		var count = 0;
+		local.positions = local.positions || {};
+		var uid = $uid(this);
+		if (!local.positions[uid]){
+			var self = this;
+			while ((self = self.previousSibling)){
+				if (self.nodeType != 1) continue;
+				count ++;
+				var position = local.positions[$uid(self)];
+				if (position != undefined){
+					count = position + count;
+					break;
+				}
+			}
+			local.positions[uid] = count;
+		}
+		return (local.positions[uid] % parsed.a == parsed.b);
+	},
+
+	// custom pseudo selectors
+
+	index: function(index){
+		var element = this, count = 0;
+		while ((element = element.previousSibling)){
+			if (element.nodeType == 1 && ++count > index) return false;
+		}
+		return (count == index);
+	},
+
+	even: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
+	},
+
+	odd: function(argument, local){
+		return Selectors.Pseudo['nth-child'].call(this, '2n', local);
+	},
+	
+	selected: function(){
+		return this.selected;
+	},
+	
+	enabled: function(){
+		return (this.disabled === false);
+	}
+
+});
+/*
+---
+
+script: DomReady.js
+
+description: Contains the custom event domready.
+
+license: MIT-style license.
+
+requires:
+- /Element.Event
+
+provides: [DomReady]
+
+...
+*/
+
+Element.Events.domready = {
+
+	onAdd: function(fn){
+		if (Browser.loaded) fn.call(this);
+	}
+
+};
+
+(function(){
+
+	var domready = function(){
+		if (Browser.loaded) return;
+		Browser.loaded = true;
+		window.fireEvent('domready');
+		document.fireEvent('domready');
+	};
+	
+	window.addEvent('load', domready);
+
+	if (Browser.Engine.trident){
+		var temp = document.createElement('div');
+		(function(){
+			($try(function(){
+				temp.doScroll(); // Technique by Diego Perini
+				return document.id(temp).inject(document.body).set('html', 'temp').dispose();
+			})) ? domready() : arguments.callee.delay(50);
+		})();
+	} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
+		(function(){
+			(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
+		})();
+	} else {
+		document.addEvent('DOMContentLoaded', domready);
+	}
+
+})();
+/*
+---
+
+script: JSON.js
+
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+See Also: <http://www.json.org/>
+
+requires:
+- /Array
+- /String
+- /Number
+- /Function
+- /Hash
+
+provides: [JSON]
+
+...
+*/
+
+var JSON = new Hash(this.JSON && {
+	stringify: JSON.stringify,
+	parse: JSON.parse
+}).extend({
+	
+	$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},
+
+	$replaceChars: function(chr){
+		return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
+	},
+
+	encode: function(obj){
+		switch ($type(obj)){
+			case 'string':
+				return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
+			case 'array':
+				return '[' + String(obj.map(JSON.encode).clean()) + ']';
+			case 'object': case 'hash':
+				var string = [];
+				Hash.each(obj, function(value, key){
+					var json = JSON.encode(value);
+					if (json) string.push(JSON.encode(key) + ':' + json);
+				});
+				return '{' + string + '}';
+			case 'number': case 'boolean': return String(obj);
+			case false: return 'null';
+		}
+		return null;
+	},
+
+	decode: function(string, secure){
+		if ($type(string) != 'string' || !string.length) return null;
+		if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
+		return eval('(' + string + ')');
+	}
+
+});
+
+Native.implement([Hash, Array, String, Number], {
+
+	toJSON: function(){
+		return JSON.encode(this);
+	}
+
+});
+/*
+---
+
+script: Cookie.js
+
+description: Class for creating, reading, and deleting browser Cookies.
+
+license: MIT-style license.
+
+credits:
+- Based on the functions by Peter-Paul Koch (http://quirksmode.org).
+
+requires:
+- /Options
+
+provides: [Cookie]
+
+...
+*/
+
+var Cookie = new Class({
+
+	Implements: Options,
+
+	options: {
+		path: false,
+		domain: false,
+		duration: false,
+		secure: false,
+		document: document
+	},
+
+	initialize: function(key, options){
+		this.key = key;
+		this.setOptions(options);
+	},
+
+	write: function(value){
+		value = encodeURIComponent(value);
+		if (this.options.domain) value += '; domain=' + this.options.domain;
+		if (this.options.path) value += '; path=' + this.options.path;
+		if (this.options.duration){
+			var date = new Date();
+			date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
+			value += '; expires=' + date.toGMTString();
+		}
+		if (this.options.secure) value += '; secure';
+		this.options.document.cookie = this.key + '=' + value;
+		return this;
+	},
+
+	read: function(){
+		var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
+		return (value) ? decodeURIComponent(value[1]) : null;
+	},
+
+	dispose: function(){
+		new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
+		return this;
+	}
+
+});
+
+Cookie.write = function(key, value, options){
+	return new Cookie(key, options).write(value);
+};
+
+Cookie.read = function(key){
+	return new Cookie(key).read();
+};
+
+Cookie.dispose = function(key, options){
+	return new Cookie(key, options).dispose();
+};
+/*
+---
+
+script: Swiff.js
+
+description: Wrapper for embedding SWF movies. Supports External Interface Communication.
+
+license: MIT-style license.
+
+credits: 
+- Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
+
+requires:
+- /Options
+- /$util
+
+provides: [Swiff]
+
+...
+*/
+
+var Swiff = new Class({
+
+	Implements: [Options],
+
+	options: {
+		id: null,
+		height: 1,
+		width: 1,
+		container: null,
+		properties: {},
+		params: {
+			quality: 'high',
+			allowScriptAccess: 'always',
+			wMode: 'transparent',
+			swLiveConnect: true
+		},
+		callBacks: {},
+		vars: {}
+	},
+
+	toElement: function(){
+		return this.object;
+	},
+
+	initialize: function(path, options){
+		this.instance = 'Swiff_' + $time();
+
+		this.setOptions(options);
+		options = this.options;
+		var id = this.id = options.id || this.instance;
+		var container = document.id(options.container);
+
+		Swiff.CallBacks[this.instance] = {};
+
+		var params = options.params, vars = options.vars, callBacks = options.callBacks;
+		var properties = $extend({height: options.height, width: options.width}, options.properties);
+
+		var self = this;
+
+		for (var callBack in callBacks){
+			Swiff.CallBacks[this.instance][callBack] = (function(option){
+				return function(){
+					return option.apply(self.object, arguments);
+				};
+			})(callBacks[callBack]);
+			vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
+		}
+
+		params.flashVars = Hash.toQueryString(vars);
+		if (Browser.Engine.trident){
+			properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			params.movie = path;
+		} else {
+			properties.type = 'application/x-shockwave-flash';
+			properties.data = path;
+		}
+		var build = '<object id="' + id + '"';
+		for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
+		build += '>';
+		for (var param in params){
+			if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
+		}
+		build += '</object>';
+		this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
+	},
+
+	replaces: function(element){
+		element = document.id(element, true);
+		element.parentNode.replaceChild(this.toElement(), element);
+		return this;
+	},
+
+	inject: function(element){
+		document.id(element, true).appendChild(this.toElement());
+		return this;
+	},
+
+	remote: function(){
+		return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
+	}
+
+});
+
+Swiff.CallBacks = {};
+
+Swiff.remote = function(obj, fn){
+	var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
+	return eval(rs);
+};
+/*
+---
+
+script: Fx.js
+
+description: Contains the basic animation logic to be extended by all other Fx Classes.
+
+license: MIT-style license.
+
+requires:
+- /Chain
+- /Events
+- /Options
+
+provides: [Fx]
+
+...
+*/
+
+var Fx = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {
+		/*
+		onStart: $empty,
+		onCancel: $empty,
+		onComplete: $empty,
+		*/
+		fps: 50,
+		unit: false,
+		duration: 500,
+		link: 'ignore'
+	},
+
+	initialize: function(options){
+		this.subject = this.subject || this;
+		this.setOptions(options);
+		this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
+		var wait = this.options.wait;
+		if (wait === false) this.options.link = 'cancel';
+	},
+
+	getTransition: function(){
+		return function(p){
+			return -(Math.cos(Math.PI * p) - 1) / 2;
+		};
+	},
+
+	step: function(){
+		var time = $time();
+		if (time < this.time + this.options.duration){
+			var delta = this.transition((time - this.time) / this.options.duration);
+			this.set(this.compute(this.from, this.to, delta));
+		} else {
+			this.set(this.compute(this.from, this.to, 1));
+			this.complete();
+		}
+	},
+
+	set: function(now){
+		return now;
+	},
+
+	compute: function(from, to, delta){
+		return Fx.compute(from, to, delta);
+	},
+
+	check: function(){
+		if (!this.timer) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	start: function(from, to){
+		if (!this.check(from, to)) return this;
+		this.from = from;
+		this.to = to;
+		this.time = 0;
+		this.transition = this.getTransition();
+		this.startTimer();
+		this.onStart();
+		return this;
+	},
+
+	complete: function(){
+		if (this.stopTimer()) this.onComplete();
+		return this;
+	},
+
+	cancel: function(){
+		if (this.stopTimer()) this.onCancel();
+		return this;
+	},
+
+	onStart: function(){
+		this.fireEvent('start', this.subject);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', this.subject);
+		if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
+	},
+
+	onCancel: function(){
+		this.fireEvent('cancel', this.subject).clearChain();
+	},
+
+	pause: function(){
+		this.stopTimer();
+		return this;
+	},
+
+	resume: function(){
+		this.startTimer();
+		return this;
+	},
+
+	stopTimer: function(){
+		if (!this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = $clear(this.timer);
+		return true;
+	},
+
+	startTimer: function(){
+		if (this.timer) return false;
+		this.time = $time() - this.time;
+		this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
+		return true;
+	}
+
+});
+
+Fx.compute = function(from, to, delta){
+	return (to - from) * delta + from;
+};
+
+Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};
+/*
+---
+
+script: Fx.CSS.js
+
+description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.
+
+license: MIT-style license.
+
+requires:
+- /Fx
+- /Element.Style
+
+provides: [Fx.CSS]
+
+...
+*/
+
+Fx.CSS = new Class({
+
+	Extends: Fx,
+
+	//prepares the base from/to object
+
+	prepare: function(element, property, values){
+		values = $splat(values);
+		var values1 = values[1];
+		if (!$chk(values1)){
+			values[1] = values[0];
+			values[0] = element.getStyle(property);
+		}
+		var parsed = values.map(this.parse);
+		return {from: parsed[0], to: parsed[1]};
+	},
+
+	//parses a value into an array
+
+	parse: function(value){
+		value = $lambda(value)();
+		value = (typeof value == 'string') ? value.split(' ') : $splat(value);
+		return value.map(function(val){
+			val = String(val);
+			var found = false;
+			Fx.CSS.Parsers.each(function(parser, key){
+				if (found) return;
+				var parsed = parser.parse(val);
+				if ($chk(parsed)) found = {value: parsed, parser: parser};
+			});
+			found = found || {value: val, parser: Fx.CSS.Parsers.String};
+			return found;
+		});
+	},
+
+	//computes by a from and to prepared objects, using their parsers.
+
+	compute: function(from, to, delta){
+		var computed = [];
+		(Math.min(from.length, to.length)).times(function(i){
+			computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
+		});
+		computed.$family = {name: 'fx:css:value'};
+		return computed;
+	},
+
+	//serves the value as settable
+
+	serve: function(value, unit){
+		if ($type(value) != 'fx:css:value') value = this.parse(value);
+		var returned = [];
+		value.each(function(bit){
+			returned = returned.concat(bit.parser.serve(bit.value, unit));
+		});
+		return returned;
+	},
+
+	//renders the change to an element
+
+	render: function(element, property, value, unit){
+		element.setStyle(property, this.serve(value, unit));
+	},
+
+	//searches inside the page css to find the values for a selector
+
+	search: function(selector){
+		if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
+		var to = {};
+		Array.each(document.styleSheets, function(sheet, j){
+			var href = sheet.href;
+			if (href && href.contains('://') && !href.contains(document.domain)) return;
+			var rules = sheet.rules || sheet.cssRules;
+			Array.each(rules, function(rule, i){
+				if (!rule.style) return;
+				var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
+					return m.toLowerCase();
+				}) : null;
+				if (!selectorText || !selectorText.test('^' + selector + '$')) return;
+				Element.Styles.each(function(value, style){
+					if (!rule.style[style] || Element.ShortStyles[style]) return;
+					value = String(rule.style[style]);
+					to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
+				});
+			});
+		});
+		return Fx.CSS.Cache[selector] = to;
+	}
+
+});
+
+Fx.CSS.Cache = {};
+
+Fx.CSS.Parsers = new Hash({
+
+	Color: {
+		parse: function(value){
+			if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
+			return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
+		},
+		compute: function(from, to, delta){
+			return from.map(function(value, i){
+				return Math.round(Fx.compute(from[i], to[i], delta));
+			});
+		},
+		serve: function(value){
+			return value.map(Number);
+		}
+	},
+
+	Number: {
+		parse: parseFloat,
+		compute: Fx.compute,
+		serve: function(value, unit){
+			return (unit) ? value + unit : value;
+		}
+	},
+
+	String: {
+		parse: $lambda(false),
+		compute: $arguments(1),
+		serve: $arguments(0)
+	}
+
+});
+/*
+---
+
+script: Fx.Tween.js
+
+description: Formerly Fx.Style, effect to transition any CSS property for an element.
+
+license: MIT-style license.
+
+requires: 
+- /Fx.CSS
+
+provides: [Fx.Tween, Element.fade, Element.highlight]
+
+...
+*/
+
+Fx.Tween = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(property, now){
+		if (arguments.length == 1){
+			now = property;
+			property = this.property || this.options.property;
+		}
+		this.render(this.element, property, now, this.options.unit);
+		return this;
+	},
+
+	start: function(property, from, to){
+		if (!this.check(property, from, to)) return this;
+		var args = Array.flatten(arguments);
+		this.property = this.options.property || args.shift();
+		var parsed = this.prepare(this.element, this.property, args);
+		return this.parent(parsed.from, parsed.to);
+	}
+
+});
+
+Element.Properties.tween = {
+
+	set: function(options){
+		var tween = this.retrieve('tween');
+		if (tween) tween.cancel();
+		return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('tween')){
+			if (options || !this.retrieve('tween:options')) this.set('tween', options);
+			this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
+		}
+		return this.retrieve('tween');
+	}
+
+};
+
+Element.implement({
+
+	tween: function(property, from, to){
+		this.get('tween').start(arguments);
+		return this;
+	},
+
+	fade: function(how){
+		var fade = this.get('tween'), o = 'opacity', toggle;
+		how = $pick(how, 'toggle');
+		switch (how){
+			case 'in': fade.start(o, 1); break;
+			case 'out': fade.start(o, 0); break;
+			case 'show': fade.set(o, 1); break;
+			case 'hide': fade.set(o, 0); break;
+			case 'toggle':
+				var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
+				fade.start(o, (flag) ? 0 : 1);
+				this.store('fade:flag', !flag);
+				toggle = true;
+			break;
+			default: fade.start(o, arguments);
+		}
+		if (!toggle) this.eliminate('fade:flag');
+		return this;
+	},
+
+	highlight: function(start, end){
+		if (!end){
+			end = this.retrieve('highlight:original', this.getStyle('background-color'));
+			end = (end == 'transparent') ? '#fff' : end;
+		}
+		var tween = this.get('tween');
+		tween.start('background-color', start || '#ffff88', end).chain(function(){
+			this.setStyle('background-color', this.retrieve('highlight:original'));
+			tween.callChain();
+		}.bind(this));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Morph.js
+
+description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.
+
+license: MIT-style license.
+
+requires:
+- /Fx.CSS
+
+provides: [Fx.Morph]
+
+...
+*/
+
+Fx.Morph = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+	},
+
+	set: function(now){
+		if (typeof now == 'string') now = this.search(now);
+		for (var p in now) this.render(this.element, p, now[p], this.options.unit);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var p in from) now[p] = this.parent(from[p], to[p], delta);
+		return now;
+	},
+
+	start: function(properties){
+		if (!this.check(properties)) return this;
+		if (typeof properties == 'string') properties = this.search(properties);
+		var from = {}, to = {};
+		for (var p in properties){
+			var parsed = this.prepare(this.element, p, properties[p]);
+			from[p] = parsed.from;
+			to[p] = parsed.to;
+		}
+		return this.parent(from, to);
+	}
+
+});
+
+Element.Properties.morph = {
+
+	set: function(options){
+		var morph = this.retrieve('morph');
+		if (morph) morph.cancel();
+		return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('morph')){
+			if (options || !this.retrieve('morph:options')) this.set('morph', options);
+			this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
+		}
+		return this.retrieve('morph');
+	}
+
+};
+
+Element.implement({
+
+	morph: function(props){
+		this.get('morph').start(props);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Transitions.js
+
+description: Contains a set of advanced transitions to be used with any of the Fx Classes.
+
+license: MIT-style license.
+
+credits:
+- Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
+
+requires:
+- /Fx
+
+provides: [Fx.Transitions]
+
+...
+*/
+
+Fx.implement({
+
+	getTransition: function(){
+		var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
+		if (typeof trans == 'string'){
+			var data = trans.split(':');
+			trans = Fx.Transitions;
+			trans = trans[data[0]] || trans[data[0].capitalize()];
+			if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
+		}
+		return trans;
+	}
+
+});
+
+Fx.Transition = function(transition, params){
+	params = $splat(params);
+	return $extend(transition, {
+		easeIn: function(pos){
+			return transition(pos, params);
+		},
+		easeOut: function(pos){
+			return 1 - transition(1 - pos, params);
+		},
+		easeInOut: function(pos){
+			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
+		}
+	});
+};
+
+Fx.Transitions = new Hash({
+
+	linear: $arguments(0)
+
+});
+
+Fx.Transitions.extend = function(transitions){
+	for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
+};
+
+Fx.Transitions.extend({
+
+	Pow: function(p, x){
+		return Math.pow(p, x[0] || 6);
+	},
+
+	Expo: function(p){
+		return Math.pow(2, 8 * (p - 1));
+	},
+
+	Circ: function(p){
+		return 1 - Math.sin(Math.acos(p));
+	},
+
+	Sine: function(p){
+		return 1 - Math.sin((1 - p) * Math.PI / 2);
+	},
+
+	Back: function(p, x){
+		x = x[0] || 1.618;
+		return Math.pow(p, 2) * ((x + 1) * p - x);
+	},
+
+	Bounce: function(p){
+		var value;
+		for (var a = 0, b = 1; 1; a += b, b /= 2){
+			if (p >= (7 - 4 * a) / 11){
+				value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
+				break;
+			}
+		}
+		return value;
+	},
+
+	Elastic: function(p, x){
+		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
+	}
+
+});
+
+['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
+	Fx.Transitions[transition] = new Fx.Transition(function(p){
+		return Math.pow(p, [i + 2]);
+	});
+});
+/*
+---
+
+script: Request.js
+
+description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
+
+license: MIT-style license.
+
+requires:
+- /Element
+- /Chain
+- /Events
+- /Options
+- /Browser
+
+provides: [Request]
+
+...
+*/
+
+var Request = new Class({
+
+	Implements: [Chain, Events, Options],
+
+	options: {/*
+		onRequest: $empty,
+		onComplete: $empty,
+		onCancel: $empty,
+		onSuccess: $empty,
+		onFailure: $empty,
+		onException: $empty,*/
+		url: '',
+		data: '',
+		headers: {
+			'X-Requested-With': 'XMLHttpRequest',
+			'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+		},
+		async: true,
+		format: false,
+		method: 'post',
+		link: 'ignore',
+		isSuccess: null,
+		emulation: true,
+		urlEncoded: true,
+		encoding: 'utf-8',
+		evalScripts: false,
+		evalResponse: false,
+		noCache: false
+	},
+
+	initialize: function(options){
+		this.xhr = new Browser.Request();
+		this.setOptions(options);
+		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
+		this.headers = new Hash(this.options.headers);
+	},
+
+	onStateChange: function(){
+		if (this.xhr.readyState != 4 || !this.running) return;
+		this.running = false;
+		this.status = 0;
+		$try(function(){
+			this.status = this.xhr.status;
+		}.bind(this));
+		this.xhr.onreadystatechange = $empty;
+		if (this.options.isSuccess.call(this, this.status)){
+			this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
+			this.success(this.response.text, this.response.xml);
+		} else {
+			this.response = {text: null, xml: null};
+			this.failure();
+		}
+	},
+
+	isSuccess: function(){
+		return ((this.status >= 200) && (this.status < 300));
+	},
+
+	processScripts: function(text){
+		if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
+		return text.stripScripts(this.options.evalScripts);
+	},
+
+	success: function(text, xml){
+		this.onSuccess(this.processScripts(text), xml);
+	},
+
+	onSuccess: function(){
+		this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
+	},
+
+	failure: function(){
+		this.onFailure();
+	},
+
+	onFailure: function(){
+		this.fireEvent('complete').fireEvent('failure', this.xhr);
+	},
+
+	setHeader: function(name, value){
+		this.headers.set(name, value);
+		return this;
+	},
+
+	getHeader: function(name){
+		return $try(function(){
+			return this.xhr.getResponseHeader(name);
+		}.bind(this));
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!this.check(options)) return this;
+		this.running = true;
+
+		var type = $type(options);
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		var old = this.options;
+		options = $extend({data: old.data, url: old.url, method: old.method}, options);
+		var data = options.data, url = String(options.url), method = options.method.toLowerCase();
+
+		switch ($type(data)){
+			case 'element': data = document.id(data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(data);
+		}
+
+		if (this.options.format){
+			var format = 'format=' + this.options.format;
+			data = (data) ? format + '&' + data : format;
+		}
+
+		if (this.options.emulation && !['get', 'post'].contains(method)){
+			var _method = '_method=' + method;
+			data = (data) ? _method + '&' + data : _method;
+			method = 'post';
+		}
+
+		if (this.options.urlEncoded && method == 'post'){
+			var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
+			this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
+		}
+
+		if (this.options.noCache){
+			var noCache = 'noCache=' + new Date().getTime();
+			data = (data) ? noCache + '&' + data : noCache;
+		}
+
+		var trimPosition = url.lastIndexOf('/');
+		if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);
+
+		if (data && method == 'get'){
+			url = url + (url.contains('?') ? '&' : '?') + data;
+			data = null;
+		}
+
+		this.xhr.open(method.toUpperCase(), url, this.options.async);
+
+		this.xhr.onreadystatechange = this.onStateChange.bind(this);
+
+		this.headers.each(function(value, key){
+			try {
+				this.xhr.setRequestHeader(key, value);
+			} catch (e){
+				this.fireEvent('exception', [key, value]);
+			}
+		}, this);
+
+		this.fireEvent('request');
+		this.xhr.send(data);
+		if (!this.options.async) this.onStateChange();
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.xhr.abort();
+		this.xhr.onreadystatechange = $empty;
+		this.xhr = new Browser.Request();
+		this.fireEvent('cancel');
+		return this;
+	}
+
+});
+
+(function(){
+
+var methods = {};
+['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
+	methods[method] = function(){
+		var params = Array.link(arguments, {url: String.type, data: $defined});
+		return this.send($extend(params, {method: method}));
+	};
+});
+
+Request.implement(methods);
+
+})();
+
+Element.Properties.send = {
+
+	set: function(options){
+		var send = this.retrieve('send');
+		if (send) send.cancel();
+		return this.eliminate('send').store('send:options', $extend({
+			data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
+		}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('send')){
+			if (options || !this.retrieve('send:options')) this.set('send', options);
+			this.store('send', new Request(this.retrieve('send:options')));
+		}
+		return this.retrieve('send');
+	}
+
+};
+
+Element.implement({
+
+	send: function(url){
+		var sender = this.get('send');
+		sender.send({data: this, url: url || sender.options.url});
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.HTML.js
+
+description: Extends the basic Request Class with additional methods for interacting with HTML responses.
+
+license: MIT-style license.
+
+requires:
+- /Request
+- /Element
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.HTML = new Class({
+
+	Extends: Request,
+
+	options: {
+		update: false,
+		append: false,
+		evalScripts: true,
+		filter: false
+	},
+
+	processHTML: function(text){
+		var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
+		text = (match) ? match[1] : text;
+
+		var container = new Element('div');
+
+		return $try(function(){
+			var root = '<root>' + text + '</root>', doc;
+			if (Browser.Engine.trident){
+				doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = false;
+				doc.loadXML(root);
+			} else {
+				doc = new DOMParser().parseFromString(root, 'text/xml');
+			}
+			root = doc.getElementsByTagName('root')[0];
+			if (!root) return null;
+			for (var i = 0, k = root.childNodes.length; i < k; i++){
+				var child = Element.clone(root.childNodes[i], true, true);
+				if (child) container.grab(child);
+			}
+			return container;
+		}) || container.set('html', text);
+	},
+
+	success: function(text){
+		var options = this.options, response = this.response;
+
+		response.html = text.stripScripts(function(script){
+			response.javascript = script;
+		});
+
+		var temp = this.processHTML(response.html);
+
+		response.tree = temp.childNodes;
+		response.elements = temp.getElements('*');
+
+		if (options.filter) response.tree = response.elements.filter(options.filter);
+		if (options.update) document.id(options.update).empty().set('html', response.html);
+		else if (options.append) document.id(options.append).adopt(temp.getChildren());
+		if (options.evalScripts) $exec(response.javascript);
+
+		this.onSuccess(response.tree, response.elements, response.html, response.javascript);
+	}
+
+});
+
+Element.Properties.load = {
+
+	set: function(options){
+		var load = this.retrieve('load');
+		if (load) load.cancel();
+		return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
+	},
+
+	get: function(options){
+		if (options || ! this.retrieve('load')){
+			if (options || !this.retrieve('load:options')) this.set('load', options);
+			this.store('load', new Request.HTML(this.retrieve('load:options')));
+		}
+		return this.retrieve('load');
+	}
+
+};
+
+Element.implement({
+
+	load: function(){
+		this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
+		return this;
+	}
+
+});
+/*
+---
+
+script: Request.JSON.js
+
+description: Extends the basic Request Class with additional methods for sending and receiving JSON data.
+
+license: MIT-style license.
+
+requires:
+- /Request JSON
+
+provides: [Request.HTML]
+
+...
+*/
+
+Request.JSON = new Class({
+
+	Extends: Request,
+
+	options: {
+		secure: true
+	},
+
+	initialize: function(options){
+		this.parent(options);
+		this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
+	},
+
+	success: function(text){
+		this.response.json = JSON.decode(text, this.options.secure);
+		this.onSuccess(this.response.json, text);
+	}
+
+});
+/*
+---
+
+script: More.js
+
+description: MooTools More
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/MooTools
+
+provides: [MooTools.More]
+
+...
+*/
+
+MooTools.More = {
+	'version': '1.2.4.4',
+	'build': '6f6057dc645fdb7547689183b2311063bd653ddf'
+};/*
+---
+
+script: MooTools.Lang.js
+
+description: Provides methods for localization.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Events
+ - /MooTools.More
+
+provides: [MooTools.Lang]
+
+...
+*/
+
+(function(){
+
+	var data = {
+		language: 'en-US',
+		languages: {
+			'en-US': {}
+		},
+		cascades: ['en-US']
+	};
+	
+	var cascaded;
+
+	MooTools.lang = new Events();
+
+	$extend(MooTools.lang, {
+
+		setLanguage: function(lang){
+			if (!data.languages[lang]) return this;
+			data.language = lang;
+			this.load();
+			this.fireEvent('langChange', lang);
+			return this;
+		},
+
+		load: function() {
+			var langs = this.cascade(this.getCurrentLanguage());
+			cascaded = {};
+			$each(langs, function(set, setName){
+				cascaded[setName] = this.lambda(set);
+			}, this);
+		},
+
+		getCurrentLanguage: function(){
+			return data.language;
+		},
+
+		addLanguage: function(lang){
+			data.languages[lang] = data.languages[lang] || {};
+			return this;
+		},
+
+		cascade: function(lang){
+			var cascades = (data.languages[lang] || {}).cascades || [];
+			cascades.combine(data.cascades);
+			cascades.erase(lang).push(lang);
+			var langs = cascades.map(function(lng){
+				return data.languages[lng];
+			}, this);
+			return $merge.apply(this, langs);
+		},
+
+		lambda: function(set) {
+			(set || {}).get = function(key, args){
+				return $lambda(set[key]).apply(this, $splat(args));
+			};
+			return set;
+		},
+
+		get: function(set, key, args){
+			if (cascaded && cascaded[set]) return (key ? cascaded[set].get(key, args) : cascaded[set]);
+		},
+
+		set: function(lang, set, members){
+			this.addLanguage(lang);
+			langData = data.languages[lang];
+			if (!langData[set]) langData[set] = {};
+			$extend(langData[set], members);
+			if (lang == this.getCurrentLanguage()){
+				this.load();
+				this.fireEvent('langChange', lang);
+			}
+			return this;
+		},
+
+		list: function(){
+			return Hash.getKeys(data.languages);
+		}
+
+	});
+
+})();/*
+---
+
+script: Log.js
+
+description: Provides basic logging functionality for plugins to implement.
+
+license: MIT-style license
+
+authors:
+ - Guillermo Rauch
+ - Thomas Aylott
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Log]
+
+...
+*/
+
+(function(){
+
+var global = this;
+
+var log = function(){
+	if (global.console && console.log){
+		try {
+			console.log.apply(console, arguments);
+		} catch(e) {
+			console.log(Array.slice(arguments));
+		}
+	} else {
+		Log.logged.push(arguments);
+	}
+	return this;
+};
+
+var disabled = function(){
+	this.logged.push(arguments);
+	return this;
+};
+
+this.Log = new Class({
+	
+	logged: [],
+	
+	log: disabled,
+	
+	resetLog: function(){
+		this.logged.empty();
+		return this;
+	},
+
+	enableLog: function(){
+		this.log = log;
+		this.logged.each(function(args){
+			this.log.apply(this, args);
+		}, this);
+		return this.resetLog();
+	},
+
+	disableLog: function(){
+		this.log = disabled;
+		return this;
+	}
+	
+});
+
+Log.extend(new Log).enableLog();
+
+// legacy
+Log.logger = function(){
+	return this.log.apply(this, arguments);
+};
+
+})();/*
+---
+
+script: Depender.js
+
+description: A stand alone dependency loader for the MooTools library.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Events
+ - core:1.2.4/Request.JSON
+ - /MooTools.More
+ - /Log
+
+provides: Depender
+
+...
+*/
+
+var Depender = {
+
+	options: {
+		/* 
+		onRequire: $empty(options),
+		onRequirementLoaded: $empty([scripts, options]),
+		onScriptLoaded: $empty({
+			script: script, 
+			totalLoaded: percentOfTotalLoaded, 
+			loaded: scriptsState
+		}),
+		serial: false,
+		target: null,
+		noCache: false,
+		log: false,*/
+		loadedSources: [],
+		loadedScripts: ['Core', 'Browser', 'Array', 'String', 'Function', 'Number', 'Hash', 'Element', 'Event', 'Element.Event', 'Class', 'DomReady', 'Class.Extras', 'Request', 'JSON', 'Request.JSON', 'More', 'Depender', 'Log'],
+		useScriptInjection: true
+	},
+
+	loaded: [],
+
+	sources: {},
+
+	libs: {},
+
+	include: function(libs){
+		this.log('include: ', libs);
+		this.mapLoaded = false;
+		var loader = function(data){
+			this.libs = $merge(this.libs, data);
+			$each(this.libs, function(data, lib){
+				if (data.scripts) this.loadSource(lib, data.scripts);
+			}, this);
+		}.bind(this);
+		if ($type(libs) == 'string'){
+			this.log('fetching libs ', libs);
+			this.request(libs, loader);
+		} else {
+			loader(libs);
+		}
+		return this;
+	},
+
+	required: [],
+
+	require: function(options){
+		var loaded = function(){
+			var scripts = this.calculateDependencies(options.scripts);
+			if (options.sources){
+				options.sources.each(function(source){
+					scripts.combine(this.libs[source].files);
+				}, this);
+			}
+			if (options.serial) scripts.combine(this.getLoadedScripts());
+			options.scripts = scripts;
+			this.required.push(options);
+			this.fireEvent('require', options);
+			this.loadScripts(options.scripts);
+		};
+		if (this.mapLoaded) loaded.call(this);
+		else this.addEvent('mapLoaded', loaded.bind(this));
+		return this;
+	},
+
+	cleanDoubleSlash: function(str){
+		if (!str) return str;
+		var prefix = '';
+		if (str.test(/^http:\/\//)){
+			prefix = 'http://';
+			str = str.substring(7, str.length);
+		}
+		str = str.replace(/\/\//g, '/');
+		return prefix + str;
+	},
+
+	request: function(url, callback){
+		new Request.JSON({
+			url: url,
+			secure: false,
+			onSuccess: callback
+		}).send();
+	},
+
+	loadSource: function(lib, source){
+		if (this.libs[lib].files){
+			this.dataLoaded();
+			return;
+		}
+		this.log('loading source: ', source);
+		this.request(this.cleanDoubleSlash(source + '/scripts.json'), function(result){
+			this.log('loaded source: ', source);
+			this.libs[lib].files = result;
+			this.dataLoaded();
+		}.bind(this));
+	},
+
+	dataLoaded: function(){
+		var loaded = true;
+		$each(this.libs, function(v, k){
+			if (!this.libs[k].files) loaded = false;
+		}, this);
+		if (loaded){
+			this.mapTree();
+			this.mapLoaded = true;
+			this.calculateLoaded();
+			this.lastLoaded = this.getLoadedScripts().getLength();
+			this.fireEvent('mapLoaded');
+			this.removeEvents('mapLoaded');
+		}
+	},
+
+	calculateLoaded: function(){
+		var set = function(script){
+			this.scriptsState[script] = true;
+		}.bind(this);
+		if (this.options.loadedScripts) this.options.loadedScripts.each(set);
+		if (this.options.loadedSources){
+			this.options.loadedSources.each(function(lib){
+				$each(this.libs[lib].files, function(dir){
+					$each(dir, function(data, file){
+						set(file);
+					}, this);
+				}, this);
+			}, this);
+		}
+	},
+
+	deps: {},
+
+	pathMap: {},
+
+	mapTree: function(){
+		$each(this.libs, function(data, source){
+			$each(data.files, function(scripts, folder){
+				$each(scripts, function(details, script){
+					var path = source + ':' + folder + ':' + script;
+					if (this.deps[path]) return;
+					this.deps[path] = details.deps;
+					this.pathMap[script] = path;
+				}, this);
+			}, this);
+		}, this);
+	},
+
+	getDepsForScript: function(script){
+		return this.deps[this.pathMap[script]] || [];
+	},
+
+	calculateDependencies: function(scripts){
+		var reqs = [];
+		$splat(scripts).each(function(script){
+			if (script == 'None' || !script) return;
+			var deps = this.getDepsForScript(script);
+			if (!deps){
+				if (window.console && console.warn) console.warn('dependencies not mapped: script: %o, map: %o, :deps: %o', script, this.pathMap, this.deps);
+			} else {
+				deps.each(function(scr){
+					if (scr == script || scr == 'None' || !scr) return;
+					if (!reqs.contains(scr)) reqs.combine(this.calculateDependencies(scr));
+					reqs.include(scr);
+				}, this);
+			}
+			reqs.include(script);
+		}, this);
+		return reqs;
+	},
+
+	getPath: function(script){
+		try {
+			var chunks = this.pathMap[script].split(':');
+			var lib = this.libs[chunks[0]];
+			var dir = (lib.path || lib.scripts) + '/';
+			chunks.shift();
+			return this.cleanDoubleSlash(dir + chunks.join('/') + '.js');
+		} catch(e){
+			return script;
+		}
+	},
+
+	loadScripts: function(scripts){
+		scripts = scripts.filter(function(s){
+			if (!this.scriptsState[s] && s != 'None'){
+				this.scriptsState[s] = false;
+				return true;
+			}
+		}, this);
+		if (scripts.length){
+			scripts.each(function(scr){
+				this.loadScript(scr);
+			}, this);
+		} else {
+			this.check();
+		}
+	},
+
+	toLoad: [],
+
+	loadScript: function(script){
+		if (this.scriptsState[script] && this.toLoad.length){
+			this.loadScript(this.toLoad.shift());
+			return;
+		} else if (this.loading){
+			this.toLoad.push(script);
+			return;
+		}
+		var finish = function(){
+			this.loading = false;
+			this.scriptLoaded(script);
+			if (this.toLoad.length) this.loadScript(this.toLoad.shift());
+		}.bind(this);
+		var error = function(){
+			this.log('could not load: ', scriptPath);
+		}.bind(this);
+		this.loading = true;
+		var scriptPath = this.getPath(script);
+		if (this.options.useScriptInjection){
+			this.log('injecting script: ', scriptPath);
+			var loaded = function(){
+				this.log('loaded script: ', scriptPath);
+				finish();
+			}.bind(this);
+			new Element('script', {
+				src: scriptPath + (this.options.noCache ? '?noCache=' + new Date().getTime() : ''),
+				events: {
+					load: loaded,
+					readystatechange: function(){
+						if (['loaded', 'complete'].contains(this.readyState)) loaded();
+					},
+					error: error
+				}
+			}).inject(this.options.target || document.head);
+		} else {
+			this.log('requesting script: ', scriptPath);
+			new Request({
+				url: scriptPath,
+				noCache: this.options.noCache,
+				onComplete: function(js){
+					this.log('loaded script: ', scriptPath);
+					$exec(js);
+					finish();
+				}.bind(this),
+				onFailure: error,
+				onException: error
+			}).send();
+		}
+	},
+
+	scriptsState: $H(),
+	
+	getLoadedScripts: function(){
+		return this.scriptsState.filter(function(state){
+			return state;
+		});
+	},
+
+	scriptLoaded: function(script){
+		this.log('loaded script: ', script);
+		this.scriptsState[script] = true;
+		this.check();
+		var loaded = this.getLoadedScripts();
+		var loadedLength = loaded.getLength();
+		var toLoad = this.scriptsState.getLength();
+		this.fireEvent('scriptLoaded', {
+			script: script,
+			totalLoaded: (loadedLength / toLoad * 100).round(),
+			currentLoaded: ((loadedLength - this.lastLoaded) / (toLoad - this.lastLoaded) * 100).round(),
+			loaded: loaded
+		});
+		if (loadedLength == toLoad) this.lastLoaded = loadedLength;
+	},
+
+	lastLoaded: 0,
+
+	check: function(){
+		var incomplete = [];
+		this.required.each(function(required){
+			var loaded = [];
+			required.scripts.each(function(script){
+				if (this.scriptsState[script]) loaded.push(script);
+			}, this);
+			if (required.onStep){
+				required.onStep({
+					percent: loaded.length / required.scripts.length * 100,
+					scripts: loaded
+				});
+			};
+			if (required.scripts.length != loaded.length) return;
+			required.callback();
+			this.required.erase(required);
+			this.fireEvent('requirementLoaded', [loaded, required]);
+		}, this);
+	}
+
+};
+
+$extend(Depender, new Events);
+$extend(Depender, new Options);
+$extend(Depender, new Log);
+
+Depender._setOptions = Depender.setOptions;
+Depender.setOptions = function(){
+	Depender._setOptions.apply(Depender, arguments);
+	if (this.options.log) Depender.enableLog();
+	return this;
+};
+/*
+---
+
+script: Class.Refactor.js
+
+description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Class.refactor]
+
+...
+*/
+
+Class.refactor = function(original, refactors){
+
+	$each(refactors, function(item, name){
+		var origin = original.prototype[name];
+		if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
+			var old = this.previous;
+			this.previous = origin;
+			var value = item.apply(this, arguments);
+			this.previous = old;
+			return value;
+		}); else original.implement(name, item);
+	});
+
+	return original;
+
+};/*
+---
+
+script: Class.Binds.js
+
+description: Automagically binds specified methods in a class to the instance of the class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Class
+ - /MooTools.More
+
+provides: [Class.Binds]
+
+...
+*/
+
+Class.Mutators.Binds = function(binds){
+    return binds;
+};
+
+Class.Mutators.initialize = function(initialize){
+	return function(){
+		$splat(this.Binds).each(function(name){
+			var original = this[name];
+			if (original) this[name] = original.bind(this);
+		}, this);
+		return initialize.apply(this, arguments);
+	};
+};
+/*
+---
+
+script: Class.Occlude.js
+
+description: Prevents a class from being applied to a DOM element twice.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires: 
+ - core/1.2.4/Class
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Class.Occlude]
+
+...
+*/
+
+Class.Occlude = new Class({
+
+	occlude: function(property, element){
+		element = document.id(element || this.element);
+		var instance = element.retrieve(property || this.property);
+		if (instance && !$defined(this.occluded))
+			return this.occluded = instance;
+
+		this.occluded = false;
+		element.store(property || this.property, this);
+		return this.occluded;
+	}
+
+});/*
+---
+
+script: Chain.Wait.js
+
+description: value, Adds a method to inject pauses between chained events.
+
+license: MIT-style license.
+
+authors:
+ - Aaron Newton
+
+requires: 
+ - core:1.2.4/Chain
+ - core:1.2.4/Element
+ - core:1.2.4/Fx
+ - /MooTools.More
+
+provides: [Chain.Wait]
+
+...
+*/
+
+(function(){
+
+	var wait = {
+		wait: function(duration){
+			return this.chain(function(){
+				this.callChain.delay($pick(duration, 500), this);
+			}.bind(this));
+		}
+	};
+
+	Chain.implement(wait);
+
+	if (window.Fx){
+		Fx.implement(wait);
+		['Css', 'Tween', 'Elements'].each(function(cls){
+			if (Fx[cls]) Fx[cls].implement(wait);
+		});
+	}
+
+	Element.implement({
+		chains: function(effects){
+			$splat($pick(effects, ['tween', 'morph', 'reveal'])).each(function(effect){
+				effect = this.get(effect);
+				if (!effect) return;
+				effect.setOptions({
+					link:'chain'
+				});
+			}, this);
+			return this;
+		},
+		pauseFx: function(duration, effect){
+			this.chains(effect).get($pick(effect, 'tween')).wait(duration);
+			return this;
+		}
+	});
+
+})();/*
+---
+
+script: Array.Extras.js
+
+description: Extends the Array native object to include useful methods to work with arrays.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Array
+
+provides: [Array.Extras]
+
+...
+*/
+Array.implement({
+
+	min: function(){
+		return Math.min.apply(null, this);
+	},
+
+	max: function(){
+		return Math.max.apply(null, this);
+	},
+
+	average: function(){
+		return this.length ? this.sum() / this.length : 0;
+	},
+
+	sum: function(){
+		var result = 0, l = this.length;
+		if (l){
+			do {
+				result += this[--l];
+			} while (l);
+		}
+		return result;
+	},
+
+	unique: function(){
+		return [].combine(this);
+	},
+
+	shuffle: function(){
+		for (var i = this.length; i && --i;){
+			var temp = this[i], r = Math.floor(Math.random() * ( i + 1 ));
+			this[i] = this[r];
+			this[r] = temp;
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Date.English.US.js
+
+description: Date messages for US English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.English.US]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Date', {
+
+	months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
+	days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['month', 'date', 'year'],
+	shortDate: '%m/%d/%Y',
+	shortTime: '%I:%M%p',
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1st, 2nd, 3rd, etc.
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'less than a minute ago',
+	minuteAgo: 'about a minute ago',
+	minutesAgo: '{delta} minutes ago',
+	hourAgo: 'about an hour ago',
+	hoursAgo: 'about {delta} hours ago',
+	dayAgo: '1 day ago',
+	daysAgo: '{delta} days ago',
+	weekAgo: '1 week ago',
+	weeksAgo: '{delta} weeks ago',
+	monthAgo: '1 month ago',
+	monthsAgo: '{delta} months ago',
+	yearAgo: '1 year ago',
+	yearsAgo: '{delta} years ago',
+	lessThanMinuteUntil: 'less than a minute from now',
+	minuteUntil: 'about a minute from now',
+	minutesUntil: '{delta} minutes from now',
+	hourUntil: 'about an hour from now',
+	hoursUntil: 'about {delta} hours from now',
+	dayUntil: '1 day from now',
+	daysUntil: '{delta} days from now',
+	weekUntil: '1 week from now',
+	weeksUntil: '{delta} weeks from now',
+	monthUntil: '1 month from now',
+	monthsUntil: '{delta} months from now',
+	yearUntil: '1 year from now',
+	yearsUntil: '{delta} years from now'
+
+});
+/*
+---
+
+script: Date.js
+
+description: Extends the Date native object to include methods useful in managing dates.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/
+ - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de
+ - Scott Kyle - scott [at] appden.com; http://appden.com
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - core:1.2.4/Number
+ - core:1.2.4/Lang
+ - core:1.2.4/Date.English.US
+ - /MooTools.More
+
+provides: [Date]
+
+...
+*/
+
+(function(){
+
+var Date = this.Date;
+
+if (!Date.now) Date.now = $time;
+
+Date.Methods = {
+	ms: 'Milliseconds',
+	year: 'FullYear',
+	min: 'Minutes',
+	mo: 'Month',
+	sec: 'Seconds',
+	hr: 'Hours'
+};
+
+['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset',
+	'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear',
+	'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds'].each(function(method){
+	Date.Methods[method.toLowerCase()] = method;
+});
+
+var pad = function(what, length){
+	return new Array(length - String(what).length + 1).join('0') + what;
+};
+
+Date.implement({
+
+	set: function(prop, value){
+		switch ($type(prop)){
+			case 'object':
+				for (var p in prop) this.set(p, prop[p]);
+				break;
+			case 'string':
+				prop = prop.toLowerCase();
+				var m = Date.Methods;
+				if (m[prop]) this['set' + m[prop]](value);
+		}
+		return this;
+	},
+
+	get: function(prop){
+		prop = prop.toLowerCase();
+		var m = Date.Methods;
+		if (m[prop]) return this['get' + m[prop]]();
+		return null;
+	},
+
+	clone: function(){
+		return new Date(this.get('time'));
+	},
+
+	increment: function(interval, times){
+		interval = interval || 'day';
+		times = $pick(times, 1);
+
+		switch (interval){
+			case 'year':
+				return this.increment('month', times * 12);
+			case 'month':
+				var d = this.get('date');
+				this.set('date', 1).set('mo', this.get('mo') + times);
+				return this.set('date', d.min(this.get('lastdayofmonth')));
+			case 'week':
+				return this.increment('day', times * 7);
+			case 'day':
+				return this.set('date', this.get('date') + times);
+		}
+
+		if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval');
+
+		return this.set('time', this.get('time') + times * Date.units[interval]());
+	},
+
+	decrement: function(interval, times){
+		return this.increment(interval, -1 * $pick(times, 1));
+	},
+
+	isLeapYear: function(){
+		return Date.isLeapYear(this.get('year'));
+	},
+
+	clearTime: function(){
+		return this.set({hr: 0, min: 0, sec: 0, ms: 0});
+	},
+
+	diff: function(date, resolution){
+		if ($type(date) == 'string') date = Date.parse(date);
+		
+		return ((date - this) / Date.units[resolution || 'day'](3, 3)).toInt(); // non-leap year, 30-day month
+	},
+
+	getLastDayOfMonth: function(){
+		return Date.daysInMonth(this.get('mo'), this.get('year'));
+	},
+
+	getDayOfYear: function(){
+		return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) 
+			- Date.UTC(this.get('year'), 0, 1)) / Date.units.day();
+	},
+
+	getWeek: function(){
+		return (this.get('dayofyear') / 7).ceil();
+	},
+	
+	getOrdinal: function(day){
+		return Date.getMsg('ordinal', day || this.get('date'));
+	},
+
+	getTimezone: function(){
+		return this.toString()
+			.replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1')
+			.replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3');
+	},
+
+	getGMTOffset: function(){
+		var off = this.get('timezoneOffset');
+		return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2);
+	},
+
+	setAMPM: function(ampm){
+		ampm = ampm.toUpperCase();
+		var hr = this.get('hr');
+		if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12);
+		else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12);
+		return this;
+	},
+
+	getAMPM: function(){
+		return (this.get('hr') < 12) ? 'AM' : 'PM';
+	},
+
+	parse: function(str){
+		this.set('time', Date.parse(str));
+		return this;
+	},
+
+	isValid: function(date) {
+		return !!(date || this).valueOf();
+	},
+
+	format: function(f){
+		if (!this.isValid()) return 'invalid date';
+		f = f || '%x %X';
+		f = formats[f.toLowerCase()] || f; // replace short-hand with actual format
+		var d = this;
+		return f.replace(/%([a-z%])/gi,
+			function($0, $1){
+				switch ($1){
+					case 'a': return Date.getMsg('days')[d.get('day')].substr(0, 3);
+					case 'A': return Date.getMsg('days')[d.get('day')];
+					case 'b': return Date.getMsg('months')[d.get('month')].substr(0, 3);
+					case 'B': return Date.getMsg('months')[d.get('month')];
+					case 'c': return d.toString();
+					case 'd': return pad(d.get('date'), 2);
+					case 'H': return pad(d.get('hr'), 2);
+					case 'I': return ((d.get('hr') % 12) || 12);
+					case 'j': return pad(d.get('dayofyear'), 3);
+					case 'm': return pad((d.get('mo') + 1), 2);
+					case 'M': return pad(d.get('min'), 2);
+					case 'o': return d.get('ordinal');
+					case 'p': return Date.getMsg(d.get('ampm'));
+					case 'S': return pad(d.get('seconds'), 2);
+					case 'U': return pad(d.get('week'), 2);
+					case 'w': return d.get('day');
+					case 'x': return d.format(Date.getMsg('shortDate'));
+					case 'X': return d.format(Date.getMsg('shortTime'));
+					case 'y': return d.get('year').toString().substr(2);
+					case 'Y': return d.get('year');
+					case 'T': return d.get('GMTOffset');
+					case 'Z': return d.get('Timezone');
+				}
+				return $1;
+			}
+		);
+	},
+
+	toISOString: function(){
+		return this.format('iso8601');
+	}
+
+});
+
+Date.alias('toISOString', 'toJSON');
+Date.alias('diff', 'compare');
+Date.alias('format', 'strftime');
+
+var formats = {
+	db: '%Y-%m-%d %H:%M:%S',
+	compact: '%Y%m%dT%H%M%S',
+	iso8601: '%Y-%m-%dT%H:%M:%S%T',
+	rfc822: '%a, %d %b %Y %H:%M:%S %Z',
+	'short': '%d %b %H:%M',
+	'long': '%B %d, %Y %H:%M'
+};
+
+var parsePatterns = [];
+var nativeParse = Date.parse;
+
+var parseWord = function(type, word, num){
+	var ret = -1;
+	var translated = Date.getMsg(type + 's');
+
+	switch ($type(word)){
+		case 'object':
+			ret = translated[word.get(type)];
+			break;
+		case 'number':
+			ret = translated[month - 1];
+			if (!ret) throw new Error('Invalid ' + type + ' index: ' + index);
+			break;
+		case 'string':
+			var match = translated.filter(function(name){
+				return this.test(name);
+			}, new RegExp('^' + word, 'i'));
+			if (!match.length)    throw new Error('Invalid ' + type + ' string');
+			if (match.length > 1) throw new Error('Ambiguous ' + type);
+			ret = match[0];
+	}
+
+	return (num) ? translated.indexOf(ret) : ret;
+};
+
+Date.extend({
+
+	getMsg: function(key, args) {
+		return MooTools.lang.get('Date', key, args);
+	},
+
+	units: {
+		ms: $lambda(1),
+		second: $lambda(1000),
+		minute: $lambda(60000),
+		hour: $lambda(3600000),
+		day: $lambda(86400000),
+		week: $lambda(608400000),
+		month: function(month, year){
+			var d = new Date;
+			return Date.daysInMonth($pick(month, d.get('mo')), $pick(year, d.get('year'))) * 86400000;
+		},
+		year: function(year){
+			year = year || new Date().get('year');
+			return Date.isLeapYear(year) ? 31622400000 : 31536000000;
+		}
+	},
+
+	daysInMonth: function(month, year){
+		return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
+	},
+
+	isLeapYear: function(year){
+		return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
+	},
+
+	parse: function(from){
+		var t = $type(from);
+		if (t == 'number') return new Date(from);
+		if (t != 'string') return from;
+		from = from.clean();
+		if (!from.length) return null;
+
+		var parsed;
+		parsePatterns.some(function(pattern){
+			var bits = pattern.re.exec(from);
+			return (bits) ? (parsed = pattern.handler(bits)) : false;
+		});
+
+		return parsed || new Date(nativeParse(from));
+	},
+
+	parseDay: function(day, num){
+		return parseWord('day', day, num);
+	},
+
+	parseMonth: function(month, num){
+		return parseWord('month', month, num);
+	},
+
+	parseUTC: function(value){
+		var localDate = new Date(value);
+		var utcSeconds = Date.UTC(
+			localDate.get('year'),
+			localDate.get('mo'),
+			localDate.get('date'),
+			localDate.get('hr'),
+			localDate.get('min'),
+			localDate.get('sec')
+		);
+		return new Date(utcSeconds);
+	},
+
+	orderIndex: function(unit){
+		return Date.getMsg('dateOrder').indexOf(unit) + 1;
+	},
+
+	defineFormat: function(name, format){
+		formats[name] = format;
+	},
+
+	defineFormats: function(formats){
+		for (var name in formats) Date.defineFormat(name, formats[name]);
+	},
+
+	parsePatterns: parsePatterns, // this is deprecated
+	
+	defineParser: function(pattern){
+		parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern));
+	},
+	
+	defineParsers: function(){
+		Array.flatten(arguments).each(Date.defineParser);
+	},
+	
+	define2DigitYearStart: function(year){
+		startYear = year % 100;
+		startCentury = year - startYear;
+	}
+
+});
+
+var startCentury = 1900;
+var startYear = 70;
+
+var regexOf = function(type){
+	return new RegExp('(?:' + Date.getMsg(type).map(function(name){
+		return name.substr(0, 3);
+	}).join('|') + ')[a-z]*');
+};
+
+var replacers = function(key){
+	switch(key){
+		case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first
+			return ((Date.orderIndex('month') == 1) ? '%m[.-/]%d' : '%d[.-/]%m') + '([.-/]%y)?';
+		case 'X':
+			return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?';
+	}
+	return null;
+};
+
+var keys = {
+	d: /[0-2]?[0-9]|3[01]/,
+	H: /[01]?[0-9]|2[0-3]/,
+	I: /0?[1-9]|1[0-2]/,
+	M: /[0-5]?\d/,
+	s: /\d+/,
+	o: /[a-z]*/,
+	p: /[ap]\.?m\.?/,
+	y: /\d{2}|\d{4}/,
+	Y: /\d{4}/,
+	T: /Z|[+-]\d{2}(?::?\d{2})?/
+};
+
+keys.m = keys.I;
+keys.S = keys.M;
+
+var currentLanguage;
+
+var recompile = function(language){
+	currentLanguage = language;
+	
+	keys.a = keys.A = regexOf('days');
+	keys.b = keys.B = regexOf('months');
+	
+	parsePatterns.each(function(pattern, i){
+		if (pattern.format) parsePatterns[i] = build(pattern.format);
+	});
+};
+
+var build = function(format){
+	if (!currentLanguage) return {format: format};
+	
+	var parsed = [];
+	var re = (format.source || format) // allow format to be regex
+	 .replace(/%([a-z])/gi,
+		function($0, $1){
+			return replacers($1) || $0;
+		}
+	).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing
+	 .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas
+	 .replace(/%([a-z%])/gi,
+		function($0, $1){
+			var p = keys[$1];
+			if (!p) return $1;
+			parsed.push($1);
+			return '(' + p.source + ')';
+		}
+	).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff]'); // handle unicode words
+
+	return {
+		format: format,
+		re: new RegExp('^' + re + '$', 'i'),
+		handler: function(bits){
+			bits = bits.slice(1).associate(parsed);
+			var date = new Date().clearTime();
+			if ('d' in bits) handle.call(date, 'd', 1);
+			if ('m' in bits || 'b' in bits || 'B' in bits) handle.call(date, 'm', 1);
+			for (var key in bits) handle.call(date, key, bits[key]);
+			return date;
+		}
+	};
+};
+
+var handle = function(key, value){
+	if (!value) return this;
+
+	switch(key){
+		case 'a': case 'A': return this.set('day', Date.parseDay(value, true));
+		case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true));
+		case 'd': return this.set('date', value);
+		case 'H': case 'I': return this.set('hr', value);
+		case 'm': return this.set('mo', value - 1);
+		case 'M': return this.set('min', value);
+		case 'p': return this.set('ampm', value.replace(/\./g, ''));
+		case 'S': return this.set('sec', value);
+		case 's': return this.set('ms', ('0.' + value) * 1000);
+		case 'w': return this.set('day', value);
+		case 'Y': return this.set('year', value);
+		case 'y':
+			value = +value;
+			if (value < 100) value += startCentury + (value < startYear ? 100 : 0);
+			return this.set('year', value);
+		case 'T':
+			if (value == 'Z') value = '+00';
+			var offset = value.match(/([+-])(\d{2}):?(\d{2})?/);
+			offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset();
+			return this.set('time', this - offset * 60000);
+	}
+
+	return this;
+};
+
+Date.defineParsers(
+	'%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601
+	'%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact
+	'%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM"
+	'%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm"
+	'%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched
+	'%Y %b( %d%o( %X)?)?', // Same as above with year coming first
+	'%o %b %d %X %T %Y' // "Thu Oct 22 08:11:23 +0000 2009"
+);
+
+MooTools.lang.addEvent('langChange', function(language){
+	if (MooTools.lang.get('Date')) recompile(language);
+}).fireEvent('langChange', MooTools.lang.getCurrentLanguage());
+
+})();/*
+---
+
+script: Date.Extras.js
+
+description: Extends the Date native object to include extra methods (on top of those in Date.js).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - /Date
+
+provides: [Date.Extras]
+
+...
+*/
+
+Date.implement({
+
+	timeDiffInWords: function(relative_to){
+		return Date.distanceOfTimeInWords(this, relative_to || new Date);
+	},
+
+	timeDiff: function(to, joiner){
+		if (to == null) to = new Date;
+		var delta = ((to - this) / 1000).toInt();
+		if (!delta) return '0s';
+		
+		var durations = {s: 60, m: 60, h: 24, d: 365, y: 0};
+		var duration, vals = [];
+		
+		for (var step in durations){
+			if (!delta) break;
+			if ((duration = durations[step])){
+				vals.unshift((delta % duration) + step);
+				delta = (delta / duration).toInt();
+			} else {
+				vals.unshift(delta + step);
+			}
+		}
+		
+		return vals.join(joiner || ':');
+	}
+
+});
+
+Date.alias('timeDiffInWords', 'timeAgoInWords');
+
+Date.extend({
+
+	distanceOfTimeInWords: function(from, to){
+		return Date.getTimePhrase(((to - from) / 1000).toInt());
+	},
+
+	getTimePhrase: function(delta){
+		var suffix = (delta < 0) ? 'Until' : 'Ago';
+		if (delta < 0) delta *= -1;
+		
+		var units = {
+			minute: 60,
+			hour: 60,
+			day: 24,
+			week: 7,
+			month: 52 / 12,
+			year: 12,
+			eon: Infinity
+		};
+		
+		var msg = 'lessThanMinute';
+		
+		for (var unit in units){
+			var interval = units[unit];
+			if (delta < 1.5 * interval){
+				if (delta > 0.75 * interval) msg = unit;
+				break;
+			}
+			delta /= interval;
+			msg = unit + 's';
+		}
+		
+		return Date.getMsg(msg + suffix).substitute({delta: delta.round()});
+	}
+
+});
+
+
+Date.defineParsers(
+
+	{
+		// "today", "tomorrow", "yesterday"
+		re: /^(?:tod|tom|yes)/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			switch(bits[0]){
+				case 'tom': return d.increment();
+				case 'yes': return d.decrement();
+				default: 	return d;
+			}
+		}
+	},
+
+	{
+		// "next Wednesday", "last Thursday"
+		re: /^(next|last) ([a-z]+)$/i,
+		handler: function(bits){
+			var d = new Date().clearTime();
+			var day = d.getDay();
+			var newDay = Date.parseDay(bits[2], true);
+			var addDays = newDay - day;
+			if (newDay <= day) addDays += 7;
+			if (bits[1] == 'last') addDays -= 7;
+			return d.set('date', d.getDate() + addDays);
+		}
+	}
+
+);
+/*
+---
+
+script: Hash.Extras.js
+
+description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Hash.base
+ - /MooTools.More
+
+provides: [Hash.Extras]
+
+...
+*/
+
+Hash.implement({
+
+	getFromPath: function(notation){
+		var source = this.getClean();
+		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
+			if (!source) return null;
+			var prop = arguments[2] || arguments[1] || arguments[0];
+			source = (prop in source) ? source[prop] : null;
+			return match;
+		});
+		return source;
+	},
+
+	cleanValues: function(method){
+		method = method || $defined;
+		this.each(function(v, k){
+			if (!method(v)) this.erase(k);
+		}, this);
+		return this;
+	},
+
+	run: function(){
+		var args = arguments;
+		this.each(function(v, k){
+			if ($type(v) == 'function') v.run(args);
+		});
+	}
+
+});/*
+---
+
+script: String.Extras.js
+
+description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+
+requires:
+ - core:1.2.4/String
+ - core:1.2.4/$util
+ - core:1.2.4/Array
+
+provides: [String.Extras]
+
+...
+*/
+
+(function(){
+  
+var special = ['À','à','Ã?','á','Â','â','Ã','ã','Ä','ä','Ã…','Ã¥','Ä‚','ă','Ä„','Ä…','Ć','ć','ÄŒ','Ä?','Ç','ç', 'ÄŽ','Ä?','Ä?','Ä‘', 'È','è','É','é','Ê','ê','Ë','ë','Äš','Ä›','Ę','Ä™', 'Äž','ÄŸ','ÃŒ','ì','Ã?','í','ÃŽ','î','Ã?','ï', 'Ĺ','ĺ','Ľ','ľ','Å?','Å‚', 'Ñ','ñ','Ň','ň','Ń','Å„','Ã’','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','Å‘','Ř','Å™','Å”','Å•','Å ','Å¡','Åž','ÅŸ','Åš','Å›', 'Ť','Å¥','Ť','Å¥','Å¢','Å£','Ù','ù','Ú','ú','Û','û','Ãœ','ü','Å®','ů', 'Ÿ','ÿ','ý','Ã?','Ž','ž','Ź','ź','Å»','ż', 'Þ','þ','Ã?','ð','ß','Å’','Å“','Æ','æ','µ'];
+
+var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];
+
+var tidymap = {
+	"[\xa0\u2002\u2003\u2009]": " ",
+	"\xb7": "*",
+	"[\u2018\u2019]": "'",
+	"[\u201c\u201d]": '"',
+	"\u2026": "...",
+	"\u2013": "-",
+	"\u2014": "--",
+	"\uFFFD": "&raquo;"
+};
+
+var getRegForTag = function(tag, contents) {
+	tag = tag || '';
+	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
+	reg = new RegExp(regstr, "gi");
+	return reg;
+};
+
+String.implement({
+
+	standardize: function(){
+		var text = this;
+		special.each(function(ch, i){
+			text = text.replace(new RegExp(ch, 'g'), standard[i]);
+		});
+		return text;
+	},
+
+	repeat: function(times){
+		return new Array(times + 1).join(this);
+	},
+
+	pad: function(length, str, dir){
+		if (this.length >= length) return this;
+		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
+		if (!dir || dir == 'right') return this + pad;
+		if (dir == 'left') return pad + this;
+		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
+	},
+
+	getTags: function(tag, contents){
+		return this.match(getRegForTag(tag, contents)) || [];
+	},
+
+	stripTags: function(tag, contents){
+		return this.replace(getRegForTag(tag, contents), '');
+	},
+
+	tidy: function(){
+		var txt = this.toString();
+		$each(tidymap, function(value, key){
+			txt = txt.replace(new RegExp(key, 'g'), value);
+		});
+		return txt;
+	}
+
+});
+
+})();/*
+---
+
+script: String.QueryString.js
+
+description: Methods for dealing with URI query strings.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - /MooTools.More
+
+provides: [String.QueryString]
+
+...
+*/
+
+String.implement({
+
+	parseQueryString: function(){
+		var vars = this.split(/[&;]/), res = {};
+		if (vars.length) vars.each(function(val){
+			var index = val.indexOf('='),
+				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
+				value = decodeURIComponent(val.substr(index + 1)),
+				obj = res;
+			keys.each(function(key, i){
+				var current = obj[key];
+				if(i < keys.length - 1)
+					obj = obj[key] = current || {};
+				else if($type(current) == 'array')
+					current.push(value);
+				else
+					obj[key] = $defined(current) ? [current, value] : value;
+			});
+		});
+		return res;
+	},
+
+	cleanQueryString: function(method){
+		return this.split('&').filter(function(val){
+			var index = val.indexOf('='),
+			key = index < 0 ? '' : val.substr(0, index),
+			value = val.substr(index + 1);
+			return method ? method.run([key, value]) : $chk(value);
+		}).join('&');
+	}
+
+});/*
+---
+
+script: URI.js
+
+description: Provides methods useful in managing the window location and uris.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Selectors
+ - /String.QueryString
+
+provides: URI
+
+...
+*/
+
+var URI = new Class({
+
+	Implements: Options,
+
+	options: {
+		/*base: false*/
+	},
+
+	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
+	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
+	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},
+
+	initialize: function(uri, options){
+		this.setOptions(options);
+		var base = this.options.base || URI.base;
+		if(!uri) uri = base;
+		
+		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
+		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
+	},
+
+	parse: function(value, base){
+		var bits = value.match(this.regex);
+		if (!bits) return false;
+		bits.shift();
+		return this.merge(bits.associate(this.parts), base);
+	},
+
+	merge: function(bits, base){
+		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
+		if (base){
+			this.parts.every(function(part){
+				if (bits[part]) return false;
+				bits[part] = base[part] || '';
+				return true;
+			});
+		}
+		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
+		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
+		return bits;
+	},
+
+	parseDirectory: function(directory, baseDirectory) {
+		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
+		if (!directory.test(URI.regs.directoryDot)) return directory;
+		var result = [];
+		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
+			if (dir == '..' && result.length > 0) result.pop();
+			else if (dir != '.') result.push(dir);
+		});
+		return result.join('/') + '/';
+	},
+
+	combine: function(bits){
+		return bits.value || bits.scheme + '://' +
+			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
+			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
+			(bits.directory || '/') + (bits.file || '') +
+			(bits.query ? '?' + bits.query : '') +
+			(bits.fragment ? '#' + bits.fragment : '');
+	},
+
+	set: function(part, value, base){
+		if (part == 'value'){
+			var scheme = value.match(URI.regs.scheme);
+			if (scheme) scheme = scheme[1];
+			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
+			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
+		} else if (part == 'data') {
+			this.setData(value);
+		} else {
+			this.parsed[part] = value;
+		}
+		return this;
+	},
+
+	get: function(part, base){
+		switch(part){
+			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
+			case 'data' : return this.getData();
+		}
+		return this.parsed[part] || '';
+	},
+
+	go: function(){
+		document.location.href = this.toString();
+	},
+
+	toURI: function(){
+		return this;
+	},
+
+	getData: function(key, part){
+		var qs = this.get(part || 'query');
+		if (!$chk(qs)) return key ? null : {};
+		var obj = qs.parseQueryString();
+		return key ? obj[key] : obj;
+	},
+
+	setData: function(values, merge, part){
+		if (typeof values == 'string'){
+			data = this.getData();
+			data[arguments[0]] = arguments[1];
+			values = data;
+		} else if (merge) {
+			values = $merge(this.getData(), values);
+		}
+		return this.set(part || 'query', Hash.toQueryString(values));
+	},
+
+	clearData: function(part){
+		return this.set(part || 'query', '');
+	}
+
+});
+
+URI.prototype.toString = URI.prototype.valueOf = function(){
+	return this.get('value');
+};
+
+URI.regs = {
+	endSlash: /\/$/,
+	scheme: /^(\w+):/,
+	directoryDot: /\.\/|\.$/
+};
+
+URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});
+
+String.implement({
+
+	toURI: function(options){
+		return new URI(this, options);
+	}
+
+});/*
+---
+
+script: URI.Relative.js
+
+description: Extends the URI class to add methods for computing relative and absolute urls.
+
+license: MIT-style license
+
+authors:
+ - Sebastian Markbåge
+
+
+requires:
+ - /Class.refactor
+ - /URI
+
+provides: [URI.Relative]
+
+...
+*/
+
+URI = Class.refactor(URI, {
+
+	combine: function(bits, base){
+		if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port)
+			return this.previous.apply(this, arguments);
+		var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : '');
+
+		if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end;
+
+		var baseDir = base.directory.split('/'),
+			relDir = bits.directory.split('/'),
+			path = '',
+			offset;
+
+		var i = 0;
+		for(offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++);
+		for(i = 0; i < baseDir.length - offset - 1; i++) path += '../';
+		for(i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/';
+
+		return (path || (bits.file ? '' : './')) + end;
+	},
+
+	toAbsolute: function(base){
+		base = new URI(base);
+		if (base) base.set('directory', '').set('file', '');
+		return this.toRelative(base);
+	},
+
+	toRelative: function(base){
+		return this.get('value', new URI(base));
+	}
+
+});/*
+---
+
+script: Element.Forms.js
+
+description: Extends the Element native object to include methods useful in managing inputs.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Element.Forms]
+
+...
+*/
+
+Element.implement({
+
+	tidy: function(){
+		this.set('value', this.get('value').tidy());
+	},
+
+	getTextInRange: function(start, end){
+		return this.get('value').substring(start, end);
+	},
+
+	getSelectedText: function(){
+		if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd());
+		return document.selection.createRange().text;
+	},
+
+	getSelectedRange: function() {
+		if ($defined(this.selectionStart)) return {start: this.selectionStart, end: this.selectionEnd};
+		var pos = {start: 0, end: 0};
+		var range = this.getDocument().selection.createRange();
+		if (!range || range.parentElement() != this) return pos;
+		var dup = range.duplicate();
+		if (this.type == 'text') {
+			pos.start = 0 - dup.moveStart('character', -100000);
+			pos.end = pos.start + range.text.length;
+		} else {
+			var value = this.get('value');
+			var offset = value.length;
+			dup.moveToElementText(this);
+			dup.setEndPoint('StartToEnd', range);
+			if(dup.text.length) offset -= value.match(/[\n\r]*$/)[0].length;
+			pos.end = offset - dup.text.length;
+			dup.setEndPoint('StartToStart', range);
+			pos.start = offset - dup.text.length;
+		}
+		return pos;
+	},
+
+	getSelectionStart: function(){
+		return this.getSelectedRange().start;
+	},
+
+	getSelectionEnd: function(){
+		return this.getSelectedRange().end;
+	},
+
+	setCaretPosition: function(pos){
+		if (pos == 'end') pos = this.get('value').length;
+		this.selectRange(pos, pos);
+		return this;
+	},
+
+	getCaretPosition: function(){
+		return this.getSelectedRange().start;
+	},
+
+	selectRange: function(start, end){
+		if (this.setSelectionRange) {
+			this.focus();
+			this.setSelectionRange(start, end);
+		} else {
+			var value = this.get('value');
+			var diff = value.substr(start, end - start).replace(/\r/g, '').length;
+			start = value.substr(0, start).replace(/\r/g, '').length;
+			var range = this.createTextRange();
+			range.collapse(true);
+			range.moveEnd('character', start + diff);
+			range.moveStart('character', start);
+			range.select();
+		}
+		return this;
+	},
+
+	insertAtCursor: function(value, select){
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length));
+		if ($pick(select, true)) this.selectRange(pos.start, pos.start + value.length);
+		else this.setCaretPosition(pos.start + value.length);
+		return this;
+	},
+
+	insertAroundCursor: function(options, select){
+		options = $extend({
+			before: '',
+			defaultMiddle: '',
+			after: ''
+		}, options);
+		var value = this.getSelectedText() || options.defaultMiddle;
+		var pos = this.getSelectedRange();
+		var text = this.get('value');
+		if (pos.start == pos.end){
+			this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length));
+			this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length);
+		} else {
+			var current = text.substring(pos.start, pos.end);
+			this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length));
+			var selStart = pos.start + options.before.length;
+			if ($pick(select, true)) this.selectRange(selStart, selStart + current.length);
+			else this.setCaretPosition(selStart + text.length);
+		}
+		return this;
+	}
+
+});/*
+---
+
+script: Elements.From.js
+
+description: Returns a collection of elements from a string of html.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - /MooTools.More
+
+provides: [Elements.from]
+
+...
+*/
+
+Elements.from = function(text, excludeScripts){
+	if ($pick(excludeScripts, true)) text = text.stripScripts();
+
+	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);
+
+	if (match){
+		container = new Element('table');
+		var tag = match[1].toLowerCase();
+		if (['td', 'th', 'tr'].contains(tag)){
+			container = new Element('tbody').inject(container);
+			if (tag != 'tr') container = new Element('tr').inject(container);
+		}
+	}
+
+	return (container || new Element('div')).set('html', text).getChildren();
+};/*
+---
+
+script: Element.Delegation.js
+
+description: Extends the Element native object to include the delegate method for more efficient event management.
+
+credits:
+ - "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Daniel Steigerwald
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Selectors
+ - /MooTools.More
+
+provides: [Element.Delegation]
+
+...
+*/
+
+(function(addEvent, removeEvent){
+	
+	var match = /(.*?):relay\(([^)]+)\)$/,
+		combinators = /[+>~\s]/,
+		splitType = function(type){
+			var bits = type.match(match);
+			return !bits ? {event: type} : {
+				event: bits[1],
+				selector: bits[2]
+			};
+		},
+		check = function(e, selector){
+			var t = e.target;
+			if (combinators.test(selector = selector.trim())){
+				var els = this.getElements(selector);
+				for (var i = els.length; i--; ){
+					var el = els[i];
+					if (t == el || el.hasChild(t)) return el;
+				}
+			} else {
+				for ( ; t && t != this; t = t.parentNode){
+					if (Element.match(t, selector)) return document.id(t);
+				}
+			}
+			return null;
+		};
+
+	Element.implement({
+
+		addEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var monitors = this.retrieve('$moo:delegateMonitors', {});
+				if (!monitors[type]){
+					var monitor = function(e){
+						var el = check.call(this, e, splitted.selector);
+						if (el) this.fireEvent(type, [e, el], 0, el);
+					}.bind(this);
+					monitors[type] = monitor;
+					addEvent.call(this, splitted.event, monitor);
+				}
+			}
+			return addEvent.apply(this, arguments);
+		},
+
+		removeEvent: function(type, fn){
+			var splitted = splitType(type);
+			if (splitted.selector){
+				var events = this.retrieve('events');
+				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;
+
+				if (fn) removeEvent.apply(this, [type, fn]);
+				else removeEvent.apply(this, type);
+
+				events = this.retrieve('events');
+				if (events && events[type] && events[type].keys.length == 0){
+					var monitors = this.retrieve('$moo:delegateMonitors', {});
+					removeEvent.apply(this, [splitted.event, monitors[type]]);
+					delete monitors[type];
+				}
+				return this;
+			}
+			return removeEvent.apply(this, arguments);
+		},
+
+		fireEvent: function(type, args, delay, bind){
+			var events = this.retrieve('events');
+			if (!events || !events[type]) return this;
+			events[type].keys.each(function(fn){
+				fn.create({bind: bind || this, delay: delay, arguments: args})();
+			}, this);
+			return this;
+		}
+
+	});
+
+})(Element.prototype.addEvent, Element.prototype.removeEvent);/*
+---
+
+script: Element.Measure.js
+
+description: Extends the Element native object to include methods useful in measuring dimensions.
+
+credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Element.Measure]
+
+...
+*/
+
+Element.implement({
+
+	measure: function(fn){
+		var vis = function(el) {
+			return !!(!el || el.offsetHeight || el.offsetWidth);
+		};
+		if (vis(this)) return fn.apply(this);
+		var parent = this.getParent(),
+			restorers = [],
+			toMeasure = []; 
+		while (!vis(parent) && parent != document.body) {
+			toMeasure.push(parent.expose());
+			parent = parent.getParent();
+		}
+		var restore = this.expose();
+		var result = fn.apply(this);
+		restore();
+		toMeasure.each(function(restore){
+			restore();
+		});
+		return result;
+	},
+
+	expose: function(){
+		if (this.getStyle('display') != 'none') return $empty;
+		var before = this.style.cssText;
+		this.setStyles({
+			display: 'block',
+			position: 'absolute',
+			visibility: 'hidden'
+		});
+		return function(){
+			this.style.cssText = before;
+		}.bind(this);
+	},
+
+	getDimensions: function(options){
+		options = $merge({computeSize: false},options);
+		var dim = {};
+		var getSize = function(el, options){
+			return (options.computeSize)?el.getComputedSize(options):el.getSize();
+		};
+		var parent = this.getParent('body');
+		if (parent && this.getStyle('display') == 'none'){
+			dim = this.measure(function(){
+				return getSize(this, options);
+			});
+		} else if (parent){
+			try { //safari sometimes crashes here, so catch it
+				dim = getSize(this, options);
+			}catch(e){}
+		} else {
+			dim = {x: 0, y: 0};
+		}
+		return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
+	},
+
+	getComputedSize: function(options){
+		options = $merge({
+			styles: ['padding','border'],
+			plains: {
+				height: ['top','bottom'],
+				width: ['left','right']
+			},
+			mode: 'both'
+		}, options);
+		var size = {width: 0,height: 0};
+		switch (options.mode){
+			case 'vertical':
+				delete size.width;
+				delete options.plains.width;
+				break;
+			case 'horizontal':
+				delete size.height;
+				delete options.plains.height;
+				break;
+		}
+		var getStyles = [];
+		//this function might be useful in other places; perhaps it should be outside this function?
+		$each(options.plains, function(plain, key){
+			plain.each(function(edge){
+				options.styles.each(function(style){
+					getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
+				});
+			});
+		});
+		var styles = {};
+		getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
+		var subtracted = [];
+		$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
+			var capitalized = key.capitalize();
+			size['total' + capitalized] = size['computed' + capitalized] = 0;
+			plain.each(function(edge){ //top, left, right, bottom
+				size['computed' + edge.capitalize()] = 0;
+				getStyles.each(function(style, i){ //padding, border, etc.
+					//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
+					if (style.test(edge)){
+						styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
+						size['total' + capitalized] = size['total' + capitalized] + styles[style];
+						size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
+					}
+					//if width != width (so, padding-left, for instance), then subtract that from the total
+					if (style.test(edge) && key != style &&
+						(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
+						subtracted.push(style);
+						size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
+					}
+				});
+			});
+		});
+
+		['Width', 'Height'].each(function(value){
+			var lower = value.toLowerCase();
+			if(!$chk(size[lower])) return;
+
+			size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
+			size['total' + value] = size[lower] + size['total' + value];
+			delete size['computed' + value];
+		}, this);
+
+		return $extend(styles, size);
+	}
+
+});/*
+---
+
+script: Element.Pin.js
+
+description: Extends the Element native object to include the pin method useful for fixed positioning for elements.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Element.Pin]
+
+...
+*/
+
+(function(){
+	var supportsPositionFixed = false;
+	window.addEvent('domready', function(){
+		var test = new Element('div').setStyles({
+			position: 'fixed',
+			top: 0,
+			right: 0
+		}).inject(document.body);
+		supportsPositionFixed = (test.offsetTop === 0);
+		test.dispose();
+	});
+
+	Element.implement({
+
+		pin: function(enable){
+			if (this.getStyle('display') == 'none') return null;
+			
+			var p,
+					scroll = window.getScroll();
+			if (enable !== false){
+				p = this.getPosition();
+				if (!this.retrieve('pinned')){
+					var pos = {
+						top: p.y - scroll.y,
+						left: p.x - scroll.x
+					};
+					if (supportsPositionFixed){
+						this.setStyle('position', 'fixed').setStyles(pos);
+					} else {
+						this.store('pinnedByJS', true);
+						this.setStyles({
+							position: 'absolute',
+							top: p.y,
+							left: p.x
+						}).addClass('isPinned');
+						this.store('scrollFixer', (function(){
+							if (this.retrieve('pinned'))
+								var scroll = window.getScroll();
+								this.setStyles({
+									top: pos.top.toInt() + scroll.y,
+									left: pos.left.toInt() + scroll.x
+								});
+						}).bind(this));
+						window.addEvent('scroll', this.retrieve('scrollFixer'));
+					}
+					this.store('pinned', true);
+				}
+			} else {
+				var op;
+				if (!Browser.Engine.trident){
+					var parent = this.getParent();
+					op = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent());
+				}
+				p = this.getPosition(op);
+				this.store('pinned', false);
+				var reposition;
+				if (supportsPositionFixed && !this.retrieve('pinnedByJS')){
+					reposition = {
+						top: p.y + scroll.y,
+						left: p.x + scroll.x
+					};
+				} else {
+					this.store('pinnedByJS', false);
+					window.removeEvent('scroll', this.retrieve('scrollFixer'));
+					reposition = {
+						top: p.y,
+						left: p.x
+					};
+				}
+				this.setStyles($merge(reposition, {position: 'absolute'})).removeClass('isPinned');
+			}
+			return this;
+		},
+
+		unpin: function(){
+			return this.pin(false);
+		},
+
+		togglepin: function(){
+			this.pin(!this.retrieve('pinned'));
+		}
+
+	});
+
+})();/*
+---
+
+script: Element.Position.js
+
+description: Extends the Element native object to include methods useful positioning elements relative to others.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Element.Measure
+
+provides: [Elements.Position]
+
+...
+*/
+
+(function(){
+
+var original = Element.prototype.position;
+
+Element.implement({
+
+	position: function(options){
+		//call original position if the options are x/y values
+		if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
+		$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
+		options = $merge({
+			// minimum: { x: 0, y: 0 },
+			// maximum: { x: 0, y: 0},
+			relativeTo: document.body,
+			position: {
+				x: 'center', //left, center, right
+				y: 'center' //top, center, bottom
+			},
+			edge: false,
+			offset: {x: 0, y: 0},
+			returnPos: false,
+			relFixedPosition: false,
+			ignoreMargins: false,
+			ignoreScroll: false,
+			allowNegative: false
+		}, options);
+		//compute the offset of the parent positioned element if this element is in one
+		var parentOffset = {x: 0, y: 0}, 
+				parentPositioned = false;
+		/* dollar around getOffsetParent should not be necessary, but as it does not return
+		 * a mootools extended element in IE, an error occurs on the call to expose. See:
+		 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
+		var offsetParent = this.measure(function(){
+			return document.id(this.getOffsetParent());
+		});
+		if (offsetParent && offsetParent != this.getDocument().body){
+			parentOffset = offsetParent.measure(function(){
+				return this.getPosition();
+			});
+			parentPositioned = offsetParent != document.id(options.relativeTo);
+			options.offset.x = options.offset.x - parentOffset.x;
+			options.offset.y = options.offset.y - parentOffset.y;
+		}
+		//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
+		//topRight, topLeft, centerTop, centerBottom, center
+		var fixValue = function(option){
+			if ($type(option) != 'string') return option;
+			option = option.toLowerCase();
+			var val = {};
+			if (option.test('left')) val.x = 'left';
+			else if (option.test('right')) val.x = 'right';
+			else val.x = 'center';
+			if (option.test('upper') || option.test('top')) val.y = 'top';
+			else if (option.test('bottom')) val.y = 'bottom';
+			else val.y = 'center';
+			return val;
+		};
+		options.edge = fixValue(options.edge);
+		options.position = fixValue(options.position);
+		if (!options.edge){
+			if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
+			else options.edge = {x:'left', y:'top'};
+		}
+
+		this.setStyle('position', 'absolute');
+		var rel = document.id(options.relativeTo) || document.body,
+				calc = rel == document.body ? window.getScroll() : rel.getPosition(),
+				top = calc.y, left = calc.x;
+
+		var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
+		var pos = {},
+				prefY = options.offset.y,
+				prefX = options.offset.x,
+				winSize = window.getSize();
+		switch(options.position.x){
+			case 'left':
+				pos.x = left + prefX;
+				break;
+			case 'right':
+				pos.x = left + prefX + rel.offsetWidth;
+				break;
+			default: //center
+				pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
+				break;
+		}
+		switch(options.position.y){
+			case 'top':
+				pos.y = top + prefY;
+				break;
+			case 'bottom':
+				pos.y = top + prefY + rel.offsetHeight;
+				break;
+			default: //center
+				pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
+				break;
+		}
+		if (options.edge){
+			var edgeOffset = {};
+
+			switch(options.edge.x){
+				case 'left':
+					edgeOffset.x = 0;
+					break;
+				case 'right':
+					edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
+					break;
+				default: //center
+					edgeOffset.x = -(dim.totalWidth/2);
+					break;
+			}
+			switch(options.edge.y){
+				case 'top':
+					edgeOffset.y = 0;
+					break;
+				case 'bottom':
+					edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
+					break;
+				default: //center
+					edgeOffset.y = -(dim.totalHeight/2);
+					break;
+			}
+			pos.x += edgeOffset.x;
+			pos.y += edgeOffset.y;
+		}
+		pos = {
+			left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
+			top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
+		};
+		var xy = {left: 'x', top: 'y'};
+		['minimum', 'maximum'].each(function(minmax) {
+			['left', 'top'].each(function(lr) {
+				var val = options[minmax] ? options[minmax][xy[lr]] : null;
+				if (val != null && pos[lr] < val) pos[lr] = val;
+			});
+		});
+		if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
+			var winScroll = window.getScroll();
+			pos.top+= winScroll.y;
+			pos.left+= winScroll.x;
+		}
+		if (options.ignoreScroll) {
+			var relScroll = rel.getScroll();
+			pos.top-= relScroll.y;
+			pos.left-= relScroll.x;
+		}
+		if (options.ignoreMargins) {
+			pos.left += (
+				options.edge.x == 'right' ? dim['margin-right'] : 
+				options.edge.x == 'center' ? -dim['margin-left'] + ((dim['margin-right'] + dim['margin-left'])/2) : 
+					- dim['margin-left']
+			);
+			pos.top += (
+				options.edge.y == 'bottom' ? dim['margin-bottom'] : 
+				options.edge.y == 'center' ? -dim['margin-top'] + ((dim['margin-bottom'] + dim['margin-top'])/2) : 
+					- dim['margin-top']
+			);
+		}
+		pos.left = Math.ceil(pos.left);
+		pos.top = Math.ceil(pos.top);
+		if (options.returnPos) return pos;
+		else this.setStyles(pos);
+		return this;
+	}
+
+});
+
+})();/*
+---
+
+script: Element.Shortcuts.js
+
+description: Extends the Element native object to include some shortcut methods.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Element.Shortcuts]
+
+...
+*/
+
+Element.implement({
+
+	isDisplayed: function(){
+		return this.getStyle('display') != 'none';
+	},
+
+	isVisible: function(){
+		var w = this.offsetWidth,
+			h = this.offsetHeight;
+		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.isDisplayed();
+	},
+
+	toggle: function(){
+		return this[this.isDisplayed() ? 'hide' : 'show']();
+	},
+
+	hide: function(){
+		var d;
+		try {
+			//IE fails here if the element is not in the dom
+			d = this.getStyle('display');
+		} catch(e){}
+		return this.store('originalDisplay', d || '').setStyle('display', 'none');
+	},
+
+	show: function(display){
+		display = display || this.retrieve('originalDisplay') || 'block';
+		return this.setStyle('display', (display == 'none') ? 'block' : display);
+	},
+
+	swapClass: function(remove, add){
+		return this.removeClass(remove).addClass(add);
+	}
+
+});
+/*
+---
+
+script: IframeShim.js
+
+description: Defines IframeShim, a class for obscuring select lists and flash objects in IE.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Options Events
+ - /Element.Position
+ - /Class.Occlude
+
+provides: [IframeShim]
+
+...
+*/
+
+var IframeShim = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		className: 'iframeShim',
+		src: 'javascript:false;document.write("");',
+		display: false,
+		zIndex: null,
+		margin: 0,
+		offset: {x: 0, y: 0},
+		browsers: (Browser.Engine.trident4 || (Browser.Engine.gecko && !Browser.Engine.gecko19 && Browser.Platform.mac))
+	},
+
+	property: 'IframeShim',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.makeShim();
+		return this;
+	},
+
+	makeShim: function(){
+		if(this.options.browsers){
+			var zIndex = this.element.getStyle('zIndex').toInt();
+
+			if (!zIndex){
+				zIndex = 1;
+				var pos = this.element.getStyle('position');
+				if (pos == 'static' || !pos) this.element.setStyle('position', 'relative');
+				this.element.setStyle('zIndex', zIndex);
+			}
+			zIndex = ($chk(this.options.zIndex) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1;
+			if (zIndex < 0) zIndex = 1;
+			this.shim = new Element('iframe', {
+				src: this.options.src,
+				scrolling: 'no',
+				frameborder: 0,
+				styles: {
+					zIndex: zIndex,
+					position: 'absolute',
+					border: 'none',
+					filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)'
+				},
+				'class': this.options.className
+			}).store('IframeShim', this);
+			var inject = (function(){
+				this.shim.inject(this.element, 'after');
+				this[this.options.display ? 'show' : 'hide']();
+				this.fireEvent('inject');
+			}).bind(this);
+			if (!IframeShim.ready) window.addEvent('load', inject);
+			else inject();
+		} else {
+			this.position = this.hide = this.show = this.dispose = $lambda(this);
+		}
+	},
+
+	position: function(){
+		if (!IframeShim.ready || !this.shim) return this;
+		var size = this.element.measure(function(){ 
+			return this.getSize(); 
+		});
+		if (this.options.margin != undefined){
+			size.x = size.x - (this.options.margin * 2);
+			size.y = size.y - (this.options.margin * 2);
+			this.options.offset.x += this.options.margin;
+			this.options.offset.y += this.options.margin;
+		}
+		this.shim.set({width: size.x, height: size.y}).position({
+			relativeTo: this.element,
+			offset: this.options.offset
+		});
+		return this;
+	},
+
+	hide: function(){
+		if (this.shim) this.shim.setStyle('display', 'none');
+		return this;
+	},
+
+	show: function(){
+		if (this.shim) this.shim.setStyle('display', 'block');
+		return this.position();
+	},
+
+	dispose: function(){
+		if (this.shim) this.shim.dispose();
+		return this;
+	},
+
+	destroy: function(){
+		if (this.shim) this.shim.destroy();
+		return this;
+	}
+
+});
+
+window.addEvent('load', function(){
+	IframeShim.ready = true;
+});/*
+---
+
+script: Mask.js
+
+description: Creates a mask element to cover another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - /Class.Binds
+ - /Element.Position
+ - /IframeShim
+
+provides: [Mask]
+
+...
+*/
+
+var Mask = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['position'],
+
+	options: {
+		// onShow: $empty,
+		// onHide: $empty,
+		// onDestroy: $empty,
+		// onClick: $empty,
+		//inject: {
+		//  where: 'after',
+		//  target: null,
+		//},
+		// hideOnClick: false,
+		// id: null,
+		// destroyOnHide: false,
+		style: {},
+		'class': 'mask',
+		maskMargins: false,
+		useIframeShim: true,
+		iframeShimOptions: {}
+	},
+
+	initialize: function(target, options){
+		this.target = document.id(target) || document.id(document.body);
+		this.target.store('Mask', this);
+		this.setOptions(options);
+		this.render();
+		this.inject();
+	},
+	
+	render: function() {
+		this.element = new Element('div', {
+			'class': this.options['class'],
+			id: this.options.id || 'mask-' + $time(),
+			styles: $merge(this.options.style, {
+				display: 'none'
+			}),
+			events: {
+				click: function(){
+					this.fireEvent('click');
+					if (this.options.hideOnClick) this.hide();
+				}.bind(this)
+			}
+		});
+		this.hidden = true;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	inject: function(target, where){
+		where = where || this.options.inject ? this.options.inject.where : '' || this.target == document.body ? 'inside' : 'after';
+		target = target || this.options.inject ? this.options.inject.target : '' || this.target;
+		this.element.inject(target, where);
+		if (this.options.useIframeShim) {
+			this.shim = new IframeShim(this.element, this.options.iframeShimOptions);
+			this.addEvents({
+				show: this.shim.show.bind(this.shim),
+				hide: this.shim.hide.bind(this.shim),
+				destroy: this.shim.destroy.bind(this.shim)
+			});
+		}
+	},
+
+	position: function(){
+		this.resize(this.options.width, this.options.height);
+		this.element.position({
+			relativeTo: this.target,
+			position: 'topLeft',
+			ignoreMargins: !this.options.maskMargins,
+			ignoreScroll: this.target == document.body
+		});
+		return this;
+	},
+
+	resize: function(x, y){
+		var opt = {
+			styles: ['padding', 'border']
+		};
+		if (this.options.maskMargins) opt.styles.push('margin');
+		var dim = this.target.getComputedSize(opt);
+		if (this.target == document.body) {
+			var win = window.getSize();
+			if (dim.totalHeight < win.y) dim.totalHeight = win.y;
+			if (dim.totalWidth < win.x) dim.totalWidth = win.x;
+		}
+		this.element.setStyles({
+			width: $pick(x, dim.totalWidth, dim.x),
+			height: $pick(y, dim.totalHeight, dim.y)
+		});
+		return this;
+	},
+
+	show: function(){
+		if (!this.hidden) return this;
+		window.addEvent('resize', this.position);
+		this.position();
+		this.showMask.apply(this, arguments);
+		return this;
+	},
+
+	showMask: function(){
+		this.element.setStyle('display', 'block');
+		this.hidden = false;
+		this.fireEvent('show');
+	},
+
+	hide: function(){
+		if (this.hidden) return this;
+		window.removeEvent('resize', this.position);
+		this.hideMask.apply(this, arguments);
+		if (this.options.destroyOnHide) return this.destroy();
+		return this;
+	},
+
+	hideMask: function(){
+		this.element.setStyle('display', 'none');
+		this.hidden = true;
+		this.fireEvent('hide');
+	},
+
+	toggle: function(){
+		this[this.hidden ? 'show' : 'hide']();
+	},
+
+	destroy: function(){
+		this.hide();
+		this.element.destroy();
+		this.fireEvent('destroy');
+		this.target.eliminate('mask');
+	}
+
+});
+
+Element.Properties.mask = {
+
+	set: function(options){
+		var mask = this.retrieve('mask');
+		return this.eliminate('mask').store('mask:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('mask')){
+			if (this.retrieve('mask')) this.retrieve('mask').destroy();
+			if (options || !this.retrieve('mask:options')) this.set('mask', options);
+			this.store('mask', new Mask(this, this.retrieve('mask:options')));
+		}
+		return this.retrieve('mask');
+	}
+
+};
+
+Element.implement({
+
+	mask: function(options){
+		this.get('mask', options).show();
+		return this;
+	},
+
+	unmask: function(){
+		this.get('mask').hide();
+		return this;
+	}
+
+});/*
+---
+
+script: Spinner.js
+
+description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Tween
+ - /Class.refactor
+ - /Mask
+
+provides: [Spinner]
+
+...
+*/
+
+var Spinner = new Class({
+
+	Extends: Mask,
+
+	options: {
+		/*message: false,*/
+		'class':'spinner',
+		containerPosition: {},
+		content: {
+			'class':'spinner-content'
+		},
+		messageContainer: {
+			'class':'spinner-msg'
+		},
+		img: {
+			'class':'spinner-img'
+		},
+		fxOptions: {
+			link: 'chain'
+		}
+	},
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		this.target.store('spinner', this);
+
+		//add this to events for when noFx is true; parent methods handle hide/show
+		var deactivate = function(){ this.active = false; }.bind(this);
+		this.addEvents({
+			hide: deactivate,
+			show: deactivate
+		});
+	},
+
+	render: function(){
+		this.parent();
+		this.element.set('id', this.options.id || 'spinner-'+$time());
+		this.content = document.id(this.options.content) || new Element('div', this.options.content);
+		this.content.inject(this.element);
+		if (this.options.message) {
+			this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message);
+			this.msg.inject(this.content);
+		}
+		if (this.options.img) {
+			this.img = document.id(this.options.img) || new Element('div', this.options.img);
+			this.img.inject(this.content);
+		}
+		this.element.set('tween', this.options.fxOptions);
+	},
+
+	show: function(noFx){
+		if (this.active) return this.chain(this.show.bind(this));
+		if (!this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	showMask: function(noFx){
+		var pos = function(){
+			this.content.position($merge({
+				relativeTo: this.element
+			}, this.options.containerPosition));
+		}.bind(this);
+		if (noFx) {
+			this.parent();
+			pos();
+		} else {
+			this.element.setStyles({
+				display: 'block',
+				opacity: 0
+			}).tween('opacity', this.options.style.opacity || 0.9);
+			pos();
+			this.hidden = false;
+			this.fireEvent('show');
+			this.callChain();
+		}
+	},
+
+	hide: function(noFx){
+		if (this.active) return this.chain(this.hide.bind(this));
+		if (this.hidden) {
+			this.callChain.delay(20, this);
+			return this;
+		}
+		this.active = true;
+		return this.parent(noFx);
+	},
+
+	hideMask: function(noFx){
+		if (noFx) return this.parent();
+		this.element.tween('opacity', 0).get('tween').chain(function(){
+			this.element.setStyle('display', 'none');
+			this.hidden = true;
+			this.fireEvent('hide');
+			this.callChain();
+		}.bind(this));
+	},
+
+	destroy: function(){
+		this.content.destroy();
+		this.parent();
+		this.target.eliminate('spinner');
+	}
+
+});
+
+Spinner.implement(new Chain);
+
+if (window.Request) {
+	Request = Class.refactor(Request, {
+		
+		options: {
+			useSpinner: false,
+			spinnerOptions: {},
+			spinnerTarget: false
+		},
+		
+		initialize: function(options){
+			this._send = this.send;
+			this.send = function(options){
+				if (this.spinner) this.spinner.chain(this._send.bind(this, options)).show();
+				else this._send(options);
+				return this;
+			};
+			this.previous(options);
+			var update = document.id(this.options.spinnerTarget) || document.id(this.options.update);
+			if (this.options.useSpinner && update) {
+				this.spinner = update.get('spinner', this.options.spinnerOptions);
+				['onComplete', 'onException', 'onCancel'].each(function(event){
+					this.addEvent(event, this.spinner.hide.bind(this.spinner));
+				}, this);
+			}
+		},
+		
+		getSpinner: function(){
+			return this.spinner;
+		}
+		
+	});
+}
+
+Element.Properties.spinner = {
+
+	set: function(options){
+		var spinner = this.retrieve('spinner');
+		return this.eliminate('spinner').store('spinner:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('spinner')){
+			if (this.retrieve('spinner')) this.retrieve('spinner').destroy();
+			if (options || !this.retrieve('spinner:options')) this.set('spinner', options);
+			new Spinner(this, this.retrieve('spinner:options'));
+		}
+		return this.retrieve('spinner');
+	}
+
+};
+
+Element.implement({
+
+	spin: function(options){
+		this.get('spinner', options).show();
+		return this;
+	},
+
+	unspin: function(){
+		var opt = Array.link(arguments, {options: Object.type, callback: Function.type});
+		this.get('spinner', opt.options).hide(opt.callback);
+		return this;
+	}
+
+});/*
+---
+
+script: Form.Request.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Request.HTML
+ - /Class.Binds
+ - /Class.Occlude
+ - /Spinner
+ - /String.QueryString
+
+provides: [Form.Request]
+
+...
+*/
+
+if (!window.Form) window.Form = {};
+
+(function(){
+
+	Form.Request = new Class({
+
+		Binds: ['onSubmit', 'onFormValidate'],
+
+		Implements: [Options, Events, Class.Occlude],
+
+		options: {
+			//onFailure: $empty,
+			//onSuccess: #empty, //aliased to onComplete,
+			//onSend: $empty
+			requestOptions: {
+				evalScripts: true,
+				useSpinner: true,
+				emulation: false,
+				link: 'ignore'
+			},
+			extraData: {},
+			resetForm: true
+		},
+
+		property: 'form.request',
+
+		initialize: function(form, update, options) {
+			this.element = document.id(form);
+			if (this.occlude()) return this.occluded;
+			this.update = document.id(update);
+			this.setOptions(options);
+			this.makeRequest();
+			if (this.options.resetForm) {
+				this.request.addEvent('success', function(){
+					$try(function(){ this.element.reset(); }.bind(this));
+					if (window.OverText) OverText.update();
+				}.bind(this));
+			}
+			this.attach();
+		},
+
+		toElement: function() {
+			return this.element;
+		},
+
+		makeRequest: function(){
+			this.request = new Request.HTML($merge({
+					update: this.update,
+					emulation: false,
+					spinnerTarget: this.element,
+					method: this.element.get('method') || 'post'
+			}, this.options.requestOptions)).addEvents({
+				success: function(text, xml){
+					['complete', 'success'].each(function(evt){
+						this.fireEvent(evt, [this.update, text, xml]);
+					}, this);
+				}.bind(this),
+				failure: function(xhr){
+					this.fireEvent('complete').fireEvent('failure', xhr);
+				}.bind(this),
+				exception: function(){
+					this.fireEvent('failure', xhr);
+				}.bind(this)
+			});
+		},
+
+		attach: function(attach){
+			attach = $pick(attach, true);
+			method = attach ? 'addEvent' : 'removeEvent';
+			
+			var fv = this.element.retrieve('validator');
+			if (fv) fv[method]('onFormValidate', this.onFormValidate);
+			if (!fv || !attach) this.element[method]('submit', this.onSubmit);
+		},
+
+		detach: function(){
+			this.attach(false);
+		},
+
+		//public method
+		enable: function(){
+			this.attach();
+		},
+
+		//public method
+		disable: function(){
+			this.detach();
+		},
+
+		onFormValidate: function(valid, form, e) {
+			var fv = this.element.retrieve('validator');
+			if (valid || (fv && !fv.options.stopOnFailure)) {
+				if (e && e.stop) e.stop();
+				this.send();
+			}
+		},
+
+		onSubmit: function(e){
+			if (this.element.retrieve('validator')) {
+				//form validator was created after Form.Request
+				this.detach();
+				return;
+			}
+			e.stop();
+			this.send();
+		},
+
+		send: function(){
+			var str = this.element.toQueryString().trim();
+			var data = $H(this.options.extraData).toQueryString();
+			if (str) str += "&" + data;
+			else str = data;
+			this.fireEvent('send', [this.element, str.parseQueryString()]);
+			this.request.send({data: str, url: this.element.get("action")});
+			return this;
+		}
+
+	});
+
+	Element.Properties.formRequest = {
+
+		set: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			var updater = this.retrieve('form.request');
+			if (update) {
+				if (updater) updater.update = document.id(update);
+				this.store('form.request:update', update);
+			}
+			if (opt.options) {
+				if (updater) updater.setOptions(opt.options);
+				this.store('form.request:options', opt.options);
+			}
+			return this;
+		},
+
+		get: function(){
+			var opt = Array.link(arguments, {options: Object.type, update: Element.type, updateId: String.type});
+			var update = opt.update || opt.updateId;
+			if (opt.options || update || !this.retrieve('form.request')){
+				if (opt.options || !this.retrieve('form.request:options')) this.set('form.request', opt.options);
+				if (update) this.set('form.request', update);
+				this.store('form.request', new Form.Request(this, this.retrieve('form.request:update'), this.retrieve('form.request:options')));
+			}
+			return this.retrieve('form.request');
+		}
+
+	};
+
+	Element.implement({
+
+		formUpdate: function(update, options){
+			this.get('form.request', update, options).send();
+			return this;
+		}
+
+	});
+
+})();/*
+---
+
+script: Fx.Reveal.js
+
+description: Defines Fx.Reveal, a class that shows and hides elements with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Morph
+ - /Element.Shortcuts
+ - /Element.Measure
+
+provides: [Fx.Reveal]
+
+...
+*/
+
+Fx.Reveal = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {/*	  
+		onShow: $empty(thisElement),
+		onHide: $empty(thisElement),
+		onComplete: $empty(thisElement),
+		heightOverride: null,
+		widthOverride: null, */
+		link: 'cancel',
+		styles: ['padding', 'border', 'margin'],
+		transitionOpacity: !Browser.Engine.trident4,
+		mode: 'vertical',
+		display: 'block',
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
+	},
+
+	dissolve: function(){
+		try {
+			if (!this.hiding && !this.showing){
+				if (this.element.getStyle('display') != 'none'){
+					this.hiding = true;
+					this.showing = false;
+					this.hidden = true;
+					this.cssText = this.element.style.cssText;
+					var startStyles = this.element.getComputedSize({
+						styles: this.options.styles,
+						mode: this.options.mode
+					});
+					this.element.setStyle('display', this.options.display);
+					if (this.options.transitionOpacity) startStyles.opacity = 1;
+					var zero = {};
+					$each(startStyles, function(style, name){
+						zero[name] = [style, 0];
+					}, this);
+					this.element.setStyle('overflow', 'hidden');
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					this.$chain.unshift(function(){
+						if (this.hidden){
+							this.hiding = false;
+							$each(startStyles, function(style, name){
+								startStyles[name] = style;
+							}, this);
+							this.element.style.cssText = this.cssText;
+							this.element.setStyle('display', 'none');
+							if (hideThese) hideThese.setStyle('visibility', 'visible');
+						}
+						this.fireEvent('hide', this.element);
+						this.callChain();
+					}.bind(this));
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					this.start(zero);
+				} else {
+					this.callChain.delay(10, this);
+					this.fireEvent('complete', this.element);
+					this.fireEvent('hide', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.dissolve.bind(this));
+			} else if (this.options.link == 'cancel' && !this.hiding){
+				this.cancel();
+				this.dissolve();
+			}
+		} catch(e){
+			this.hiding = false;
+			this.element.setStyle('display', 'none');
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('hide', this.element);
+		}
+		return this;
+	},
+
+	reveal: function(){
+		try {
+			if (!this.showing && !this.hiding){
+				if (this.element.getStyle('display') == 'none' ||
+					 this.element.getStyle('visiblity') == 'hidden' ||
+					 this.element.getStyle('opacity') == 0){
+					this.showing = true;
+					this.hiding = this.hidden =  false;
+					var startStyles;
+					this.cssText = this.element.style.cssText;
+					//toggle display, but hide it
+					this.element.measure(function(){
+						//create the styles for the opened/visible state
+						startStyles = this.element.getComputedSize({
+							styles: this.options.styles,
+							mode: this.options.mode
+						});
+					}.bind(this));
+					$each(startStyles, function(style, name){
+						startStyles[name] = style;
+					});
+					//if we're overridding height/width
+					if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
+					if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
+					if (this.options.transitionOpacity) {
+						this.element.setStyle('opacity', 0);
+						startStyles.opacity = 1;
+					}
+					//create the zero state for the beginning of the transition
+					var zero = {
+						height: 0,
+						display: this.options.display
+					};
+					$each(startStyles, function(style, name){ zero[name] = 0; });
+					//set to zero
+					this.element.setStyles($merge(zero, {overflow: 'hidden'}));
+					//hide inputs
+					var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
+					if (hideThese) hideThese.setStyle('visibility', 'hidden');
+					//start the effect
+					this.start(startStyles);
+					this.$chain.unshift(function(){
+						this.element.style.cssText = this.cssText;
+						this.element.setStyle('display', this.options.display);
+						if (!this.hidden) this.showing = false;
+						if (hideThese) hideThese.setStyle('visibility', 'visible');
+						this.callChain();
+						this.fireEvent('show', this.element);
+					}.bind(this));
+				} else {
+					this.callChain();
+					this.fireEvent('complete', this.element);
+					this.fireEvent('show', this.element);
+				}
+			} else if (this.options.link == 'chain'){
+				this.chain(this.reveal.bind(this));
+			} else if (this.options.link == 'cancel' && !this.showing){
+				this.cancel();
+				this.reveal();
+			}
+		} catch(e){
+			this.element.setStyles({
+				display: this.options.display,
+				visiblity: 'visible',
+				opacity: 1
+			});
+			this.showing = false;
+			this.callChain.delay(10, this);
+			this.fireEvent('complete', this.element);
+			this.fireEvent('show', this.element);
+		}
+		return this;
+	},
+
+	toggle: function(){
+		if (this.element.getStyle('display') == 'none' ||
+			 this.element.getStyle('visiblity') == 'hidden' ||
+			 this.element.getStyle('opacity') == 0){
+			this.reveal();
+		} else {
+			this.dissolve();
+		}
+		return this;
+	},
+
+	cancel: function(){
+		this.parent.apply(this, arguments);
+		this.element.style.cssText = this.cssText;
+		this.hidding = false;
+		this.showing = false;
+	}
+
+});
+
+Element.Properties.reveal = {
+
+	set: function(options){
+		var reveal = this.retrieve('reveal');
+		if (reveal) reveal.cancel();
+		return this.eliminate('reveal').store('reveal:options', options);
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('reveal')){
+			if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
+			this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
+		}
+		return this.retrieve('reveal');
+	}
+
+};
+
+Element.Properties.dissolve = Element.Properties.reveal;
+
+Element.implement({
+
+	reveal: function(options){
+		this.get('reveal', options).reveal();
+		return this;
+	},
+
+	dissolve: function(options){
+		this.get('reveal', options).dissolve();
+		return this;
+	},
+
+	nix: function(){
+		var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
+		this.get('reveal', params.options).dissolve().chain(function(){
+			this[params.destroy ? 'destroy' : 'dispose']();
+		}.bind(this));
+		return this;
+	},
+
+	wink: function(){
+		var params = Array.link(arguments, {duration: Number.type, options: Object.type});
+		var reveal = this.get('reveal', params.options);
+		reveal.reveal().chain(function(){
+			(function(){
+				reveal.dissolve();
+			}).delay(params.duration || 2000);
+		});
+	}
+
+
+});/*
+---
+
+script: Form.Request.Append.js
+
+description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Request
+ - /Fx.Reveal
+ - /Elements.from
+
+provides: [Form.Request.Append]
+
+...
+*/
+
+Form.Request.Append = new Class({
+
+	Extends: Form.Request,
+
+	options: {
+		//onBeforeEffect: $empty,
+		useReveal: true,
+		revealOptions: {},
+		inject: 'bottom'
+	},
+
+	makeRequest: function(){
+		this.request = new Request.HTML($merge({
+				url: this.element.get('action'),
+				method: this.element.get('method') || 'post',
+				spinnerTarget: this.element
+			}, this.options.requestOptions, {
+				evalScripts: false
+			})
+		).addEvents({
+			success: function(tree, elements, html, javascript){
+				var container;
+				var kids = Elements.from(html);
+				if (kids.length == 1) {
+					container = kids[0];
+				} else {
+					 container = new Element('div', {
+						styles: {
+							display: 'none'
+						}
+					}).adopt(kids);
+				}
+				container.inject(this.update, this.options.inject);
+				if (this.options.requestOptions.evalScripts) $exec(javascript);
+				this.fireEvent('beforeEffect', container);
+				var finish = function(){
+					this.fireEvent('success', [container, this.update, tree, elements, html, javascript]);
+				}.bind(this);
+				if (this.options.useReveal) {
+					container.get('reveal', this.options.revealOptions).chain(finish);
+					container.reveal();
+				} else {
+					finish();
+				}
+			}.bind(this),
+			failure: function(xhr){
+				this.fireEvent('failure', xhr);
+			}.bind(this)
+		});
+	}
+
+});/*
+---
+
+script: Form.Validator.English.js
+
+description: Form Validator messages for English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.English]
+
+...
+*/
+
+MooTools.lang.set('en-US', 'Form.Validator', {
+
+	required:'This field is required.',
+	minLength:'Please enter at least {minLength} characters (you entered {length} characters).',
+	maxLength:'Please enter no more than {maxLength} characters (you entered {length} characters).',
+	integer:'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.',
+	numeric:'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").',
+	digits:'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).',
+	alpha:'Please use letters only (a-z) with in this field. No spaces or other characters are allowed.',
+	alphanum:'Please use only letters (a-z) or numbers (0-9) only in this field. No spaces or other characters are allowed.',
+	dateSuchAs:'Please enter a valid date such as {date}',
+	dateInFormatMDY:'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")',
+	email:'Please enter a valid email address. For example "fred at domain.com".',
+	url:'Please enter a valid URL such as http://www.google.com.',
+	currencyDollar:'Please enter a valid $ amount. For example $100.00 .',
+	oneRequired:'Please enter something for at least one of these inputs.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Warning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'There can be no spaces in this input.',
+	reqChkByNode: 'No items are selected.',
+	requiredChk: 'This field is required.',
+	reqChkByName: 'Please select a {label}.',
+	match: 'This field needs to match the {matchName} field',
+	startDate: 'the start date',
+	endDate: 'the end date',
+	currendDate: 'the current date',
+	afterDate: 'The date should be the same or after {label}.',
+	beforeDate: 'The date should be the same or before {label}.',
+	startMonth: 'Please select a start month',
+	sameMonth: 'These two dates must be in the same month - you must change one or the other.',
+	creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.'
+
+});
+/*
+---
+
+script: Form.Validator.js
+
+description: A css-class based form validation system.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Selectors
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/JSON
+ - /Lang
+ - /Class.Binds
+ - /Date Element.Forms
+ - /Form.Validator.English
+ - /Element.Shortcuts
+
+provides: [Form.Validator, InputValidator, FormValidator.BaseValidators]
+
+...
+*/
+if (!window.Form) window.Form = {};
+
+var InputValidator = new Class({
+
+	Implements: [Options],
+
+	options: {
+		errorMsg: 'Validation failed.',
+		test: function(field){return true;}
+	},
+
+	initialize: function(className, options){
+		this.setOptions(options);
+		this.className = className;
+	},
+
+	test: function(field, props){
+		if (document.id(field)) return this.options.test(document.id(field), props||this.getProps(field));
+		else return false;
+	},
+
+	getError: function(field, props){
+		var err = this.options.errorMsg;
+		if ($type(err) == 'function') err = err(document.id(field), props||this.getProps(field));
+		return err;
+	},
+
+	getProps: function(field){
+		if (!document.id(field)) return {};
+		return field.get('validatorProps');
+	}
+
+});
+
+Element.Properties.validatorProps = {
+
+	set: function(props){
+		return this.eliminate('validatorProps').store('validatorProps', props);
+	},
+
+	get: function(props){
+		if (props) this.set(props);
+		if (this.retrieve('validatorProps')) return this.retrieve('validatorProps');
+		if (this.getProperty('validatorProps')){
+			try {
+				this.store('validatorProps', JSON.decode(this.getProperty('validatorProps')));
+			}catch(e){
+				return {};
+			}
+		} else {
+			var vals = this.get('class').split(' ').filter(function(cls){
+				return cls.test(':');
+			});
+			if (!vals.length){
+				this.store('validatorProps', {});
+			} else {
+				props = {};
+				vals.each(function(cls){
+					var split = cls.split(':');
+					if (split[1]) {
+						try {
+							props[split[0]] = JSON.decode(split[1]);
+						} catch(e) {}
+					}
+				});
+				this.store('validatorProps', props);
+			}
+		}
+		return this.retrieve('validatorProps');
+	}
+
+};
+
+Form.Validator = new Class({
+
+	Implements:[Options, Events],
+
+	Binds: ['onSubmit'],
+
+	options: {/*
+		onFormValidate: $empty(isValid, form, event),
+		onElementValidate: $empty(isValid, field, className, warn),
+		onElementPass: $empty(field),
+		onElementFail: $empty(field, validatorsFailed) */
+		fieldSelectors: 'input, select, textarea',
+		ignoreHidden: true,
+		ignoreDisabled: true,
+		useTitles: false,
+		evaluateOnSubmit: true,
+		evaluateFieldsOnBlur: true,
+		evaluateFieldsOnChange: true,
+		serial: true,
+		stopOnFailure: true,
+		warningPrefix: function(){
+			return Form.Validator.getMsg('warningPrefix') || 'Warning: ';
+		},
+		errorPrefix: function(){
+			return Form.Validator.getMsg('errorPrefix') || 'Error: ';
+		}
+	},
+
+	initialize: function(form, options){
+		this.setOptions(options);
+		this.element = document.id(form);
+		this.element.store('validator', this);
+		this.warningPrefix = $lambda(this.options.warningPrefix)();
+		this.errorPrefix = $lambda(this.options.errorPrefix)();
+		if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this.onSubmit);
+		if (this.options.evaluateFieldsOnBlur || this.options.evaluateFieldsOnChange) this.watchFields(this.getFields());
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	getFields: function(){
+		return (this.fields = this.element.getElements(this.options.fieldSelectors));
+	},
+
+	watchFields: function(fields){
+		fields.each(function(el){
+			if (this.options.evaluateFieldsOnBlur)
+				el.addEvent('blur', this.validationMonitor.pass([el, false], this));
+			if (this.options.evaluateFieldsOnChange)
+				el.addEvent('change', this.validationMonitor.pass([el, true], this));
+		}, this);
+	},
+
+	validationMonitor: function(){
+		$clear(this.timer);
+		this.timer = this.validateField.delay(50, this, arguments);
+	},
+
+	onSubmit: function(event){
+		if (!this.validate(event) && event) event.preventDefault();
+		else this.reset();
+	},
+
+	reset: function(){
+		this.getFields().each(this.resetField, this);
+		return this;
+	},
+
+	validate: function(event){
+		var result = this.getFields().map(function(field){
+			return this.validateField(field, true);
+		}, this).every(function(v){ return v;});
+		this.fireEvent('formValidate', [result, this.element, event]);
+		if (this.options.stopOnFailure && !result && event) event.preventDefault();
+		return result;
+	},
+
+	validateField: function(field, force){
+		if (this.paused) return true;
+		field = document.id(field);
+		var passed = !field.hasClass('validation-failed');
+		var failed, warned;
+		if (this.options.serial && !force){
+			failed = this.element.getElement('.validation-failed');
+			warned = this.element.getElement('.warning');
+		}
+		if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){
+			var validators = field.className.split(' ').some(function(cn){
+				return this.getValidator(cn);
+			}, this);
+			var validatorsFailed = [];
+			field.className.split(' ').each(function(className){
+				if (className && !this.test(className, field)) validatorsFailed.include(className);
+			}, this);
+			passed = validatorsFailed.length === 0;
+			if (validators && !field.hasClass('warnOnly')){
+				if (passed){
+					field.addClass('validation-passed').removeClass('validation-failed');
+					this.fireEvent('elementPass', field);
+				} else {
+					field.addClass('validation-failed').removeClass('validation-passed');
+					this.fireEvent('elementFail', [field, validatorsFailed]);
+				}
+			}
+			if (!warned){
+				var warnings = field.className.split(' ').some(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.getValidator(cn.replace(/^warn-/,''));
+					else return null;
+				}, this);
+				field.removeClass('warning');
+				var warnResult = field.className.split(' ').map(function(cn){
+					if (cn.test('^warn-') || field.hasClass('warnOnly'))
+						return this.test(cn.replace(/^warn-/,''), field, true);
+					else return null;
+				}, this);
+			}
+		}
+		return passed;
+	},
+
+	test: function(className, field, warn){
+		field = document.id(field);
+		if((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true;
+		var validator = this.getValidator(className);
+		if (field.hasClass('ignoreValidation')) return true;
+		warn = $pick(warn, false);
+		if (field.hasClass('warnOnly')) warn = true;
+		var isValid = validator ? validator.test(field) : true;
+		if (validator && field.isVisible()) this.fireEvent('elementValidate', [isValid, field, className, warn]);
+		if (warn) return true;
+		return isValid;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (field){
+			field.className.split(' ').each(function(className){
+				if (className.test('^warn-')) className = className.replace(/^warn-/, '');
+				field.removeClass('validation-failed');
+				field.removeClass('warning');
+				field.removeClass('validation-passed');
+			}, this);
+		}
+		return this;
+	},
+
+	stop: function(){
+		this.paused = true;
+		return this;
+	},
+
+	start: function(){
+		this.paused = false;
+		return this;
+	},
+
+	ignoreField: function(field, warn){
+		field = document.id(field);
+		if (field){
+			this.enforceField(field);
+			if (warn) field.addClass('warnOnly');
+			else field.addClass('ignoreValidation');
+		}
+		return this;
+	},
+
+	enforceField: function(field){
+		field = document.id(field);
+		if (field) field.removeClass('warnOnly').removeClass('ignoreValidation');
+		return this;
+	}
+
+});
+
+Form.Validator.getMsg = function(key){
+	return MooTools.lang.get('Form.Validator', key);
+};
+
+Form.Validator.adders = {
+
+	validators:{},
+
+	add : function(className, options){
+		this.validators[className] = new InputValidator(className, options);
+		//if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace)
+		//extend these validators into it
+		//this allows validators to be global and/or per instance
+		if (!this.initialize){
+			this.implement({
+				validators: this.validators
+			});
+		}
+	},
+
+	addAllThese : function(validators){
+		$A(validators).each(function(validator){
+			this.add(validator[0], validator[1]);
+		}, this);
+	},
+
+	getValidator: function(className){
+		return this.validators[className.split(':')[0]];
+	}
+
+};
+
+$extend(Form.Validator, Form.Validator.adders);
+
+Form.Validator.implement(Form.Validator.adders);
+
+Form.Validator.add('IsEmpty', {
+
+	errorMsg: false,
+	test: function(element){
+		if (element.type == 'select-one' || element.type == 'select')
+			return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != '');
+		else
+			return ((element.get('value') == null) || (element.get('value').length == 0));
+	}
+
+});
+
+Form.Validator.addAllThese([
+
+	['required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element){
+			return !Form.Validator.getValidator('IsEmpty').test(element);
+		}
+	}],
+
+	['minLength', {
+		errorMsg: function(element, props){
+			if ($type(props.minLength))
+				return Form.Validator.getMsg('minLength').substitute({minLength:props.minLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			if ($type(props.minLength)) return (element.get('value').length >= $pick(props.minLength, 0));
+			else return true;
+		}
+	}],
+
+	['maxLength', {
+		errorMsg: function(element, props){
+			//props is {maxLength:10}
+			if ($type(props.maxLength))
+				return Form.Validator.getMsg('maxLength').substitute({maxLength:props.maxLength,length:element.get('value').length });
+			else return '';
+		},
+		test: function(element, props){
+			//if the value is <= than the maxLength value, element passes test
+			return (element.get('value').length <= $pick(props.maxLength, 10000));
+		}
+	}],
+
+	['validate-integer', {
+		errorMsg: Form.Validator.getMsg.pass('integer'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-numeric', {
+		errorMsg: Form.Validator.getMsg.pass('numeric'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||
+				(/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-digits', {
+		errorMsg: Form.Validator.getMsg.pass('digits'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value')));
+		}
+	}],
+
+	['validate-alpha', {
+		errorMsg: Form.Validator.getMsg.pass('alpha'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^[a-zA-Z]+$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-alphanum', {
+		errorMsg: Form.Validator.getMsg.pass('alphanum'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value'));
+		}
+	}],
+
+	['validate-date', {
+		errorMsg: function(element, props){
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)});
+			} else {
+				return Form.Validator.getMsg('dateInFormatMDY');
+			}
+		},
+		test: function(element, props){
+			if (Form.Validator.getValidator('IsEmpty').test(element)) return true;
+			var d;
+			if (Date.parse){
+				var format = props.dateFormat || '%x';
+				d = Date.parse(element.get('value'));
+				var formatted = d.format(format);
+				if (formatted != 'invalid date') element.set('value', formatted);
+				return !isNaN(d);
+			} else {
+				var regex = /^(\d{2})\/(\d{2})\/(\d{4})$/;
+				if (!regex.test(element.get('value'))) return false;
+				d = new Date(element.get('value').replace(regex, '$1/$2/$3'));
+				return (parseInt(RegExp.$1, 10) == (1 + d.getMonth())) &&
+					(parseInt(RegExp.$2, 10) == d.getDate()) &&
+					(parseInt(RegExp.$3, 10) == d.getFullYear());
+			}
+		}
+	}],
+
+	['validate-email', {
+		errorMsg: Form.Validator.getMsg.pass('email'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-url', {
+		errorMsg: Form.Validator.getMsg.pass('url'),
+		test: function(element){
+			return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value'));
+		}
+	}],
+
+	['validate-currency-dollar', {
+		errorMsg: Form.Validator.getMsg.pass('currencyDollar'),
+		test: function(element){
+			// [$]1[##][,###]+[.##]
+			// [$]1###+[.##]
+			// [$]0.##
+			// [$].##
+			return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+		}
+	}],
+
+	['validate-one-required', {
+		errorMsg: Form.Validator.getMsg.pass('oneRequired'),
+		test: function(element, props){
+			var p = document.id(props['validate-one-required']) || element.getParent();
+			return p.getElements('input').some(function(el){
+				if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked');
+				return el.get('value');
+			});
+		}
+	}]
+
+]);
+
+Element.Properties.validator = {
+
+	set: function(options){
+		var validator = this.retrieve('validator');
+		if (validator) validator.setOptions(options);
+		return this.store('validator:options');
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('validator')){
+			if (options || !this.retrieve('validator:options')) this.set('validator', options);
+			this.store('validator', new Form.Validator(this, this.retrieve('validator:options')));
+		}
+		return this.retrieve('validator');
+	}
+
+};
+
+Element.implement({
+
+	validate: function(options){
+		this.set('validator', options);
+		return this.get('validator', options).validate();
+	}
+
+});
+//legacy
+var FormValidator = Form.Validator;/*
+---
+
+script: Form.Validator.Inline.js
+
+description: Extends Form.Validator to add inline messages.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Validator
+
+provides: [Form.Validator.Inline]
+
+...
+*/
+
+Form.Validator.Inline = new Class({
+
+	Extends: Form.Validator,
+
+	options: {
+		scrollToErrorsOnSubmit: true,
+		scrollFxOptions: {
+			transition: 'quad:out',
+			offset: {
+				y: -20
+			}
+		}
+	},
+
+	initialize: function(form, options){
+		this.parent(form, options);
+		this.addEvent('onElementValidate', function(isValid, field, className, warn){
+			var validator = this.getValidator(className);
+			if (!isValid && validator.getError(field)){
+				if (warn) field.addClass('warning');
+				var advice = this.makeAdvice(className, field, validator.getError(field), warn);
+				this.insertAdvice(advice, field);
+				this.showAdvice(className, field);
+			} else {
+				this.hideAdvice(className, field);
+			}
+		});
+	},
+
+	makeAdvice: function(className, field, error, warn){
+		var errorMsg = (warn)?this.warningPrefix:this.errorPrefix;
+			errorMsg += (this.options.useTitles) ? field.title || error:error;
+		var cssClass = (warn) ? 'warning-advice' : 'validation-advice';
+		var advice = this.getAdvice(className, field);
+		if(advice) {
+			advice = advice.set('html', errorMsg);
+		} else {
+			advice = new Element('div', {
+				html: errorMsg,
+				styles: { display: 'none' },
+				id: 'advice-' + className + '-' + this.getFieldId(field)
+			}).addClass(cssClass);
+		}
+		field.store('advice-' + className, advice);
+		return advice;
+	},
+
+	getFieldId : function(field){
+		return field.id ? field.id : field.id = 'input_' + field.name;
+	},
+
+	showAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && !field.retrieve(this.getPropName(className))
+				&& (advice.getStyle('display') == 'none'
+				|| advice.getStyle('visiblity') == 'hidden'
+				|| advice.getStyle('opacity') == 0)){
+			field.store(this.getPropName(className), true);
+			if (advice.reveal) advice.reveal();
+			else advice.setStyle('display', 'block');
+		}
+	},
+
+	hideAdvice: function(className, field){
+		var advice = this.getAdvice(className, field);
+		if (advice && field.retrieve(this.getPropName(className))){
+			field.store(this.getPropName(className), false);
+			//if Fx.Reveal.js is present, transition the advice out
+			if (advice.dissolve) advice.dissolve();
+			else advice.setStyle('display', 'none');
+		}
+	},
+
+	getPropName: function(className){
+		return 'advice' + className;
+	},
+
+	resetField: function(field){
+		field = document.id(field);
+		if (!field) return this;
+		this.parent(field);
+		field.className.split(' ').each(function(className){
+			this.hideAdvice(className, field);
+		}, this);
+		return this;
+	},
+
+	getAllAdviceMessages: function(field, force){
+		var advice = [];
+		if (field.hasClass('ignoreValidation') && !force) return advice;
+		var validators = field.className.split(' ').some(function(cn){
+			var warner = cn.test('^warn-') || field.hasClass('warnOnly');
+			if (warner) cn = cn.replace(/^warn-/, '');
+			var validator = this.getValidator(cn);
+			if (!validator) return;
+			advice.push({
+				message: validator.getError(field),
+				warnOnly: warner,
+				passed: validator.test(),
+				validator: validator
+			});
+		}, this);
+		return advice;
+	},
+
+	getAdvice: function(className, field){
+		return field.retrieve('advice-' + className);
+	},
+
+	insertAdvice: function(advice, field){
+		//Check for error position prop
+		var props = field.get('validatorProps');
+		//Build advice
+		if (!props.msgPos || !document.id(props.msgPos)){
+			if(field.type.toLowerCase() == 'radio') field.getParent().adopt(advice);
+			else advice.inject(document.id(field), 'after');
+		} else {
+			document.id(props.msgPos).grab(advice);
+		}
+	},
+
+	validateField: function(field, force){
+		var result = this.parent(field, force);
+		if (this.options.scrollToErrorsOnSubmit && !result){
+			var failed = document.id(this).getElement('.validation-failed');
+			var par = document.id(this).getParent();
+			while (par != document.body && par.getScrollSize().y == par.getSize().y){
+				par = par.getParent();
+			}
+			var fx = par.retrieve('fvScroller');
+			if (!fx && window.Fx && Fx.Scroll){
+				fx = new Fx.Scroll(par, this.options.scrollFxOptions);
+				par.store('fvScroller', fx);
+			}
+			if (failed){
+				if (fx) fx.toElement(failed);
+				else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20);
+			}
+		}
+		return result;
+	}
+
+});
+/*
+---
+
+script: Form.Validator.Extras.js
+
+description: Additional validators for the Form.Validator class.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Form.Validator
+
+provides: [Form.Validator.Extras]
+
+...
+*/
+Form.Validator.addAllThese([
+
+	['validate-enforce-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-ignore-oncheck', {
+		test: function(element, props){
+			if (element.checked){
+				var fv = element.getParent('form').retrieve('validator');
+				if (!fv) return true;
+				(props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-nospace', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('noSpace');
+		},
+		test: function(element, props){
+			return !element.get('value').test(/\s/);
+		}
+	}],
+
+	['validate-toggle-oncheck', {
+		test: function(element, props){
+			var fv = element.getParent('form').retrieve('validator');
+			if (!fv) return true;
+			var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea');
+			if (!element.checked){
+				eleArr.each(function(item){
+					fv.ignoreField(item);
+					fv.resetField(item);
+				});
+			} else {
+				eleArr.each(function(item){
+					fv.enforceField(item);
+				});
+			}
+			return true;
+		}
+	}],
+
+	['validate-reqchk-bynode', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('reqChkByNode');
+		},
+		test: function(element, props){
+			return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){
+				return item.checked;
+			});
+		}
+	}],
+
+	['validate-required-check', {
+		errorMsg: function(element, props){
+			return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk');
+		},
+		test: function(element, props){
+			return !!element.checked;
+		}
+	}],
+
+	['validate-reqchk-byname', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')});
+		},
+		test: function(element, props){
+			var grpName = props.groupName || element.get('name');
+			var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){
+				return item.checked;
+			});
+			var fv = element.getParent('form').retrieve('validator');
+			if (oneCheckedItem && fv) fv.resetField(element);
+			return oneCheckedItem;
+		}
+	}],
+
+	['validate-match', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')});
+		},
+		test: function(element, props){
+			var eleVal = element.get('value');
+			var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value');
+			return eleVal && matchVal ? eleVal == matchVal : true;
+		}
+	}],
+
+	['validate-after-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('afterDate').substitute({
+				label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date();
+			var end = Date.parse(element.get('value'));
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-before-date', {
+		errorMsg: function(element, props){
+			return Form.Validator.getMsg('beforeDate').substitute({
+				label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate'))
+			});
+		},
+		test: function(element, props){
+			var start = Date.parse(element.get('value'));
+			var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date();
+			return end && start ? end >= start : true;
+		}
+	}],
+
+	['validate-custom-required', {
+		errorMsg: function(){
+			return Form.Validator.getMsg('required');
+		},
+		test: function(element, props){
+			return element.get('value') != props.emptyValue;
+		}
+	}],
+
+	['validate-same-month', {
+		errorMsg: function(element, props){
+			var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value');
+			var eleVal = element.get('value');
+			if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth');
+		},
+		test: function(element, props){
+			var d1 = Date.parse(element.get('value'));
+			var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'));
+			return d1 && d2 ? d1.format('%B') == d2.format('%B') : true;
+		}
+	}],
+
+
+	['validate-cc-num', {
+		errorMsg: function(element){
+			var ccNum = element.get('value').replace(/[^0-9]/g, '');
+			return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length});
+		},
+		test: function(element){
+			// required is a different test
+			if (Form.Validator.getValidator('IsEmpty').test(element)) { return true; }
+
+			// Clean number value
+			var ccNum = element.get('value');
+			ccNum = ccNum.replace(/[^0-9]/g, '');
+
+			var valid_type = false;
+
+			if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa';
+			else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card';
+			else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express';
+			else if (ccNum.test(/^6011[0-9]{12}$/)) valid_type = 'Discover';
+
+			if (valid_type) {
+				var sum = 0;
+				var cur = 0;
+
+				for(var i=ccNum.length-1; i>=0; --i) {
+					cur = ccNum.charAt(i).toInt();
+					if (cur == 0) { continue; }
+
+					if ((ccNum.length-i) % 2 == 0) { cur += cur; }
+					if (cur > 9) { cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); }
+
+					sum += cur;
+				}
+				if ((sum % 10) == 0) { return true; }
+			}
+
+			var chunks = '';
+			while (ccNum != '') {
+				chunks += ' ' + ccNum.substr(0,4);
+				ccNum = ccNum.substr(4);
+			}
+
+			element.getParent('form').retrieve('validator').ignoreField(element);
+			element.set('value', chunks.clean());
+			element.getParent('form').retrieve('validator').enforceField(element);
+			return false;
+		}
+	}]
+
+
+]);/*
+---
+
+script: OverText.js
+
+description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - /Class.Binds
+ - /Class.Occlude
+ - /Element.Position
+ - /Element.Shortcuts
+
+provides: [OverText]
+
+...
+*/
+
+var OverText = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	Binds: ['reposition', 'assert', 'focus', 'hide'],
+
+	options: {/*
+		textOverride: null,
+		onFocus: $empty()
+		onTextHide: $empty(textEl, inputEl),
+		onTextShow: $empty(textEl, inputEl), */
+		element: 'label',
+		positionOptions: {
+			position: 'upperLeft',
+			edge: 'upperLeft',
+			offset: {
+				x: 4,
+				y: 2
+			}
+		},
+		poll: false,
+		pollInterval: 250,
+		wrap: false
+	},
+
+	property: 'OverText',
+
+	initialize: function(element, options){
+		this.element = document.id(element);
+		if (this.occlude()) return this.occluded;
+		this.setOptions(options);
+		this.attach(this.element);
+		OverText.instances.push(this);
+		if (this.options.poll) this.poll();
+		return this;
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	attach: function(){
+		var val = this.options.textOverride || this.element.get('alt') || this.element.get('title');
+		if (!val) return;
+		this.text = new Element(this.options.element, {
+			'class': 'overTxtLabel',
+			styles: {
+				lineHeight: 'normal',
+				position: 'absolute',
+				cursor: 'text'
+			},
+			html: val,
+			events: {
+				click: this.hide.pass(this.options.element == 'label', this)
+			}
+		}).inject(this.element, 'after');
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+
+		if (this.options.wrap) {
+			this.textHolder = new Element('div', {
+				styles: {
+					lineHeight: 'normal',
+					position: 'relative'
+				},
+				'class':'overTxtWrapper'
+			}).adopt(this.text).inject(this.element, 'before');
+		}
+
+		this.element.addEvents({
+			focus: this.focus,
+			blur: this.assert,
+			change: this.assert
+		}).store('OverTextDiv', this.text);
+		window.addEvent('resize', this.reposition.bind(this));
+		this.assert(true);
+		this.reposition();
+	},
+
+	wrap: function(){
+		if (this.options.element == 'label') {
+			if (!this.element.get('id')) this.element.set('id', 'input_' + new Date().getTime());
+			this.text.set('for', this.element.get('id'));
+		}
+	},
+
+	startPolling: function(){
+		this.pollingPaused = false;
+		return this.poll();
+	},
+
+	poll: function(stop){
+		//start immediately
+		//pause on focus
+		//resumeon blur
+		if (this.poller && !stop) return this;
+		var test = function(){
+			if (!this.pollingPaused) this.assert(true);
+		}.bind(this);
+		if (stop) $clear(this.poller);
+		else this.poller = test.periodical(this.options.pollInterval, this);
+		return this;
+	},
+
+	stopPolling: function(){
+		this.pollingPaused = true;
+		return this.poll(true);
+	},
+
+	focus: function(){
+		if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return;
+		this.hide();
+	},
+
+	hide: function(suppressFocus, force){
+		if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){
+			this.text.hide();
+			this.fireEvent('textHide', [this.text, this.element]);
+			this.pollingPaused = true;
+			if (!suppressFocus){
+				try {
+					this.element.fireEvent('focus');
+					this.element.focus();
+				} catch(e){} //IE barfs if you call focus on hidden elements
+			}
+		}
+		return this;
+	},
+
+	show: function(){
+		if (this.text && !this.text.isDisplayed()){
+			this.text.show();
+			this.reposition();
+			this.fireEvent('textShow', [this.text, this.element]);
+			this.pollingPaused = false;
+		}
+		return this;
+	},
+
+	assert: function(suppressFocus){
+		this[this.test() ? 'show' : 'hide'](suppressFocus);
+	},
+
+	test: function(){
+		var v = this.element.get('value');
+		return !v;
+	},
+
+	reposition: function(){
+		this.assert(true);
+		if (!this.element.isVisible()) return this.stopPolling().hide();
+		if (this.text && this.test()) this.text.position($merge(this.options.positionOptions, {relativeTo: this.element}));
+		return this;
+	}
+
+});
+
+OverText.instances = [];
+
+$extend(OverText, {
+
+	each: function(fn) {
+		return OverText.instances.map(function(ot, i){
+			if (ot.element && ot.text) return fn.apply(OverText, [ot, i]);
+			return null; //the input or the text was destroyed
+		});
+	},
+	
+	update: function(){
+
+		return OverText.each(function(ot){
+			return ot.reposition();
+		});
+
+	},
+
+	hideAll: function(){
+
+		return OverText.each(function(ot){
+			return ot.hide(true, true);
+		});
+
+	},
+
+	showAll: function(){
+		return OverText.each(function(ot) {
+			return ot.show();
+		});
+	}
+
+});
+
+if (window.Fx && Fx.Reveal) {
+	Fx.Reveal.implement({
+		hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel' : false
+	});
+}/*
+---
+
+script: Fx.Elements.js
+
+description: Effect to change any number of CSS properties of any number of Elements.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx.CSS
+ - /MooTools.More
+
+provides: [Fx.Elements]
+
+...
+*/
+
+Fx.Elements = new Class({
+
+	Extends: Fx.CSS,
+
+	initialize: function(elements, options){
+		this.elements = this.subject = $$(elements);
+		this.parent(options);
+	},
+
+	compute: function(from, to, delta){
+		var now = {};
+		for (var i in from){
+			var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
+			for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
+		}
+		return now;
+	},
+
+	set: function(now){
+		for (var i in now){
+			var iNow = now[i];
+			for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
+		}
+		return this;
+	},
+
+	start: function(obj){
+		if (!this.check(obj)) return this;
+		var from = {}, to = {};
+		for (var i in obj){
+			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
+			for (var p in iProps){
+				var parsed = this.prepare(this.elements[i], p, iProps[p]);
+				iFrom[p] = parsed.from;
+				iTo[p] = parsed.to;
+			}
+		}
+		return this.parent(from, to);
+	}
+
+});/*
+---
+
+script: Fx.Accordion.js
+
+description: An Fx.Elements extension which allows you to easily create accordion type controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Event
+ - /Fx.Elements
+
+provides: [Fx.Accordion]
+
+...
+*/
+
+Fx.Accordion = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {/*
+		onActive: $empty(toggler, section),
+		onBackground: $empty(toggler, section),
+		fixedHeight: false,
+		fixedWidth: false,
+		*/
+		display: 0,
+		show: false,
+		height: true,
+		width: false,
+		opacity: true,
+		alwaysHide: false,
+		trigger: 'click',
+		initialDisplayFx: true,
+		returnHeightToAuto: true
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {
+			'container': Element.type, //deprecated
+			'options': Object.type,
+			'togglers': $defined,
+			'elements': $defined
+		});
+		this.parent(params.elements, params.options);
+		this.togglers = $$(params.togglers);
+		this.previous = -1;
+		this.internalChain = new Chain();
+		if (this.options.alwaysHide) this.options.wait = true;
+		if ($chk(this.options.show)){
+			this.options.display = false;
+			this.previous = this.options.show;
+		}
+		if (this.options.start){
+			this.options.display = false;
+			this.options.show = false;
+		}
+		this.effects = {};
+		if (this.options.opacity) this.effects.opacity = 'fullOpacity';
+		if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
+		if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
+		for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
+		this.elements.each(function(el, i){
+			if (this.options.show === i){
+				this.fireEvent('active', [this.togglers[i], el]);
+			} else {
+				for (var fx in this.effects) el.setStyle(fx, 0);
+			}
+		}, this);
+		if ($chk(this.options.display) || this.options.initialDisplayFx === false) this.display(this.options.display, this.options.initialDisplayFx);
+		if (this.options.fixedHeight !== false) this.options.returnHeightToAuto = false;
+		this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain));
+	},
+
+	addSection: function(toggler, element){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		this.togglers.include(toggler);
+		this.elements.include(element);
+		var idx = this.togglers.indexOf(toggler);
+		var displayer = this.display.bind(this, idx);
+		toggler.store('accordion:display', displayer);
+		toggler.addEvent(this.options.trigger, displayer);
+		if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
+		if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
+		element.fullOpacity = 1;
+		if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
+		if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
+		element.setStyle('overflow', 'hidden');
+		if (!test){
+			for (var fx in this.effects) element.setStyle(fx, 0);
+		}
+		return this;
+	},
+
+	detach: function(){
+		this.togglers.each(function(toggler) {
+			toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display'));
+		}, this);
+	},
+
+	display: function(index, useFx){
+		if (!this.check(index, useFx)) return this;
+		useFx = $pick(useFx, true);
+		if (this.options.returnHeightToAuto){
+			var prev = this.elements[this.previous];
+			if (prev && !this.selfHidden){
+				for (var fx in this.effects){
+					prev.setStyle(fx, prev[this.effects[fx]]);
+				}
+			}
+		}
+		index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
+		if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
+		this.previous = index;
+		var obj = {};
+		this.elements.each(function(el, i){
+			obj[i] = {};
+			var hide;
+			if (i != index){
+				hide = true;
+			} else if (this.options.alwaysHide && ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0 && this.options.width)){
+				hide = true;
+				this.selfHidden = true;
+			}
+			this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
+			for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
+		}, this);
+		this.internalChain.chain(function(){
+			if (this.options.returnHeightToAuto && !this.selfHidden){
+				var el = this.elements[index];
+				if (el) el.setStyle('height', 'auto');
+			};
+		}.bind(this));
+		return useFx ? this.start(obj) : this.set(obj);
+	}
+
+});
+
+/*
+	Compatibility with 1.2.0
+*/
+var Accordion = new Class({
+
+	Extends: Fx.Accordion,
+
+	initialize: function(){
+		this.parent.apply(this, arguments);
+		var params = Array.link(arguments, {'container': Element.type});
+		this.container = params.container;
+	},
+
+	addSection: function(toggler, element, pos){
+		toggler = document.id(toggler);
+		element = document.id(element);
+		var test = this.togglers.contains(toggler);
+		var len = this.togglers.length;
+		if (len && (!test || pos)){
+			pos = $pick(pos, len - 1);
+			toggler.inject(this.togglers[pos], 'before');
+			element.inject(toggler, 'after');
+		} else if (this.container && !test){
+			toggler.inject(this.container);
+			element.inject(this.container);
+		}
+		return this.parent.apply(this, arguments);
+	}
+
+});/*
+---
+
+script: Fx.Move.js
+
+description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Fx.Morph
+ - /Element.Position
+
+provides: [Fx.Move]
+
+...
+*/
+
+Fx.Move = new Class({
+
+	Extends: Fx.Morph,
+
+	options: {
+		relativeTo: document.body,
+		position: 'center',
+		edge: false,
+		offset: {x: 0, y: 0}
+	},
+
+	start: function(destination){
+		return this.parent(this.element.position($merge(this.options, destination, {returnPos: true})));
+	}
+
+});
+
+Element.Properties.move = {
+
+	set: function(options){
+		var morph = this.retrieve('move');
+		if (morph) morph.cancel();
+		return this.eliminate('move').store('move:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('move')){
+			if (options || !this.retrieve('move:options')) this.set('move', options);
+			this.store('move', new Fx.Move(this, this.retrieve('move:options')));
+		}
+		return this.retrieve('move');
+	}
+
+};
+
+Element.implement({
+
+	move: function(options){
+		this.get('move').start(options);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Scroll.js
+
+description: Effect to smoothly scroll any element, including the window.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Fx.Scroll]
+
+...
+*/
+
+Fx.Scroll = new Class({
+
+	Extends: Fx,
+
+	options: {
+		offset: {x: 0, y: 0},
+		wheelStops: true
+	},
+
+	initialize: function(element, options){
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var cancel = this.cancel.bind(this, false);
+
+		if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);
+
+		var stopper = this.element;
+
+		if (this.options.wheelStops){
+			this.addEvent('start', function(){
+				stopper.addEvent('mousewheel', cancel);
+			}, true);
+			this.addEvent('complete', function(){
+				stopper.removeEvent('mousewheel', cancel);
+			}, true);
+		}
+	},
+
+	set: function(){
+		var now = Array.flatten(arguments);
+		if (Browser.Engine.gecko) now = [Math.round(now[0]), Math.round(now[1])];
+		this.element.scrollTo(now[0], now[1]);
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(x, y){
+		if (!this.check(x, y)) return this;
+		var scrollSize = this.element.getScrollSize(),
+			scroll = this.element.getScroll(), 
+			values = {x: x, y: y};
+		for (var z in values){
+			var max = scrollSize[z];
+			if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z] : max;
+			else values[z] = scroll[z];
+			values[z] += this.options.offset[z];
+		}
+		return this.parent([scroll.x, scroll.y], [values.x, values.y]);
+	},
+
+	toTop: function(){
+		return this.start(false, 0);
+	},
+
+	toLeft: function(){
+		return this.start(0, false);
+	},
+
+	toRight: function(){
+		return this.start('right', false);
+	},
+
+	toBottom: function(){
+		return this.start(false, 'bottom');
+	},
+
+	toElement: function(el){
+		var position = document.id(el).getPosition(this.element);
+		return this.start(position.x, position.y);
+	},
+
+	scrollIntoView: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x','y'];
+		var to = {};
+		el = document.id(el);
+		var pos = el.getPosition(this.element);
+		var size = el.getSize();
+		var scroll = this.element.getScroll();
+		var containerSize = this.element.getSize();
+		var edge = {
+			x: pos.x + size.x,
+			y: pos.y + size.y
+		};
+		['x','y'].each(function(axis) {
+			if (axes.contains(axis)) {
+				if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
+				if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
+			}
+			if (to[axis] == null) to[axis] = scroll[axis];
+			if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	},
+
+	scrollToCenter: function(el, axes, offset){
+		axes = axes ? $splat(axes) : ['x', 'y'];
+		el = $(el);
+		var to = {},
+			pos = el.getPosition(this.element),
+			size = el.getSize(),
+			scroll = this.element.getScroll(),
+			containerSize = this.element.getSize(),
+			edge = {
+				x: pos.x + size.x,
+				y: pos.y + size.y
+			};
+
+		['x','y'].each(function(axis){
+			if(axes.contains(axis)){
+				to[axis] = pos[axis] - (containerSize[axis] - size[axis])/2;
+			}
+			if(to[axis] == null) to[axis] = scroll[axis];
+			if(offset && offset[axis]) to[axis] = to[axis] + offset[axis];
+		}, this);
+		if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.Slide.js
+
+description: Effect to slide an element in and out of view.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Fx Element.Style
+ - /MooTools.More
+
+provides: [Fx.Slide]
+
+...
+*/
+
+Fx.Slide = new Class({
+
+	Extends: Fx,
+
+	options: {
+		mode: 'vertical',
+		wrapper: false,
+		hideOverflow: true
+	},
+
+	initialize: function(element, options){
+		this.addEvent('complete', function(){
+			this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
+			if (this.open) this.wrapper.setStyle('height', '');
+			if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
+		}, true);
+		this.element = this.subject = document.id(element);
+		this.parent(options);
+		var wrapper = this.element.retrieve('wrapper');
+		var styles = this.element.getStyles('margin', 'position', 'overflow');
+		if (this.options.hideOverflow) styles = $extend(styles, {overflow: 'hidden'});
+		if (this.options.wrapper) wrapper = document.id(this.options.wrapper).setStyles(styles);
+		this.wrapper = wrapper || new Element('div', {
+			styles: styles
+		}).wraps(this.element);
+		this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
+		this.now = [];
+		this.open = true;
+	},
+
+	vertical: function(){
+		this.margin = 'margin-top';
+		this.layout = 'height';
+		this.offset = this.element.offsetHeight;
+	},
+
+	horizontal: function(){
+		this.margin = 'margin-left';
+		this.layout = 'width';
+		this.offset = this.element.offsetWidth;
+	},
+
+	set: function(now){
+		this.element.setStyle(this.margin, now[0]);
+		this.wrapper.setStyle(this.layout, now[1]);
+		return this;
+	},
+
+	compute: function(from, to, delta){
+		return [0, 1].map(function(i){
+			return Fx.compute(from[i], to[i], delta);
+		});
+	},
+
+	start: function(how, mode){
+		if (!this.check(how, mode)) return this;
+		this[mode || this.options.mode]();
+		var margin = this.element.getStyle(this.margin).toInt();
+		var layout = this.wrapper.getStyle(this.layout).toInt();
+		var caseIn = [[margin, layout], [0, this.offset]];
+		var caseOut = [[margin, layout], [-this.offset, 0]];
+		var start;
+		switch (how){
+			case 'in': start = caseIn; break;
+			case 'out': start = caseOut; break;
+			case 'toggle': start = (layout == 0) ? caseIn : caseOut;
+		}
+		return this.parent(start[0], start[1]);
+	},
+
+	slideIn: function(mode){
+		return this.start('in', mode);
+	},
+
+	slideOut: function(mode){
+		return this.start('out', mode);
+	},
+
+	hide: function(mode){
+		this[mode || this.options.mode]();
+		this.open = false;
+		return this.set([-this.offset, 0]);
+	},
+
+	show: function(mode){
+		this[mode || this.options.mode]();
+		this.open = true;
+		return this.set([0, this.offset]);
+	},
+
+	toggle: function(mode){
+		return this.start('toggle', mode);
+	}
+
+});
+
+Element.Properties.slide = {
+
+	set: function(options){
+		var slide = this.retrieve('slide');
+		if (slide) slide.cancel();
+		return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
+	},
+
+	get: function(options){
+		if (options || !this.retrieve('slide')){
+			if (options || !this.retrieve('slide:options')) this.set('slide', options);
+			this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
+		}
+		return this.retrieve('slide');
+	}
+
+};
+
+Element.implement({
+
+	slide: function(how, mode){
+		how = how || 'toggle';
+		var slide = this.get('slide'), toggle;
+		switch (how){
+			case 'hide': slide.hide(mode); break;
+			case 'show': slide.show(mode); break;
+			case 'toggle':
+				var flag = this.retrieve('slide:flag', slide.open);
+				slide[flag ? 'slideOut' : 'slideIn'](mode);
+				this.store('slide:flag', !flag);
+				toggle = true;
+			break;
+			default: slide.start(how, mode);
+		}
+		if (!toggle) this.eliminate('slide:flag');
+		return this;
+	}
+
+});
+/*
+---
+
+script: Fx.SmoothScroll.js
+
+description: Class for creating a smooth scrolling effect to all internal links on the page.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Selectors
+ - /Fx.Scroll
+
+provides: [Fx.SmoothScroll]
+
+...
+*/
+
+var SmoothScroll = Fx.SmoothScroll = new Class({
+
+	Extends: Fx.Scroll,
+
+	initialize: function(options, context){
+		context = context || document;
+		this.doc = context.getDocument();
+		var win = context.getWindow();
+		this.parent(this.doc, options);
+		this.links = $$(this.options.links || this.doc.links);
+		var location = win.location.href.match(/^[^#]*/)[0] + '#';
+		this.links.each(function(link){
+			if (link.href.indexOf(location) != 0) {return;}
+			var anchor = link.href.substr(location.length);
+			if (anchor) this.useLink(link, anchor);
+		}, this);
+		if (!Browser.Engine.webkit419) {
+			this.addEvent('complete', function(){
+				win.location.hash = this.anchor;
+			}, true);
+		}
+	},
+
+	useLink: function(link, anchor){
+		var el;
+		link.addEvent('click', function(event){
+			if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
+			if (el) {
+				event.preventDefault();
+				this.anchor = anchor;
+				this.toElement(el).chain(function(){
+					this.fireEvent('scrolledTo', [link, el]);
+				}.bind(this));
+				link.blur();
+			}
+		}.bind(this));
+	}
+});/*
+---
+
+script: Fx.Sort.js
+
+description: Defines Fx.Sort, a class that reorders lists with a transition.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Fx.Elements
+ - /Element.Measure
+
+provides: [Fx.Sort]
+
+...
+*/
+
+Fx.Sort = new Class({
+
+	Extends: Fx.Elements,
+
+	options: {
+		mode: 'vertical'
+	},
+
+	initialize: function(elements, options){
+		this.parent(elements, options);
+		this.elements.each(function(el){
+			if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
+		});
+		this.setDefaultOrder();
+	},
+
+	setDefaultOrder: function(){
+		this.currentOrder = this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	sort: function(newOrder){
+		if ($type(newOrder) != 'array') return false;
+		var top = 0,
+			left = 0,
+			next = {},
+			zero = {},
+			vert = this.options.mode == 'vertical';
+		var current = this.elements.map(function(el, index){
+			var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
+			var val;
+			if (vert){
+				val = {
+					top: top,
+					margin: size['margin-top'],
+					height: size.totalHeight
+				};
+				top += val.height - size['margin-top'];
+			} else {
+				val = {
+					left: left,
+					margin: size['margin-left'],
+					width: size.totalWidth
+				};
+				left += val.width;
+			}
+			var plain = vert ? 'top' : 'left';
+			zero[index] = {};
+			var start = el.getStyle(plain).toInt();
+			zero[index][plain] = start || 0;
+			return val;
+		}, this);
+		this.set(zero);
+		newOrder = newOrder.map(function(i){ return i.toInt(); });
+		if (newOrder.length != this.elements.length){
+			this.currentOrder.each(function(index){
+				if (!newOrder.contains(index)) newOrder.push(index);
+			});
+			if (newOrder.length > this.elements.length)
+				newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
+		}
+		var margin = top = left = 0;
+		newOrder.each(function(item, index){
+			var newPos = {};
+			if (vert){
+				newPos.top = top - current[item].top - margin;
+				top += current[item].height;
+			} else {
+				newPos.left = left - current[item].left;
+				left += current[item].width;
+			}
+			margin = margin + current[item].margin;
+			next[item]=newPos;
+		}, this);
+		var mapped = {};
+		$A(newOrder).sort().each(function(index){
+			mapped[index] = next[index];
+		});
+		this.start(mapped);
+		this.currentOrder = newOrder;
+		return this;
+	},
+
+	rearrangeDOM: function(newOrder){
+		newOrder = newOrder || this.currentOrder;
+		var parent = this.elements[0].getParent();
+		var rearranged = [];
+		this.elements.setStyle('opacity', 0);
+		//move each element and store the new default order
+		newOrder.each(function(index){
+			rearranged.push(this.elements[index].inject(parent).setStyles({
+				top: 0,
+				left: 0
+			}));
+		}, this);
+		this.elements.setStyle('opacity', 1);
+		this.elements = $$(rearranged);
+		this.setDefaultOrder();
+		return this;
+	},
+
+	getDefaultOrder: function(){
+		return this.elements.map(function(el, index){
+			return index;
+		});
+	},
+
+	forward: function(){
+		return this.sort(this.getDefaultOrder());
+	},
+
+	backward: function(){
+		return this.sort(this.getDefaultOrder().reverse());
+	},
+
+	reverse: function(){
+		return this.sort(this.currentOrder.reverse());
+	},
+
+	sortByElements: function(elements){
+		return this.sort(elements.map(function(el){
+			return this.elements.indexOf(el);
+		}, this));
+	},
+
+	swap: function(one, two){
+		if ($type(one) == 'element') one = this.elements.indexOf(one);
+		if ($type(two) == 'element') two = this.elements.indexOf(two);
+		
+		var newOrder = $A(this.currentOrder);
+		newOrder[this.currentOrder.indexOf(one)] = two;
+		newOrder[this.currentOrder.indexOf(two)] = one;
+		return this.sort(newOrder);
+	}
+
+});/*
+---
+
+script: Drag.js
+
+description: The base Drag Class. Can be used to drag and resize Elements using mouse events.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - /MooTools.More
+
+provides: [Drag]
+
+...
+*/
+
+var Drag = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onBeforeStart: $empty(thisElement),
+		onStart: $empty(thisElement, event),
+		onSnap: $empty(thisElement)
+		onDrag: $empty(thisElement, event),
+		onCancel: $empty(thisElement),
+		onComplete: $empty(thisElement, event),*/
+		snap: 6,
+		unit: 'px',
+		grid: false,
+		style: true,
+		limit: false,
+		handle: false,
+		invert: false,
+		preventDefault: false,
+		stopPropagation: false,
+		modifiers: {x: 'left', y: 'top'}
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
+		this.element = document.id(params.element);
+		this.document = this.element.getDocument();
+		this.setOptions(params.options || {});
+		var htype = $type(this.options.handle);
+		this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
+		this.mouse = {'now': {}, 'pos': {}};
+		this.value = {'start': {}, 'now': {}};
+
+		this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';
+
+		this.bound = {
+			start: this.start.bind(this),
+			check: this.check.bind(this),
+			drag: this.drag.bind(this),
+			stop: this.stop.bind(this),
+			cancel: this.cancel.bind(this),
+			eventStop: $lambda(false)
+		};
+		this.attach();
+	},
+
+	attach: function(){
+		this.handles.addEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	detach: function(){
+		this.handles.removeEvent('mousedown', this.bound.start);
+		return this;
+	},
+
+	start: function(event){
+		if (event.rightClick) return;
+		if (this.options.preventDefault) event.preventDefault();
+		if (this.options.stopPropagation) event.stopPropagation();
+		this.mouse.start = event.page;
+		this.fireEvent('beforeStart', this.element);
+		var limit = this.options.limit;
+		this.limit = {x: [], y: []};
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
+			else this.value.now[z] = this.element[this.options.modifiers[z]];
+			if (this.options.invert) this.value.now[z] *= -1;
+			this.mouse.pos[z] = event.page[z] - this.value.now[z];
+			if (limit && limit[z]){
+				for (var i = 2; i--; i){
+					if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
+				}
+			}
+		}
+		if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
+		this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
+		this.document.addEvent(this.selection, this.bound.eventStop);
+	},
+
+	check: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
+		if (distance > this.options.snap){
+			this.cancel();
+			this.document.addEvents({
+				mousemove: this.bound.drag,
+				mouseup: this.bound.stop
+			});
+			this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
+		}
+	},
+
+	drag: function(event){
+		if (this.options.preventDefault) event.preventDefault();
+		this.mouse.now = event.page;
+		for (var z in this.options.modifiers){
+			if (!this.options.modifiers[z]) continue;
+			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
+			if (this.options.invert) this.value.now[z] *= -1;
+			if (this.options.limit && this.limit[z]){
+				if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
+					this.value.now[z] = this.limit[z][1];
+				} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
+					this.value.now[z] = this.limit[z][0];
+				}
+			}
+			if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
+			if (this.options.style) {
+				this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
+			} else {
+				this.element[this.options.modifiers[z]] = this.value.now[z];
+			}
+		}
+		this.fireEvent('drag', [this.element, event]);
+	},
+
+	cancel: function(event){
+		this.document.removeEvent('mousemove', this.bound.check);
+		this.document.removeEvent('mouseup', this.bound.cancel);
+		if (event){
+			this.document.removeEvent(this.selection, this.bound.eventStop);
+			this.fireEvent('cancel', this.element);
+		}
+	},
+
+	stop: function(event){
+		this.document.removeEvent(this.selection, this.bound.eventStop);
+		this.document.removeEvent('mousemove', this.bound.drag);
+		this.document.removeEvent('mouseup', this.bound.stop);
+		if (event) this.fireEvent('complete', [this.element, event]);
+	}
+
+});
+
+Element.implement({
+
+	makeResizable: function(options){
+		var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
+		this.store('resizer', drag);
+		return drag.addEvent('drag', function(){
+			this.fireEvent('resize', drag);
+		}.bind(this));
+	}
+
+});
+/*
+---
+
+script: Drag.Move.js
+
+description: A Drag extension that provides support for the constraining of draggables to containers and droppables.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Tom Occhinno
+ - Jan Kassens
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Drag
+
+provides: [Drag.Move]
+
+...
+*/
+
+Drag.Move = new Class({
+
+	Extends: Drag,
+
+	options: {/*
+		onEnter: $empty(thisElement, overed),
+		onLeave: $empty(thisElement, overed),
+		onDrop: $empty(thisElement, overed, event),*/
+		droppables: [],
+		container: false,
+		precalculate: false,
+		includeMargins: true,
+		checkDroppables: true
+	},
+
+	initialize: function(element, options){
+		this.parent(element, options);
+		element = this.element;
+		
+		this.droppables = $$(this.options.droppables);
+		this.container = document.id(this.options.container);
+		
+		if (this.container && $type(this.container) != 'element')
+			this.container = document.id(this.container.getDocument().body);
+		
+		var styles = element.getStyles('left', 'top', 'position');
+		if (styles.left == 'auto' || styles.top == 'auto')
+			element.setPosition(element.getPosition(element.getOffsetParent()));
+		
+		if (styles.position == 'static')
+			element.setStyle('position', 'absolute');
+
+		this.addEvent('start', this.checkDroppables, true);
+
+		this.overed = null;
+	},
+
+	start: function(event){
+		if (this.container) this.options.limit = this.calculateLimit();
+		
+		if (this.options.precalculate){
+			this.positions = this.droppables.map(function(el){
+				return el.getCoordinates();
+			});
+		}
+		
+		this.parent(event);
+	},
+	
+	calculateLimit: function(){
+		var offsetParent = this.element.getOffsetParent(),
+			containerCoordinates = this.container.getCoordinates(offsetParent),
+			containerBorder = {},
+			elementMargin = {},
+			elementBorder = {},
+			containerMargin = {},
+			offsetParentPadding = {};
+
+		['top', 'right', 'bottom', 'left'].each(function(pad){
+			containerBorder[pad] = this.container.getStyle('border-' + pad).toInt();
+			elementBorder[pad] = this.element.getStyle('border-' + pad).toInt();
+			elementMargin[pad] = this.element.getStyle('margin-' + pad).toInt();
+			containerMargin[pad] = this.container.getStyle('margin-' + pad).toInt();
+			offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt();
+		}, this);
+
+		var width = this.element.offsetWidth + elementMargin.left + elementMargin.right,
+			height = this.element.offsetHeight + elementMargin.top + elementMargin.bottom,
+			left = 0,
+			top = 0,
+			right = containerCoordinates.right - containerBorder.right - width,
+			bottom = containerCoordinates.bottom - containerBorder.bottom - height;
+
+		if (this.options.includeMargins){
+			left += elementMargin.left;
+			top += elementMargin.top;
+		} else {
+			right += elementMargin.right;
+			bottom += elementMargin.bottom;
+		}
+		
+		if (this.element.getStyle('position') == 'relative'){
+			var coords = this.element.getCoordinates(offsetParent);
+			coords.left -= this.element.getStyle('left').toInt();
+			coords.top -= this.element.getStyle('top').toInt();
+			
+			left += containerBorder.left - coords.left;
+			top += containerBorder.top - coords.top;
+			right += elementMargin.left - coords.left;
+			bottom += elementMargin.top - coords.top;
+			
+			if (this.container != offsetParent){
+				left += containerMargin.left + offsetParentPadding.left;
+				top += (Browser.Engine.trident4 ? 0 : containerMargin.top) + offsetParentPadding.top;
+			}
+		} else {
+			left -= elementMargin.left;
+			top -= elementMargin.top;
+			
+			if (this.container == offsetParent){
+				right -= containerBorder.left;
+				bottom -= containerBorder.top;
+			} else {
+				left += containerCoordinates.left + containerBorder.left;
+				top += containerCoordinates.top + containerBorder.top;
+			}
+		}
+		
+		return {
+			x: [left, right],
+			y: [top, bottom]
+		};
+	},
+
+	checkAgainst: function(el, i){
+		el = (this.positions) ? this.positions[i] : el.getCoordinates();
+		var now = this.mouse.now;
+		return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
+	},
+
+	checkDroppables: function(){
+		var overed = this.droppables.filter(this.checkAgainst, this).getLast();
+		if (this.overed != overed){
+			if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
+			if (overed) this.fireEvent('enter', [this.element, overed]);
+			this.overed = overed;
+		}
+	},
+
+	drag: function(event){
+		this.parent(event);
+		if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
+	},
+
+	stop: function(event){
+		this.checkDroppables();
+		this.fireEvent('drop', [this.element, this.overed, event]);
+		this.overed = null;
+		return this.parent(event);
+	}
+
+});
+
+Element.implement({
+
+	makeDraggable: function(options){
+		var drag = new Drag.Move(this, options);
+		this.store('dragger', drag);
+		return drag;
+	}
+
+});
+/*
+---
+
+script: Slider.js
+
+description: Class for creating horizontal and vertical slider controls.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Dimensions
+ - /Class.Binds
+ - /Drag
+ - /Element.Dimensions
+ - /Element.Measure
+
+provides: [Slider]
+
+...
+*/
+
+var Slider = new Class({
+
+	Implements: [Events, Options],
+
+	Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],
+
+	options: {/*
+		onTick: $empty(intPosition),
+		onChange: $empty(intStep),
+		onComplete: $empty(strStep),*/
+		onTick: function(position){
+			if (this.options.snap) position = this.toPosition(this.step);
+			this.knob.setStyle(this.property, position);
+		},
+		initialStep: 0,
+		snap: false,
+		offset: 0,
+		range: false,
+		wheel: false,
+		steps: 100,
+		mode: 'horizontal'
+	},
+
+	initialize: function(element, knob, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.knob = document.id(knob);
+		this.previousChange = this.previousEnd = this.step = -1;
+		var offset, limit = {}, modifiers = {'x': false, 'y': false};
+		switch (this.options.mode){
+			case 'vertical':
+				this.axis = 'y';
+				this.property = 'top';
+				offset = 'offsetHeight';
+				break;
+			case 'horizontal':
+				this.axis = 'x';
+				this.property = 'left';
+				offset = 'offsetWidth';
+		}
+		
+		this.full = this.element.measure(function(){ 
+			this.half = this.knob[offset] / 2; 
+			return this.element[offset] - this.knob[offset] + (this.options.offset * 2); 
+		}.bind(this));
+		
+		this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
+		this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
+		this.range = this.max - this.min;
+		this.steps = this.options.steps || this.full;
+		this.stepSize = Math.abs(this.range) / this.steps;
+		this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;
+
+		this.knob.setStyle('position', 'relative').setStyle(this.property, this.options.initialStep ? this.toPosition(this.options.initialStep) : - this.options.offset);
+		modifiers[this.axis] = this.property;
+		limit[this.axis] = [- this.options.offset, this.full - this.options.offset];
+
+		var dragOptions = {
+			snap: 0,
+			limit: limit,
+			modifiers: modifiers,
+			onDrag: this.draggedKnob,
+			onStart: this.draggedKnob,
+			onBeforeStart: (function(){
+				this.isDragging = true;
+			}).bind(this),
+			onCancel: function() {
+				this.isDragging = false;
+			}.bind(this),
+			onComplete: function(){
+				this.isDragging = false;
+				this.draggedKnob();
+				this.end();
+			}.bind(this)
+		};
+		if (this.options.snap){
+			dragOptions.grid = Math.ceil(this.stepWidth);
+			dragOptions.limit[this.axis][1] = this.full;
+		}
+
+		this.drag = new Drag(this.knob, dragOptions);
+		this.attach();
+	},
+
+	attach: function(){
+		this.element.addEvent('mousedown', this.clickedElement);
+		if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement);
+		this.drag.attach();
+		return this;
+	},
+
+	detach: function(){
+		this.element.removeEvent('mousedown', this.clickedElement);
+		this.element.removeEvent('mousewheel', this.scrolledElement);
+		this.drag.detach();
+		return this;
+	},
+
+	set: function(step){
+		if (!((this.range > 0) ^ (step < this.min))) step = this.min;
+		if (!((this.range > 0) ^ (step > this.max))) step = this.max;
+
+		this.step = Math.round(step);
+		this.checkStep();
+		this.fireEvent('tick', this.toPosition(this.step));
+		this.end();
+		return this;
+	},
+
+	clickedElement: function(event){
+		if (this.isDragging || event.target == this.knob) return;
+
+		var dir = this.range < 0 ? -1 : 1;
+		var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+		this.fireEvent('tick', position);
+		this.end();
+	},
+
+	scrolledElement: function(event){
+		var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
+		this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
+		event.stop();
+	},
+
+	draggedKnob: function(){
+		var dir = this.range < 0 ? -1 : 1;
+		var position = this.drag.value.now[this.axis];
+		position = position.limit(-this.options.offset, this.full -this.options.offset);
+		this.step = Math.round(this.min + dir * this.toStep(position));
+		this.checkStep();
+	},
+
+	checkStep: function(){
+		if (this.previousChange != this.step){
+			this.previousChange = this.step;
+			this.fireEvent('change', this.step);
+		}
+	},
+
+	end: function(){
+		if (this.previousEnd !== this.step){
+			this.previousEnd = this.step;
+			this.fireEvent('complete', this.step + '');
+		}
+	},
+
+	toStep: function(position){
+		var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
+		return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
+	},
+
+	toPosition: function(step){
+		return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
+	}
+
+});/*
+---
+
+script: Sortables.js
+
+description: Class for creating a drag and drop sorting interface for lists of items.
+
+license: MIT-style license
+
+authors:
+ - Tom Occhino
+
+requires:
+ - /Drag.Move
+
+provides: [Slider]
+
+...
+*/
+
+var Sortables = new Class({
+
+	Implements: [Events, Options],
+
+	options: {/*
+		onSort: $empty(element, clone),
+		onStart: $empty(element, clone),
+		onComplete: $empty(element),*/
+		snap: 4,
+		opacity: 1,
+		clone: false,
+		revert: false,
+		handle: false,
+		constrain: false
+	},
+
+	initialize: function(lists, options){
+		this.setOptions(options);
+		this.elements = [];
+		this.lists = [];
+		this.idle = true;
+
+		this.addLists($$(document.id(lists) || lists));
+		if (!this.options.clone) this.options.revert = false;
+		if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
+	},
+
+	attach: function(){
+		this.addLists(this.lists);
+		return this;
+	},
+
+	detach: function(){
+		this.lists = this.removeLists(this.lists);
+		return this;
+	},
+
+	addItems: function(){
+		Array.flatten(arguments).each(function(element){
+			this.elements.push(element);
+			var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
+		}, this);
+		return this;
+	},
+
+	addLists: function(){
+		Array.flatten(arguments).each(function(list){
+			this.lists.push(list);
+			this.addItems(list.getChildren());
+		}, this);
+		return this;
+	},
+
+	removeItems: function(){
+		return $$(Array.flatten(arguments).map(function(element){
+			this.elements.erase(element);
+			var start = element.retrieve('sortables:start');
+			(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
+			
+			return element;
+		}, this));
+	},
+
+	removeLists: function(){
+		return $$(Array.flatten(arguments).map(function(list){
+			this.lists.erase(list);
+			this.removeItems(list.getChildren());
+			
+			return list;
+		}, this));
+	},
+
+	getClone: function(event, element){
+		if (!this.options.clone) return new Element('div').inject(document.body);
+		if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
+		var clone = element.clone(true).setStyles({
+			margin: '0px',
+			position: 'absolute',
+			visibility: 'hidden',
+			'width': element.getStyle('width')
+		});
+		//prevent the duplicated radio inputs from unchecking the real one
+		if (clone.get('html').test('radio')) {
+			clone.getElements('input[type=radio]').each(function(input, i) {
+				input.set('name', 'clone_' + i);
+			});
+		}
+		
+		return clone.inject(this.list).setPosition(element.getPosition(element.getOffsetParent()));
+	},
+
+	getDroppables: function(){
+		var droppables = this.list.getChildren();
+		if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
+		return droppables.erase(this.clone).erase(this.element);
+	},
+
+	insert: function(dragging, element){
+		var where = 'inside';
+		if (this.lists.contains(element)){
+			this.list = element;
+			this.drag.droppables = this.getDroppables();
+		} else {
+			where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
+		}
+		this.element.inject(element, where);
+		this.fireEvent('sort', [this.element, this.clone]);
+	},
+
+	start: function(event, element){
+		if (!this.idle) return;
+		this.idle = false;
+		this.element = element;
+		this.opacity = element.get('opacity');
+		this.list = element.getParent();
+		this.clone = this.getClone(event, element);
+
+		this.drag = new Drag.Move(this.clone, {
+			snap: this.options.snap,
+			container: this.options.constrain && this.element.getParent(),
+			droppables: this.getDroppables(),
+			onSnap: function(){
+				event.stop();
+				this.clone.setStyle('visibility', 'visible');
+				this.element.set('opacity', this.options.opacity || 0);
+				this.fireEvent('start', [this.element, this.clone]);
+			}.bind(this),
+			onEnter: this.insert.bind(this),
+			onCancel: this.reset.bind(this),
+			onComplete: this.end.bind(this)
+		});
+
+		this.clone.inject(this.element, 'before');
+		this.drag.start(event);
+	},
+
+	end: function(){
+		this.drag.detach();
+		this.element.set('opacity', this.opacity);
+		if (this.effect){
+			var dim = this.element.getStyles('width', 'height');
+			var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
+			this.effect.element = this.clone;
+			this.effect.start({
+				top: pos.top,
+				left: pos.left,
+				width: dim.width,
+				height: dim.height,
+				opacity: 0.25
+			}).chain(this.reset.bind(this));
+		} else {
+			this.reset();
+		}
+	},
+
+	reset: function(){
+		this.idle = true;
+		this.clone.destroy();
+		this.fireEvent('complete', this.element);
+	},
+
+	serialize: function(){
+		var params = Array.link(arguments, {modifier: Function.type, index: $defined});
+		var serial = this.lists.map(function(list){
+			return list.getChildren().map(params.modifier || function(element){
+				return element.get('id');
+			}, this);
+		}, this);
+
+		var index = params.index;
+		if (this.lists.length == 1) index = 0;
+		return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
+	}
+
+});
+/*
+---
+
+script: Request.JSONP.js
+
+description: Defines Request.JSONP, a class for cross domain javascript via script injection.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+ - Guillermo Rauch
+
+requires:
+ - core:1.2.4/Element
+ - core:1.2.4/Request
+ - /Log
+
+provides: [Request.JSONP]
+
+...
+*/
+
+Request.JSONP = new Class({
+
+	Implements: [Chain, Events, Options, Log],
+
+	options: {/*
+		onRetry: $empty(intRetries),
+		onRequest: $empty(scriptElement),
+		onComplete: $empty(data),
+		onSuccess: $empty(data),
+		onCancel: $empty(),
+		log: false,
+		*/
+		url: '',
+		data: {},
+		retries: 0,
+		timeout: 0,
+		link: 'ignore',
+		callbackKey: 'callback',
+		injectScript: document.head
+	},
+
+	initialize: function(options){
+		this.setOptions(options);
+		if (this.options.log) this.enableLog();
+		this.running = false;
+		this.requests = 0;
+		this.triesRemaining = [];
+	},
+
+	check: function(){
+		if (!this.running) return true;
+		switch (this.options.link){
+			case 'cancel': this.cancel(); return true;
+			case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
+		}
+		return false;
+	},
+
+	send: function(options){
+		if (!$chk(arguments[1]) && !this.check(options)) return this;
+
+		var type = $type(options), 
+				old = this.options, 
+				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
+		if (type == 'string' || type == 'element') options = {data: options};
+
+		options = $extend({data: old.data, url: old.url}, options);
+
+		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
+		var remaining = this.triesRemaining[index];
+
+		(function(){
+			var script = this.getScript(options);
+			this.log('JSONP retrieving script with url: ' + script.get('src'));
+			this.fireEvent('request', script);
+			this.running = true;
+
+			(function(){
+				if (remaining){
+					this.triesRemaining[index] = remaining - 1;
+					if (script){
+						script.destroy();
+						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
+					}
+				} else if(script && this.options.timeout){
+					script.destroy();
+					this.cancel().fireEvent('failure');
+				}
+			}).delay(this.options.timeout, this);
+		}).delay(Browser.Engine.trident ? 50 : 0, this);
+		return this;
+	},
+
+	cancel: function(){
+		if (!this.running) return this;
+		this.running = false;
+		this.fireEvent('cancel');
+		return this;
+	},
+
+	getScript: function(options){
+		var index = Request.JSONP.counter,
+				data;
+		Request.JSONP.counter++;
+
+		switch ($type(options.data)){
+			case 'element': data = document.id(options.data).toQueryString(); break;
+			case 'object': case 'hash': data = Hash.toQueryString(options.data);
+		}
+
+		var src = options.url + 
+			 (options.url.test('\\?') ? '&' :'?') + 
+			 (options.callbackKey || this.options.callbackKey) + 
+			 '=Request.JSONP.request_map.request_'+ index + 
+			 (data ? '&' + data : '');
+		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');
+
+		var script = new Element('script', {type: 'text/javascript', src: src});
+		Request.JSONP.request_map['request_' + index] = function(){ this.success(arguments, script); }.bind(this);
+		return script.inject(this.options.injectScript);
+	},
+
+	success: function(args, script){
+		if (script) script.destroy();
+		this.running = false;
+		this.log('JSONP successfully retrieved: ', args);
+		this.fireEvent('complete', args).fireEvent('success', args).callChain();
+	}
+
+});
+
+Request.JSONP.counter = 0;
+Request.JSONP.request_map = {};/*
+---
+
+script: Request.Queue.js
+
+description: Controls several instances of Request and its variants to run only one request at a time.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Element
+ - core:1.2.4/Request
+ - /Log
+
+provides: [Request.Queue]
+
+...
+*/
+
+Request.Queue = new Class({
+
+	Implements: [Options, Events],
+
+	Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'],
+
+	options: {/*
+		onRequest: $empty(argsPassedToOnRequest),
+		onSuccess: $empty(argsPassedToOnSuccess),
+		onComplete: $empty(argsPassedToOnComplete),
+		onCancel: $empty(argsPassedToOnCancel),
+		onException: $empty(argsPassedToOnException),
+		onFailure: $empty(argsPassedToOnFailure),
+		onEnd: $empty,
+		*/
+		stopOnFailure: true,
+		autoAdvance: true,
+		concurrent: 1,
+		requests: {}
+	},
+
+	initialize: function(options){
+		if(options){
+			var requests = options.requests;
+			delete options.requests;	
+		}
+		this.setOptions(options);
+		this.requests = new Hash;
+		this.queue = [];
+		this.reqBinders = {};
+		
+		if(requests) this.addRequests(requests);
+	},
+
+	addRequest: function(name, request){
+		this.requests.set(name, request);
+		this.attach(name, request);
+		return this;
+	},
+
+	addRequests: function(obj){
+		$each(obj, function(req, name){
+			this.addRequest(name, req);
+		}, this);
+		return this;
+	},
+
+	getName: function(req){
+		return this.requests.keyOf(req);
+	},
+
+	attach: function(name, req){
+		if (req._groupSend) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			if(!this.reqBinders[name]) this.reqBinders[name] = {};
+			this.reqBinders[name][evt] = function(){
+				this['on' + evt.capitalize()].apply(this, [name, req].extend(arguments));
+			}.bind(this);
+			req.addEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req._groupSend = req.send;
+		req.send = function(options){
+			this.send(name, options);
+			return req;
+		}.bind(this);
+		return this;
+	},
+
+	removeRequest: function(req){
+		var name = $type(req) == 'object' ? this.getName(req) : req;
+		if (!name && $type(name) != 'string') return this;
+		req = this.requests.get(name);
+		if (!req) return this;
+		['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){
+			req.removeEvent(evt, this.reqBinders[name][evt]);
+		}, this);
+		req.send = req._groupSend;
+		delete req._groupSend;
+		return this;
+	},
+
+	getRunning: function(){
+		return this.requests.filter(function(r){
+			return r.running;
+		});
+	},
+
+	isRunning: function(){
+		return !!(this.getRunning().getKeys().length);
+	},
+
+	send: function(name, options){
+		var q = function(){
+			this.requests.get(name)._groupSend(options);
+			this.queue.erase(q);
+		}.bind(this);
+		q.name = name;
+		if (this.getRunning().getKeys().length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q);
+		else q();
+		return this;
+	},
+
+	hasNext: function(name){
+		return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length;
+	},
+
+	resume: function(){
+		this.error = false;
+		(this.options.concurrent - this.getRunning().getKeys().length).times(this.runNext, this);
+		return this;
+	},
+
+	runNext: function(name){
+		if (!this.queue.length) return this;
+		if (!name){
+			this.queue[0]();
+		} else {
+			var found;
+			this.queue.each(function(q){
+				if (!found && q.name == name){
+					found = true;
+					q();
+				}
+			});
+		}
+		return this;
+	},
+
+	runAll: function() {
+		this.queue.each(function(q) {
+			q();
+		});
+		return this;
+	},
+
+	clear: function(name){
+		if (!name){
+			this.queue.empty();
+		} else {
+			this.queue = this.queue.map(function(q){
+				if (q.name != name) return q;
+				else return false;
+			}).filter(function(q){ return q; });
+		}
+		return this;
+	},
+
+	cancel: function(name){
+		this.requests.get(name).cancel();
+		return this;
+	},
+
+	onRequest: function(){
+		this.fireEvent('request', arguments);
+	},
+
+	onComplete: function(){
+		this.fireEvent('complete', arguments);
+		if (!this.queue.length) this.fireEvent('end');
+	},
+
+	onCancel: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('cancel', arguments);
+	},
+
+	onSuccess: function(){
+		if (this.options.autoAdvance && !this.error) this.runNext();
+		this.fireEvent('success', arguments);
+	},
+
+	onFailure: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('failure', arguments);
+	},
+
+	onException: function(){
+		this.error = true;
+		if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext();
+		this.fireEvent('exception', arguments);
+	}
+
+});
+/*
+---
+
+script: Request.Periodical.js
+
+description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Request
+ - /MooTools.More
+
+provides: [Request.Periodical]
+
+...
+*/
+
+Request.implement({
+
+	options: {
+		initialDelay: 5000,
+		delay: 5000,
+		limit: 60000
+	},
+
+	startTimer: function(data){
+		var fn = function(){
+			if (!this.running) this.send({data: data});
+		};
+		this.timer = fn.delay(this.options.initialDelay, this);
+		this.lastDelay = this.options.initialDelay;
+		this.completeCheck = function(response){
+			$clear(this.timer);
+			this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit);
+			this.timer = fn.delay(this.lastDelay, this);
+		};
+		return this.addEvent('complete', this.completeCheck);
+	},
+
+	stopTimer: function(){
+		$clear(this.timer);
+		return this.removeEvent('complete', this.completeCheck);
+	}
+
+});/*
+---
+
+script: Assets.js
+
+description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Element.Event
+ - /MooTools.More
+
+provides: [Assets]
+
+...
+*/
+
+var Asset = {
+
+	javascript: function(source, properties){
+		properties = $extend({
+			onload: $empty,
+			document: document,
+			check: $lambda(true)
+		}, properties);
+		
+		if (properties.onLoad) properties.onload = properties.onLoad;
+		
+		var script = new Element('script', {src: source, type: 'text/javascript'});
+
+		var load = properties.onload.bind(script), 
+			check = properties.check, 
+			doc = properties.document;
+		delete properties.onload;
+		delete properties.check;
+		delete properties.document;
+
+		script.addEvents({
+			load: load,
+			readystatechange: function(){
+				if (['loaded', 'complete'].contains(this.readyState)) load();
+			}
+		}).set(properties);
+
+		if (Browser.Engine.webkit419) var checker = (function(){
+			if (!$try(check)) return;
+			$clear(checker);
+			load();
+		}).periodical(50);
+
+		return script.inject(doc.head);
+	},
+
+	css: function(source, properties){
+		return new Element('link', $merge({
+			rel: 'stylesheet',
+			media: 'screen',
+			type: 'text/css',
+			href: source
+		}, properties)).inject(document.head);
+	},
+
+	image: function(source, properties){
+		properties = $merge({
+			onload: $empty,
+			onabort: $empty,
+			onerror: $empty
+		}, properties);
+		var image = new Image();
+		var element = document.id(image) || new Element('img');
+		['load', 'abort', 'error'].each(function(name){
+			var type = 'on' + name;
+			var cap = name.capitalize();
+			if (properties['on' + cap]) properties[type] = properties['on' + cap];
+			var event = properties[type];
+			delete properties[type];
+			image[type] = function(){
+				if (!image) return;
+				if (!element.parentNode){
+					element.width = image.width;
+					element.height = image.height;
+				}
+				image = image.onload = image.onabort = image.onerror = null;
+				event.delay(1, element, element);
+				element.fireEvent(name, element, 1);
+			};
+		});
+		image.src = element.src = source;
+		if (image && image.complete) image.onload.delay(1);
+		return element.set(properties);
+	},
+
+	images: function(sources, options){
+		options = $merge({
+			onComplete: $empty,
+			onProgress: $empty,
+			onError: $empty,
+			properties: {}
+		}, options);
+		sources = $splat(sources);
+		var images = [];
+		var counter = 0;
+		return new Elements(sources.map(function(source){
+			return Asset.image(source, $extend(options.properties, {
+				onload: function(){
+					options.onProgress.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				},
+				onerror: function(){
+					options.onError.call(this, counter, sources.indexOf(source));
+					counter++;
+					if (counter == sources.length) options.onComplete();
+				}
+			}));
+		}));
+	}
+
+};/*
+---
+
+script: Color.js
+
+description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Array
+ - core:1.2.4/String
+ - core:1.2.4/Number
+ - core:1.2.4/Hash
+ - core:1.2.4/Function
+ - core:1.2.4/$util
+
+provides: [Color]
+
+...
+*/
+
+var Color = new Native({
+
+	initialize: function(color, type){
+		if (arguments.length >= 3){
+			type = 'rgb'; color = Array.slice(arguments, 0, 3);
+		} else if (typeof color == 'string'){
+			if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
+			else if (color.match(/hsb/)) color = color.hsbToRgb();
+			else color = color.hexToRgb(true);
+		}
+		type = type || 'rgb';
+		switch (type){
+			case 'hsb':
+				var old = color;
+				color = color.hsbToRgb();
+				color.hsb = old;
+			break;
+			case 'hex': color = color.hexToRgb(true); break;
+		}
+		color.rgb = color.slice(0, 3);
+		color.hsb = color.hsb || color.rgbToHsb();
+		color.hex = color.rgbToHex();
+		return $extend(color, this);
+	}
+
+});
+
+Color.implement({
+
+	mix: function(){
+		var colors = Array.slice(arguments);
+		var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
+		var rgb = this.slice();
+		colors.each(function(color){
+			color = new Color(color);
+			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
+		});
+		return new Color(rgb, 'rgb');
+	},
+
+	invert: function(){
+		return new Color(this.map(function(value){
+			return 255 - value;
+		}));
+	},
+
+	setHue: function(value){
+		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
+	},
+
+	setSaturation: function(percent){
+		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
+	},
+
+	setBrightness: function(percent){
+		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
+	}
+
+});
+
+var $RGB = function(r, g, b){
+	return new Color([r, g, b], 'rgb');
+};
+
+var $HSB = function(h, s, b){
+	return new Color([h, s, b], 'hsb');
+};
+
+var $HEX = function(hex){
+	return new Color(hex, 'hex');
+};
+
+Array.implement({
+
+	rgbToHsb: function(){
+		var red = this[0],
+				green = this[1],
+				blue = this[2],
+				hue = 0;
+		var max = Math.max(red, green, blue),
+				min = Math.min(red, green, blue);
+		var delta = max - min;
+		var brightness = max / 255,
+				saturation = (max != 0) ? delta / max : 0;
+		if(saturation != 0) {
+			var rr = (max - red) / delta;
+			var gr = (max - green) / delta;
+			var br = (max - blue) / delta;
+			if (red == max) hue = br - gr;
+			else if (green == max) hue = 2 + rr - br;
+			else hue = 4 + gr - rr;
+			hue /= 6;
+			if (hue < 0) hue++;
+		}
+		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
+	},
+
+	hsbToRgb: function(){
+		var br = Math.round(this[2] / 100 * 255);
+		if (this[1] == 0){
+			return [br, br, br];
+		} else {
+			var hue = this[0] % 360;
+			var f = hue % 60;
+			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
+			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
+			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
+			switch (Math.floor(hue / 60)){
+				case 0: return [br, t, p];
+				case 1: return [q, br, p];
+				case 2: return [p, br, t];
+				case 3: return [p, q, br];
+				case 4: return [t, p, br];
+				case 5: return [br, p, q];
+			}
+		}
+		return false;
+	}
+
+});
+
+String.implement({
+
+	rgbToHsb: function(){
+		var rgb = this.match(/\d{1,3}/g);
+		return (rgb) ? rgb.rgbToHsb() : null;
+	},
+
+	hsbToRgb: function(){
+		var hsb = this.match(/\d{1,3}/g);
+		return (hsb) ? hsb.hsbToRgb() : null;
+	}
+
+});
+/*
+---
+
+script: Group.js
+
+description: Class for monitoring collections of events
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Events
+ - /MooTools.More
+
+provides: [Group]
+
+...
+*/
+
+var Group = new Class({
+
+	initialize: function(){
+		this.instances = Array.flatten(arguments);
+		this.events = {};
+		this.checker = {};
+	},
+
+	addEvent: function(type, fn){
+		this.checker[type] = this.checker[type] || {};
+		this.events[type] = this.events[type] || [];
+		if (this.events[type].contains(fn)) return false;
+		else this.events[type].push(fn);
+		this.instances.each(function(instance, i){
+			instance.addEvent(type, this.check.bind(this, [type, instance, i]));
+		}, this);
+		return this;
+	},
+
+	check: function(type, instance, i){
+		this.checker[type][i] = true;
+		var every = this.instances.every(function(current, j){
+			return this.checker[type][j] || false;
+		}, this);
+		if (!every) return;
+		this.checker[type] = {};
+		this.events[type].each(function(event){
+			event.call(this, this.instances, instance);
+		}, this);
+	}
+
+});
+/*
+---
+
+script: Hash.Cookie.js
+
+description: Class for creating, reading, and deleting Cookies in JSON format.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Cookie
+ - core:1.2.4/JSON
+ - /MooTools.More
+
+provides: [Hash.Cookie]
+
+...
+*/
+
+Hash.Cookie = new Class({
+
+	Extends: Cookie,
+
+	options: {
+		autoSave: true
+	},
+
+	initialize: function(name, options){
+		this.parent(name, options);
+		this.load();
+	},
+
+	save: function(){
+		var value = JSON.encode(this.hash);
+		if (!value || value.length > 4096) return false; //cookie would be truncated!
+		if (value == '{}') this.dispose();
+		else this.write(value);
+		return true;
+	},
+
+	load: function(){
+		this.hash = new Hash(JSON.decode(this.read(), true));
+		return this;
+	}
+
+});
+
+Hash.each(Hash.prototype, function(method, name){
+	if (typeof method == 'function') Hash.Cookie.implement(name, function(){
+		var value = method.apply(this.hash, arguments);
+		if (this.options.autoSave) this.save();
+		return value;
+	});
+});/*
+---
+
+script: HtmlTable.js
+
+description: Builds table elements with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - /Class.Occlude
+
+provides: [HtmlTable]
+
+...
+*/
+
+var HtmlTable = new Class({
+
+	Implements: [Options, Events, Class.Occlude],
+
+	options: {
+		properties: {
+			cellpadding: 0,
+			cellspacing: 0,
+			border: 0
+		},
+		rows: [],
+		headers: [],
+		footers: []
+	},
+
+	property: 'HtmlTable',
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, table: Element.type});
+		this.setOptions(params.options);
+		this.element = params.table || new Element('table', this.options.properties);
+		if (this.occlude()) return this.occluded;
+		this.build();
+	},
+
+	build: function(){
+		this.element.store('HtmlTable', this);
+
+		this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element);
+		$$(this.body.rows);
+
+		if (this.options.headers.length) this.setHeaders(this.options.headers);
+		else this.thead = document.id(this.element.tHead);
+		if (this.thead) this.head = document.id(this.thead.rows[0]);
+
+		if (this.options.footers.length) this.setFooters(this.options.footers);
+		this.tfoot = document.id(this.element.tFoot);
+		if (this.tfoot) this.foot = document.id(this.thead.rows[0]);
+
+		this.options.rows.each(function(row){
+			this.push(row);
+		}, this);
+
+		['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){
+				this[method] = this.element[method].bind(this.element);
+		}, this);
+	},
+
+	toElement: function(){
+		return this.element;
+	},
+
+	empty: function(){
+		this.body.empty();
+		return this;
+	},
+
+	set: function(what, items) {
+		var target = (what == 'headers') ? 'tHead' : 'tFoot';
+		this[target.toLowerCase()] = (document.id(this.element[target]) || new Element(target.toLowerCase()).inject(this.element, 'top')).empty();
+		var data = this.push(items, {}, this[target.toLowerCase()], what == 'headers' ? 'th' : 'td');
+		if (what == 'headers') this.head = document.id(this.thead.rows[0]);
+		else this.foot = document.id(this.thead.rows[0]);
+		return data;
+	},
+
+	setHeaders: function(headers){
+		this.set('headers', headers);
+		return this;
+	},
+
+	setFooters: function(footers){
+		this.set('footers', footers);
+		return this;
+	},
+
+	push: function(row, rowProperties, target, tag){
+		var tds = row.map(function(data){
+			var td = new Element(tag || 'td', data.properties),
+				type = data.content || data || '',
+				element = document.id(type);
+			if($type(type) != 'string' && element) td.adopt(element);
+			else td.set('html', type);
+
+			return td;
+		});
+
+		return {
+			tr: new Element('tr', rowProperties).inject(target || this.body).adopt(tds),
+			tds: tds
+		};
+	}
+
+});
+/*
+---
+
+script: HtmlTable.Zebra.js
+
+description: Builds a stripy table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - /HtmlTable
+ - /Class.refactor
+
+provides: [HtmlTable.Zebra]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		classZebra: 'table-tr-odd',
+		zebra: true
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		if (this.options.zebra) this.updateZebras();
+	},
+
+	updateZebras: function(){
+		Array.each(this.body.rows, this.zebra, this);
+	},
+
+	zebra: function(row, i){
+		return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra);
+	},
+
+	push: function(){
+		var pushed = this.previous.apply(this, arguments);
+		if (this.options.zebra) this.updateZebras();
+		return pushed;
+	}
+
+});/*
+---
+
+script: HtmlTable.Sort.js
+
+description: Builds a stripy, sortable table with methods to add rows.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - core:1.2.4/Hash
+ - /HtmlTable
+ - /Class.refactor
+ - /Element.Delegation
+ - /Date
+
+provides: [HtmlTable.Sort]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {/*
+		onSort: $empty, */
+		sortIndex: 0,
+		sortReverse: false,
+		parsers: [],
+		defaultParser: 'string',
+		classSortable: 'table-sortable',
+		classHeadSort: 'table-th-sort',
+		classHeadSortRev: 'table-th-sort-rev',
+		classNoSort: 'table-th-nosort',
+		classGroupHead: 'table-tr-group-head',
+		classGroup: 'table-tr-group',
+		classCellSort: 'table-td-sort',
+		classSortSpan: 'table-th-sort-span',
+		sortable: false
+	},
+
+	initialize: function () {
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.sorted = {index: null, dir: 1};
+		this.bound = {
+			headClick: this.headClick.bind(this)
+		};
+		this.sortSpans = new Elements();
+		if (this.options.sortable) {
+			this.enableSort();
+			if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse);
+		}
+	},
+
+	attachSorts: function(attach){
+		this.element.removeEvents('click:relay(th)');
+		this.element[$pick(attach, true) ? 'addEvent' : 'removeEvent']('click:relay(th)', this.bound.headClick);
+	},
+
+	setHeaders: function(){
+		this.previous.apply(this, arguments);
+		if (this.sortEnabled) this.detectParsers();
+	},
+	
+	detectParsers: function(force){
+		if (!this.head) return;
+		var parsers = this.options.parsers, 
+				rows = this.body.rows;
+
+		// auto-detect
+		this.parsers = $$(this.head.cells).map(function(cell, index) {
+			if (!force && (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser'))) return cell.retrieve('htmltable-parser');
+			var thDiv = new Element('div');
+			$each(cell.childNodes, function(node) {
+				thDiv.adopt(node);
+			});
+			thDiv.inject(cell);
+			var sortSpan = new Element('span', {'html': '&#160;', 'class': this.options.classSortSpan}).inject(thDiv, 'top');
+			
+			this.sortSpans.push(sortSpan);
+
+			var parser = parsers[index], 
+					cancel;
+			switch ($type(parser)) {
+				case 'function': parser = {convert: parser}; cancel = true; break;
+				case 'string': parser = parser; cancel = true; break;
+			}
+			if (!cancel) {
+				HtmlTable.Parsers.some(function(current) {
+					var match = current.match;
+					if (!match) return false;
+					for (var i = 0, j = rows.length; i < j; i++) {
+						var text = $(rows[i].cells[index]).get('html').clean();
+						if (text && match.test(text)) {
+							parser = current;
+							return true;
+						}
+					}
+				});
+			}
+
+			if (!parser) parser = this.options.defaultParser;
+			cell.store('htmltable-parser', parser);
+			return parser;
+		}, this);
+	},
+
+	headClick: function(event, el) {
+		console.log(el);
+		if (!this.head || el.hasClass(this.options.classNoSort)) return;
+		var index = Array.indexOf(this.head.cells, el);
+		this.sort(index);
+		return false;
+	},
+
+	sort: function(index, reverse, pre) {
+		if (!this.head) return;
+		pre = !!(pre);
+		var classCellSort = this.options.classCellSort;
+		var classGroup = this.options.classGroup, 
+				classGroupHead = this.options.classGroupHead;
+
+		if (!pre) {
+			if (index != null) {
+				if (this.sorted.index == index) {
+					this.sorted.reverse = !(this.sorted.reverse);
+				} else {
+					if (this.sorted.index != null) {
+						this.sorted.reverse = false;
+						this.head.cells[this.sorted.index].removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev);
+					} else {
+						this.sorted.reverse = true;
+					}
+					this.sorted.index = index;
+				}
+			} else {
+				index = this.sorted.index;
+			}
+
+			if (reverse != null) this.sorted.reverse = reverse;
+
+			var head = document.id(this.head.cells[index]);
+			if (head) {
+				head.addClass(this.options.classHeadSort);
+				if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev);
+				else head.removeClass(this.options.classHeadSortRev);
+			}
+
+			this.body.getElements('td').removeClass(this.options.classCellSort);
+		}
+
+		var parser = this.parsers[index];
+		if ($type(parser) == 'string') parser = HtmlTable.Parsers.get(parser);
+		if (!parser) return;
+
+		if (!Browser.Engine.trident) {
+			var rel = this.body.getParent();
+			this.body.dispose();
+		}
+
+		var data = Array.map(this.body.rows, function(row, i) {
+			var value = parser.convert.call(document.id(row.cells[index]));
+
+			return {
+				position: i,
+				value: value,
+				toString:  function() {
+					return value.toString();
+				}
+			};
+		}, this);
+		data.reverse(true);
+
+		data.sort(function(a, b){
+			if (a.value === b.value) return 0;
+			return a.value > b.value ? 1 : -1;
+		});
+
+		if (!this.sorted.reverse) data.reverse(true);
+
+		var i = data.length, body = this.body;
+		var j, position, entry, group;
+
+		while (i) {
+			var item = data[--i];
+			position = item.position;
+			var row = body.rows[position];
+			if (row.disabled) continue;
+
+			if (!pre) {
+				if (group === item.value) {
+					row.removeClass(classGroupHead).addClass(classGroup);
+				} else {
+					group = item.value;
+					row.removeClass(classGroup).addClass(classGroupHead);
+				}
+				if (this.zebra) this.zebra(row, i);
+
+				row.cells[index].addClass(classCellSort);
+			}
+
+			body.appendChild(row);
+			for (j = 0; j < i; j++) {
+				if (data[j].position > position) data[j].position--;
+			}
+		};
+		data = null;
+		if (rel) rel.grab(body);
+
+		return this.fireEvent('sort', [body, index]);
+	},
+
+	reSort: function(){
+		if (this.sortEnabled) this.sort.call(this, this.sorted.index, this.sorted.reverse);
+		return this;
+	},
+
+	enableSort: function(){
+		this.element.addClass(this.options.classSortable);
+		this.attachSorts(true);
+		this.detectParsers();
+		this.sortEnabled = true;
+		return this;
+	},
+
+	disableSort: function(){
+		this.element.removeClass(this.options.classSortable);
+		this.attachSorts(false);
+		this.sortSpans.each(function(span) { span.destroy(); });
+		this.sortSpans.empty();
+		this.sortEnabled = false;
+		return this;
+	}
+
+});
+
+HtmlTable.Parsers = new Hash({
+
+	'date': {
+		match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/,
+		convert: function() {
+			return Date.parse(this.get('text')).format('db');
+		},
+		type: 'date'
+	},
+	'input-checked': {
+		match: / type="(radio|checkbox)" /,
+		convert: function() {
+			return this.getElement('input').checked;
+		}
+	},
+	'input-value': {
+		match: /<input/,
+		convert: function() {
+			return this.getElement('input').value;
+		}
+	},
+	'number': {
+		match: /^\d+[^\d.,]*$/,
+		convert: function() {
+			return this.get('text').toInt();
+		},
+		number: true
+	},
+	'numberLax': {
+		match: /^[^\d]+\d+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^0-9]/, '').toInt();
+		},
+		number: true
+	},
+	'float': {
+		match: /^[\d]+\.[\d]+/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '').toFloat();
+		},
+		number: true
+	},
+	'floatLax': {
+		match: /^[^\d]+[\d]+\.[\d]+$/,
+		convert: function() {
+			return this.get('text').replace(/[^-?^\d.]/, '');
+		},
+		number: true
+	},
+	'string': {
+		match: null,
+		convert: function() {
+			return this.get('text');
+		}
+	},
+	'title': {
+		match: null,
+		convert: function() {
+			return this.title;
+		}
+	}
+
+});
+
+/*
+---
+
+script: Keyboard.js
+
+description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+ - Aaron Newton
+ - Scott Kyle
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - /Log
+
+provides: [Keyboard]
+
+...
+*/
+
+(function(){
+	
+	var Keyboard = this.Keyboard = new Class({
+
+		Extends: Events,
+
+		Implements: [Options, Log],
+
+		options: {
+			/*
+			onActivate: $empty,
+			onDeactivate: $empty,
+			*/
+			defaultEventType: 'keydown',
+			active: false,
+			events: {},
+			nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged']
+		},
+
+		initialize: function(options){
+			this.setOptions(options);
+			this.setup();
+		}, 
+		setup: function(){
+			this.addEvents(this.options.events);
+			//if this is the root manager, nothing manages it
+			if (Keyboard.manager && !this.manager) Keyboard.manager.manage(this);
+			if (this.options.active) this.activate();
+		},
+
+		handle: function(event, type){
+			//Keyboard.stop(event) prevents key propagation
+			if (event.preventKeyboardPropagation) return;
+			
+			var bubbles = !!this.manager;
+			if (bubbles && this.activeKB){
+				this.activeKB.handle(event, type);
+				if (event.preventKeyboardPropagation) return;
+			}
+			this.fireEvent(type, event);
+			
+			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
+		},
+
+		addEvent: function(type, fn, internal){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal);
+		},
+
+		removeEvent: function(type, fn){
+			return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn);
+		},
+
+		toggleActive: function(){
+			return this[this.active ? 'deactivate' : 'activate']();
+		},
+
+		activate: function(instance){
+			if (instance) {
+				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
+				if (instance != this.activeKB) this.previous = this.activeKB;
+				//if we're enabling a child, assign it so that events are now passed to it
+				this.activeKB = instance.fireEvent('activate');
+				Keyboard.manager.fireEvent('changed');
+			} else if (this.manager) {
+				//else we're enabling ourselves, we must ask our parent to do it for us
+				this.manager.activate(this);
+			}
+			return this;
+		},
+
+		deactivate: function(instance){
+			if (instance) {
+				if(instance === this.activeKB) {
+					this.activeKB = null;
+					instance.fireEvent('deactivate');
+					Keyboard.manager.fireEvent('changed');
+				}
+			}
+			else if (this.manager) {
+				this.manager.deactivate(this);
+			}
+			return this;
+		},
+
+		relenquish: function(){
+			if (this.previous) this.activate(this.previous);
+		},
+
+		//management logic
+		manage: function(instance){
+			if (instance.manager) instance.manager.drop(instance);
+			this.instances.push(instance);
+			instance.manager = this;
+			if (!this.activeKB) this.activate(instance);
+			else this._disable(instance);
+		},
+
+		_disable: function(instance){
+			if (this.activeKB == instance) this.activeKB = null;
+		},
+
+		drop: function(instance){
+			this._disable(instance);
+			this.instances.erase(instance);
+		},
+
+		instances: [],
+
+		trace: function(){
+			Keyboard.trace(this);
+		},
+
+		each: function(fn){
+			Keyboard.each(this, fn);
+		}
+
+	});
+	
+	var parsed = {};
+	var modifiers = ['shift', 'control', 'alt', 'meta'];
+	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
+	
+	Keyboard.parse = function(type, eventType, ignore){
+		if (ignore && ignore.contains(type.toLowerCase())) return type;
+		
+		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
+			eventType = $1;
+			return '';
+		});
+
+		if (!parsed[type]){
+			var key, mods = {};
+			type.split('+').each(function(part){
+				if (regex.test(part)) mods[part] = true;
+				else key = part;
+			});
+
+			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
+			
+			var keys = [];
+			modifiers.each(function(mod){
+				if (mods[mod]) keys.push(mod);
+			});
+			
+			if (key) keys.push(key);
+			parsed[type] = keys.join('+');
+		}
+
+		return eventType + ':' + parsed[type];
+	};
+
+	Keyboard.each = function(keyboard, fn){
+		var current = keyboard || Keyboard.manager;
+		while (current){
+			fn.run(current);
+			current = current.activeKB;
+		}
+	};
+
+	Keyboard.stop = function(event){
+		event.preventKeyboardPropagation = true;
+	};
+
+	Keyboard.manager = new Keyboard({
+		active: true
+	});
+	
+	Keyboard.trace = function(keyboard){
+		keyboard = keyboard || Keyboard.manager;
+		keyboard.enableLog();
+		keyboard.log('the following items have focus: ');
+		Keyboard.each(keyboard, function(current){
+			keyboard.log(document.id(current.widget) || current.wiget || current);
+		});
+	};
+	
+	var handler = function(event){
+		var keys = [];
+		modifiers.each(function(mod){
+			if (event[mod]) keys.push(mod);
+		});
+		
+		if (!regex.test(event.key)) keys.push(event.key);
+		Keyboard.manager.handle(event, event.type + ':' + keys.join('+'));
+	};
+	
+	document.addEvents({
+		'keyup': handler,
+		'keydown': handler
+	});
+
+	Event.Keys.extend({
+		'shift': 16,
+		'control': 17,
+		'alt': 18,
+		'capslock': 20,
+		'pageup': 33,
+		'pagedown': 34,
+		'end': 35,
+		'home': 36,
+		'numlock': 144,
+		'scrolllock': 145,
+		';': 186,
+		'=': 187,
+		',': 188,
+		'-': Browser.Engine.Gecko ? 109 : 189,
+		'.': 190,
+		'/': 191,
+		'`': 192,
+		'[': 219,
+		'\\': 220,
+		']': 221,
+		"'": 222
+	});
+
+})();
+/*
+---
+
+script: HtmlTable.Select.js
+
+description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation.
+
+license: MIT-style license
+
+authors:
+ - Harald Kirschner
+ - Aaron Newton
+
+requires:
+ - /Keyboard
+ - /HtmlTable
+ - /Class.refactor
+ - /Element.Delegation
+
+provides: [HtmlTable.Select]
+
+...
+*/
+
+HtmlTable = Class.refactor(HtmlTable, {
+
+	options: {
+		/*onRowFocus: $empty,
+		onRowUnfocus: $empty,*/
+		useKeyboard: true,
+		classRowSelected: 'table-tr-selected',
+		classRowHovered: 'table-tr-hovered',
+		classSelectable: 'table-selectable',
+		allowMultiSelect: true,
+		selectable: false
+	},
+
+	initialize: function(){
+		this.previous.apply(this, arguments);
+		if (this.occluded) return this.occluded;
+		this.selectedRows = new Elements();
+		this.bound = {
+			mouseleave: this.mouseleave.bind(this),
+			focusRow: this.focusRow.bind(this)
+		};
+		if (this.options.selectable) this.enableSelect();
+	},
+
+	enableSelect: function(){
+		this.selectEnabled = true;
+		this.attachSelects();
+		this.element.addClass(this.options.classSelectable);
+	},
+
+	disableSelect: function(){
+		this.selectEnabled = false;
+		this.attach(false);
+		this.element.removeClass(this.options.classSelectable);
+	},
+
+	attachSelects: function(attach){
+		attach = $pick(attach, true);
+		var method = attach ? 'addEvents' : 'removeEvents';
+		this.element[method]({
+			mouseleave: this.bound.mouseleave
+		});
+		this.body[method]({
+			'click:relay(tr)': this.bound.focusRow
+		});
+		if (this.options.useKeyboard || this.keyboard){
+			if (!this.keyboard) this.keyboard = new Keyboard({
+				events: {
+					down: function(e) {
+						e.preventDefault();
+						this.shiftFocus(1);
+					}.bind(this),
+					up: function(e) {
+						e.preventDefault();
+						this.shiftFocus(-1);
+					}.bind(this),
+					enter: function(e) {
+						e.preventDefault();
+						if (this.hover) this.focusRow(this.hover);
+					}.bind(this)
+				},
+				active: true
+			});
+			this.keyboard[attach ? 'activate' : 'deactivate']();
+		}
+		this.updateSelects();
+	},
+
+	mouseleave: function(){
+		if (this.hover) this.leaveRow(this.hover);
+	},
+
+	focus: function(){
+		if (this.keyboard) this.keyboard.activate();
+	},
+
+	blur: function(){
+		if (this.keyboard) this.keyboard.deactivate();
+	},
+
+	push: function(){
+		var ret = this.previous.apply(this, arguments);
+		this.updateSelects();
+		return ret;
+	},
+
+	updateSelects: function(){
+		Array.each(this.body.rows, function(row){
+			var binders = row.retrieve('binders');
+			if ((binders && this.selectEnabled) || (!binders && !this.selectEnabled)) return;
+			if (!binders){
+				binders = {
+					mouseenter: this.enterRow.bind(this, [row]),
+					mouseleave: this.leaveRow.bind(this, [row])
+				};
+				row.store('binders', binders).addEvents(binders);
+			} else {
+				row.removeEvents(binders);
+			}
+		}, this);
+	},
+
+	enterRow: function(row){
+		if (this.hover) this.hover = this.leaveRow(this.hover);
+		this.hover = row.addClass(this.options.classRowHovered);
+	},
+
+	shiftFocus: function(offset){
+		if (!this.hover) return this.enterRow(this.body.rows[0]);
+		var to = Array.indexOf(this.body.rows, this.hover) + offset;
+		if (to < 0) to = 0;
+		if (to >= this.body.rows.length) to = this.body.rows.length - 1;
+		if (this.hover == this.body.rows[to]) return this;
+		this.enterRow(this.body.rows[to]);
+	},
+
+	leaveRow: function(row){
+		row.removeClass(this.options.classRowHovered);
+	},
+
+	focusRow: function(){
+		var row = arguments[1] || arguments[0]; //delegation passes the event first
+		if (!this.body.getChildren().contains(row)) return;
+		var unfocus = function(row){
+			this.selectedRows.erase(row);
+			row.removeClass(this.options.classRowSelected);
+			this.fireEvent('rowUnfocus', [row, this.selectedRows]);
+		}.bind(this);
+		if (!this.options.allowMultiSelect) this.selectedRows.each(unfocus);
+		if (!this.selectedRows.contains(row)) {
+			this.selectedRows.push(row);
+			row.addClass(this.options.classRowSelected);
+			this.fireEvent('rowFocus', [row, this.selectedRows]);
+		} else {
+			unfocus(row);
+		}
+		return false;
+	},
+
+	selectAll: function(status){
+		status = $pick(status, true);
+		if (!this.options.allowMultiSelect && status) return;
+		if (!status) this.selectedRows.removeClass(this.options.classRowSelected).empty();
+		else this.selectedRows.combine(this.body.rows).addClass(this.options.classRowSelected);
+		return this;
+	},
+
+	selectNone: function(){
+		return this.selectAll(false);
+	}
+
+});
+/*
+---
+
+script: Keyboard.js
+
+description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys.
+
+license: MIT-style license
+
+authors:
+ - Perrin Westrich
+
+requires:
+ - core:1.2.4/Function
+ - /Keyboard.Extras
+
+provides: [Keyboard.Extras]
+
+...
+*/
+Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']);
+
+Keyboard.implement({
+
+	/*
+		shortcut should be in the format of:
+		{
+			'keys': 'shift+s', // the default to add as an event.
+			'description': 'blah blah blah', // a brief description of the functionality.
+			'handler': function(){} // the event handler to run when keys are pressed.
+		}
+	*/
+	addShortcut: function(name, shortcut) {
+		this.shortcuts = this.shortcuts || [];
+		this.shortcutIndex = this.shortcutIndex || {};
+		
+		shortcut.getKeyboard = $lambda(this);
+		shortcut.name = name;
+		this.shortcutIndex[name] = shortcut;
+		this.shortcuts.push(shortcut);
+		if(shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler);
+		return this;
+	},
+
+	addShortcuts: function(obj){
+		for(var name in obj) this.addShortcut(name, obj[name]);
+		return this;
+	},
+
+	getShortcuts: function(){
+		return this.shortcuts || [];
+	},
+
+	getShortcut: function(name){
+		return (this.shortcutIndex || {})[name];
+	}
+
+});
+
+Keyboard.rebind = function(newKeys, shortcuts){
+	$splat(shortcuts).each(function(shortcut){
+		shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler);
+		shortcut.getKeyboard().addEvent(newKeys, shortcut.handler);
+		shortcut.keys = newKeys;
+		shortcut.getKeyboard().fireEvent('rebound');
+	});
+};
+
+
+Keyboard.getActiveShortcuts = function(keyboard) {
+	var activeKBS = [], activeSCS = [];
+	Keyboard.each(keyboard, [].push.bind(activeKBS));
+	activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); });
+	return activeSCS;
+};
+
+Keyboard.getShortcut = function(name, keyboard, opts){
+	opts = opts || {};
+	var shortcuts = opts.many ? [] : null,
+		set = opts.many ? function(kb){
+				var shortcut = kb.getShortcut(name);
+				if(shortcut) shortcuts.push(shortcut);
+			} : function(kb) { 
+				if(!shortcuts) shortcuts = kb.getShortcut(name);
+			};
+	Keyboard.each(keyboard, set);
+	return shortcuts;
+};
+
+Keyboard.getShortcuts = function(name, keyboard) {
+	return Keyboard.getShortcut(name, keyboard, { many: true });
+};
+/*
+---
+
+script: Scroller.js
+
+description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+
+requires:
+ - core:1.2.4/Events
+ - core:1.2.4/Options
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Dimensions
+
+provides: [Scroller]
+
+...
+*/
+
+var Scroller = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		area: 20,
+		velocity: 1,
+		onChange: function(x, y){
+			this.element.scrollTo(x, y);
+		},
+		fps: 50
+	},
+
+	initialize: function(element, options){
+		this.setOptions(options);
+		this.element = document.id(element);
+		this.docBody = document.id(this.element.getDocument().body);
+		this.listener = ($type(this.element) != 'element') ?  this.docBody : this.element;
+		this.timer = null;
+		this.bound = {
+			attach: this.attach.bind(this),
+			detach: this.detach.bind(this),
+			getCoords: this.getCoords.bind(this)
+		};
+	},
+
+	start: function(){
+		this.listener.addEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+	},
+
+	stop: function(){
+		this.listener.removeEvents({
+			mouseover: this.bound.attach,
+			mouseout: this.bound.detach
+		});
+		this.detach();
+		this.timer = $clear(this.timer);
+	},
+
+	attach: function(){
+		this.listener.addEvent('mousemove', this.bound.getCoords);
+	},
+
+	detach: function(){
+		this.listener.removeEvent('mousemove', this.bound.getCoords);
+		this.timer = $clear(this.timer);
+	},
+
+	getCoords: function(event){
+		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
+		if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
+	},
+
+	scroll: function(){
+		var size = this.element.getSize(), 
+			scroll = this.element.getScroll(), 
+			pos = this.element != this.docBody ? this.element.getOffsets() : {x: 0, y:0}, 
+			scrollSize = this.element.getScrollSize(), 
+			change = {x: 0, y: 0};
+		for (var z in this.page){
+			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0) {
+				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
+			} else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]) {
+				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
+			}
+		}
+		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
+	}
+
+});/*
+---
+
+script: Tips.js
+
+description: Class for creating nice tips that follow the mouse cursor when hovering an element.
+
+license: MIT-style license
+
+authors:
+ - Valerio Proietti
+ - Christoph Pojer
+
+requires:
+ - core:1.2.4/Options
+ - core:1.2.4/Events
+ - core:1.2.4/Element.Event
+ - core:1.2.4/Element.Style
+ - core:1.2.4/Element.Dimensions
+ - /MooTools.More
+
+provides: [Tips]
+
+...
+*/
+
+(function(){
+
+var read = function(option, element){
+	return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
+};
+
+this.Tips = new Class({
+
+	Implements: [Events, Options],
+
+	options: {
+		/*
+		onAttach: $empty(element),
+		onDetach: $empty(element),
+		*/
+		onShow: function(){
+			this.tip.setStyle('display', 'block');
+		},
+		onHide: function(){
+			this.tip.setStyle('display', 'none');
+		},
+		title: 'title',
+		text: function(element){
+			return element.get('rel') || element.get('href');
+		},
+		showDelay: 100,
+		hideDelay: 100,
+		className: 'tip-wrap',
+		offset: {x: 16, y: 16},
+		windowPadding: {x:0, y:0},
+		fixed: false
+	},
+
+	initialize: function(){
+		var params = Array.link(arguments, {options: Object.type, elements: $defined});
+		this.setOptions(params.options);
+		if (params.elements) this.attach(params.elements);
+		this.container = new Element('div', {'class': 'tip'});
+	},
+
+	toElement: function(){
+		if (this.tip) return this.tip;
+
+		return this.tip = new Element('div', {
+			'class': this.options.className,
+			styles: {
+				position: 'absolute',
+				top: 0,
+				left: 0
+			}
+		}).adopt(
+			new Element('div', {'class': 'tip-top'}),
+			this.container,
+			new Element('div', {'class': 'tip-bottom'})
+		).inject(document.body);
+	},
+
+	attach: function(elements){
+		$$(elements).each(function(element){
+			var title = read(this.options.title, element),
+				text = read(this.options.text, element);
+			
+			element.erase('title').store('tip:native', title).retrieve('tip:title', title);
+			element.retrieve('tip:text', text);
+			this.fireEvent('attach', [element]);
+			
+			var events = ['enter', 'leave'];
+			if (!this.options.fixed) events.push('move');
+			
+			events.each(function(value){
+				var event = element.retrieve('tip:' + value);
+				if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
+				
+				element.store('tip:' + value, event).addEvent('mouse' + value, event);
+			}, this);
+		}, this);
+		
+		return this;
+	},
+
+	detach: function(elements){
+		$$(elements).each(function(element){
+			['enter', 'leave', 'move'].each(function(value){
+				element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
+			});
+			
+			this.fireEvent('detach', [element]);
+			
+			if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
+				var original = element.retrieve('tip:native');
+				if (original) element.set('title', original);
+			}
+		}, this);
+		
+		return this;
+	},
+
+	elementEnter: function(event, element){
+		this.container.empty();
+		
+		['title', 'text'].each(function(value){
+			var content = element.retrieve('tip:' + value);
+			if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
+		}, this);
+		
+		$clear(this.timer);
+		this.timer = (function(){
+			this.show(this, element);
+			this.position((this.options.fixed) ? {page: element.getPosition()} : event);
+		}).delay(this.options.showDelay, this);
+	},
+
+	elementLeave: function(event, element){
+		$clear(this.timer);
+		this.timer = this.hide.delay(this.options.hideDelay, this, element);
+		this.fireForParent(event, element);
+	},
+
+	fireForParent: function(event, element){
+		element = element.getParent();
+		if (!element || element == document.body) return;
+		if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event);
+		else this.fireForParent(event, element);
+	},
+
+	elementMove: function(event, element){
+		this.position(event);
+	},
+
+	position: function(event){
+		if (!this.tip) document.id(this);
+
+		var size = window.getSize(), scroll = window.getScroll(),
+			tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
+			props = {x: 'left', y: 'top'},
+			obj = {};
+		
+		for (var z in props){
+			obj[props[z]] = event.page[z] + this.options.offset[z];
+			if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
+		}
+		
+		this.tip.setStyles(obj);
+	},
+
+	fill: function(element, contents){
+		if(typeof contents == 'string') element.set('html', contents);
+		else element.adopt(contents);
+	},
+
+	show: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('show', [this.tip, element]);
+	},
+
+	hide: function(element){
+		if (!this.tip) document.id(this);
+		this.fireEvent('hide', [this.tip, element]);
+	}
+
+});
+
+})();/*
+---
+
+script: Date.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Alfons Sanchez
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Date', {
+
+	months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'],
+	days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	AM: 'AM',
+	PM: 'PM',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'fa menys d`un minut',
+	minuteAgo: 'fa un minut',
+	minutesAgo: 'fa {delta} minuts',
+	hourAgo: 'fa un hora',
+	hoursAgo: 'fa unes {delta} hores',
+	dayAgo: 'fa un dia',
+	daysAgo: 'fa {delta} dies',
+	lessThanMinuteUntil: 'menys d`un minut des d`ara',
+	minuteUntil: 'un minut des d`ara',
+	minutesUntil: '{delta} minuts des d`ara',
+	hourUntil: 'un hora des d`ara',
+	hoursUntil: 'unes {delta} hores des d`ara',
+	dayUntil: '1 dia des d`ara',
+	daysUntil: '{delta} dies des d`ara'
+
+});/*
+---
+
+script: Date.Czech.js
+
+description: Date messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan Černý chemiX
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Date', {
+
+	months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
+	days: ['Neděle', 'Pondělí', 'Úterý', 'Středa', 'Čtvrtek', 'Pátek', 'Sobota'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+	AM: 'dop.',
+	PM: 'odp.',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return '.';
+	},
+
+    // TODO : in examples use and fix it
+	lessThanMinuteAgo: 'méně než minutou',
+	minuteAgo: 'přibližně před minutou',
+	minutesAgo: 'před {delta} minutami',
+	hourAgo: 'přibližně před hodinou',
+	hoursAgo: 'před {delta} hodinami',
+	dayAgo: 'před dnem',
+	daysAgo: 'před {delta} dni',
+	lessThanMinuteUntil: 'před méně než minutou',
+	minuteUntil: 'asi před minutou',
+	minutesUntil: ' asi před {delta} minutami',
+	hourUntil: 'asi před hodinou',
+	hoursUntil: 'před {delta} hodinami',
+	dayUntil: 'před dnem',
+	daysUntil: 'před {delta} dni',
+	weekUntil: 'před týdnem',
+	weeksUntil: 'před {delta} týdny',
+	monthUntil: 'před měsícem',
+	monthsUntil: 'před {delta} měsíci',
+	yearUntil: 'před rokem',
+	yearsUntil: 'před {delta} lety'
+
+});
+/*
+---
+
+script: Date.Danish.js
+
+description: Date messages for Danish.
+
+license: MIT-style license
+
+authors:
+ - Martin Overgaard
+ - Henrik Hansen
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Danish]
+
+...
+*/
+ 
+MooTools.lang.set('da-DK', 'Date', {
+
+	months: ['Januar', 'Februa', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
+	days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d-%m-%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+	  //1st, 2nd, 3rd, etc.
+	  return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mindre end et minut siden',
+	minuteAgo: 'omkring et minut siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omkring en time siden',
+	hoursAgo: 'omkring {delta} timer siden',
+	dayAgo: '1 dag siden',
+	daysAgo: '{delta} dage siden',
+	weekAgo: '1 uge siden',
+	weeksAgo: '{delta} uger siden',
+	monthAgo: '1 måned siden',
+	monthsAgo: '{delta} måneder siden',
+	yearthAgo: '1 år siden',
+	yearsAgo: '{delta} år siden',
+	lessThanMinuteUntil: 'mindre end et minut fra nu',
+	minuteUntil: 'omkring et minut fra nu',
+	minutesUntil: '{delta} minutter fra nu',
+	hourUntil: 'omkring en time fra nu',
+	hoursUntil: 'omkring {delta} timer fra nu',
+	dayUntil: '1 dag fra nu',
+	daysUntil: '{delta} dage fra nu',
+	weekUntil: '1 uge fra nu',
+	weeksUntil: '{delta} uger fra nu',
+	monthUntil: '1 måned fra nu',
+	monthsUntil: '{delta} måneder fra nu',
+	yearUntil: '1 år fra nu',
+	yearsUntil: '{delta} år fra nu'
+
+});
+/*
+---
+
+script: Date.Dutch.js
+
+description: Date messages in Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Date', {
+
+	months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
+	days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: 'e',
+
+	lessThanMinuteAgo: 'minder dan een minuut geleden',
+	minuteAgo: 'ongeveer een minuut geleden',
+	minutesAgo: 'minuten geleden',
+	hourAgo: 'ongeveer een uur geleden',
+	hoursAgo: 'ongeveer {delta} uur geleden',
+	dayAgo: '{delta} dag geleden',
+	daysAgo: 'dagen geleden',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+	lessThanMinuteUntil: 'minder dan een minuut vanaf nu',
+	minuteUntil: 'ongeveer een minuut vanaf nu',
+	minutesUntil: '{delta} minuten vanaf nu',
+	hourUntil: 'ongeveer een uur vanaf nu',
+	hoursUntil: 'ongeveer {delta} uur vanaf nu',
+	dayUntil: '1 dag vanaf nu',
+	daysUntil: '{delta} dagen vanaf nu',
+	weekAgo: 'een week geleden',
+	weeksAgo: '{delta} weken geleden',
+	monthAgo: 'een maand geleden',
+	monthsAgo: '{delta} maanden geleden',
+	yearthAgo: 'een jaar geleden',
+	yearsAgo: '{delta} jaar geleden',
+
+	weekUntil: 'over een week',
+	weeksUntil: 'over {delta} weken',
+	monthUntil: 'over een maand',
+	monthsUntil: 'over {delta} maanden',
+	yearUntil: 'over een jaar',
+	yearsUntil: 'over {delta} jaar' 
+
+});/*
+---
+
+script: Date.English.GB.js
+
+description: Date messages for British English.
+
+license: MIT-style license
+
+authors:
+ - Aaron Newton
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.English.GB]
+
+...
+*/
+
+MooTools.lang.set('en-GB', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+	
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M'
+
+}).set('cascade', ['en-US']);/*
+---
+
+script: Date.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Date', {
+
+	months: ['jaanuar', 'veebruar', 'märts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'],
+	days: ['pühapäev', 'esmaspäev', 'teisipäev', 'kolmapäev', 'neljapäev', 'reede', 'laupäev'],
+	//culture's date order: MM.DD.YYYY
+	dateOrder: ['month', 'date', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%m.%d.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'vähem kui minut aega tagasi',
+	minuteAgo: 'umbes minut aega tagasi',
+	minutesAgo: '{delta} minutit tagasi',
+	hourAgo: 'umbes tund aega tagasi',
+	hoursAgo: 'umbes {delta} tundi tagasi',
+	dayAgo: '1 päev tagasi',
+	daysAgo: '{delta} päeva tagasi',
+	weekAgo: '1 nädal tagasi',
+	weeksAgo: '{delta} nädalat tagasi',
+	monthAgo: '1 kuu tagasi',
+	monthsAgo: '{delta} kuud tagasi',
+	yearAgo: '1 aasta tagasi',
+	yearsAgo: '{delta} aastat tagasi',
+	lessThanMinuteUntil: 'vähem kui minuti aja pärast',
+	minuteUntil: 'umbes minuti aja pärast',
+	minutesUntil: '{delta} minuti pärast',
+	hourUntil: 'umbes tunni aja pärast',
+	hoursUntil: 'umbes {delta} tunni pärast',
+	dayUntil: '1 päeva pärast',
+	daysUntil: '{delta} päeva pärast',
+	weekUntil: '1 nädala pärast',
+	weeksUntil: '{delta} nädala pärast',
+	monthUntil: '1 kuu pärast',
+	monthsUntil: '{delta} kuu pärast',
+	yearUntil: '1 aasta pärast',
+	yearsUntil: '{delta} aasta pärast'
+
+});/*
+---
+
+script: Date.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors:
+ - Christoph Pojer
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Date', {
+
+	months: ['Januar', 'Februar', 'M&auml;rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
+	days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: [ 'date', 'month', 'year', '.'],
+
+	AM: 'vormittags',
+	PM: 'nachmittags',
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '.',
+
+	lessThanMinuteAgo: 'Vor weniger als einer Minute',
+	minuteAgo: 'Vor einer Minute',
+	minutesAgo: 'Vor {delta} Minuten',
+	hourAgo: 'Vor einer Stunde',
+	hoursAgo: 'Vor {delta} Stunden',
+	dayAgo: 'Vor einem Tag',
+	daysAgo: 'Vor {delta} Tagen',
+	weekAgo: 'Vor einer Woche',
+	weeksAgo: 'Vor {delta} Wochen',
+	monthAgo: 'Vor einem Monat',
+	monthsAgo: 'Vor {delta} Monaten',
+	yearAgo: 'Vor einem Jahr',
+	yearsAgo: 'Vor {delta} Jahren',
+	lessThanMinuteUntil: 'In weniger als einer Minute',
+	minuteUntil: 'In einer Minute',
+	minutesUntil: 'In {delta} Minuten',
+	hourUntil: 'In ca. einer Stunde',
+	hoursUntil: 'In ca. {delta} Stunden',
+	dayUntil: 'In einem Tag',
+	daysUntil: 'In {delta} Tagen',
+	weekUntil: 'In einer Woche',
+	weeksUntil: 'In {delta} Wochen',
+	monthUntil: 'In einem Monat',
+	monthsUntil: 'In {delta} Monaten',
+	yearUntil: 'In einem Jahr',
+	yearsUntil: 'In {delta} Jahren'
+});/*
+---
+
+script: Date.German.CH.js
+
+description: Date messages for German (Switzerland).
+
+license: MIT-style license
+
+authors:
+ - Michael van der Weg
+
+requires:
+ - /Lang
+ - /Date.German
+
+provides: [Date.German.CH]
+
+...
+*/
+
+MooTools.lang.set('de-CH', 'cascade', ['de-DE']);/*
+---
+
+script: Date.French.js
+
+description: Date messages in French.
+
+license: MIT-style license
+
+authors:
+ - Nicolas Sorosac
+ - Antoine Abt
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Date', {
+
+	months: ['janvier', 'f&eacute;vrier', 'mars', 'avril', 'mai', 'juin', 'juillet', 'ao&ucirc;t', 'septembre', 'octobre', 'novembre', 'd&eacute;cembre'],
+	days: ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'],
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	getOrdinal: function(dayOfMonth){
+	  return (dayOfMonth > 1) ? '' : 'er';
+	},
+
+	lessThanMinuteAgo: 'il y a moins d\'une minute',
+	minuteAgo: 'il y a une minute',
+	minutesAgo: 'il y a {delta} minutes',
+	hourAgo: 'il y a une heure',
+	hoursAgo: 'il y a {delta} heures',
+	dayAgo: 'il y a un jour',
+	daysAgo: 'il y a {delta} jours',
+	weekAgo: 'il y a une semaine',
+	weeksAgo: 'il y a {delta} semaines',
+	monthAgo: 'il y a 1 mois',
+	monthsAgo: 'il y a {delta} mois',
+	yearthAgo: 'il y a 1 an',
+	yearsAgo: 'il y a {delta} ans',
+	lessThanMinuteUntil: 'dans moins d\'une minute',
+	minuteUntil: 'dans une minute',
+	minutesUntil: 'dans {delta} minutes',
+	hourUntil: 'dans une heure',
+	hoursUntil: 'dans {delta} heures',
+	dayUntil: 'dans un jour',
+	daysUntil: 'dans {delta} jours',
+	weekUntil: 'dans 1 semaine',
+	weeksUntil: 'dans {delta} semaines',
+	monthUntil: 'dans 1 mois',
+	monthsUntil: 'dans {delta} mois',
+	yearUntil: 'dans 1 an',
+	yearsUntil: 'dans {delta} ans'
+
+});
+/*
+---
+
+script: Date.Italian.js
+
+description: Date messages for Italian.
+
+license: MIT-style license.
+
+authors:
+ - Andrea Novero
+ - Valerio Proietti
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Date', {
+ 
+	months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
+	days: ['Domenica', 'Luned&igrave;', 'Marted&igrave;', 'Mercoled&igrave;', 'Gioved&igrave;', 'Venerd&igrave;', 'Sabato'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H.%M',
+
+	/* Date.Extras */
+	ordinal: '&ordm;',
+
+	lessThanMinuteAgo: 'meno di un minuto fa',
+	minuteAgo: 'circa un minuto fa',
+	minutesAgo: 'circa {delta} minuti fa',
+	hourAgo: 'circa un\'ora fa',
+	hoursAgo: 'circa {delta} ore fa',
+	dayAgo: 'circa 1 giorno fa',
+	daysAgo: 'circa {delta} giorni fa',
+	lessThanMinuteUntil: 'tra meno di un minuto',
+	minuteUntil: 'tra circa un minuto',
+	minutesUntil: 'tra circa {delta} minuti',
+	hourUntil: 'tra circa un\'ora',
+	hoursUntil: 'tra circa {delta} ore',
+	dayUntil: 'tra circa un giorno',
+	daysUntil: 'tra circa {delta} giorni'
+
+});/*
+---
+
+script: Date.Norwegian.js
+
+description: Date messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Date', {
+
+	dateOrder: ['date', 'month', 'year'],
+
+	shortDate: '%d.%m.%Y',
+	shortTime: '%H:%M',
+
+	lessThanMinuteAgo: 'kortere enn et minutt siden',
+	minuteAgo: 'omtrent et minutt siden',
+	minutesAgo: '{delta} minutter siden',
+	hourAgo: 'omtrent en time siden',
+	hoursAgo: 'omtrent {delta} timer siden',
+	dayAgo: '{delta} dag siden',
+	daysAgo: '{delta} dager siden'
+
+});/*
+---
+
+script: Date.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Date', {
+	months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
+	days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
+	dateOrder: ['year', 'month', 'date'],
+	AM: 'nad ranem',
+	PM: 'po południu',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)];
+	},
+
+	lessThanMinuteAgo: 'mniej niż minute temu',
+	minuteAgo: 'około minutę temu',
+	minutesAgo: '{delta} minut temu',
+	hourAgo: 'około godzinę temu',
+	hoursAgo: 'około {delta} godzin temu',
+	dayAgo: 'Wczoraj',
+	daysAgo: '{delta} dni temu',
+	lessThanMinuteUntil: 'za niecałą minutę',
+	minuteUntil: 'za około minutę',
+	minutesUntil: 'za {delta} minut',
+	hourUntil: 'za około godzinę',
+	hoursUntil: 'za około {delta} godzin',
+	dayUntil: 'za 1 dzień',
+	daysUntil: 'za {delta} dni'
+});/*
+---
+
+script: Date.Portuguese.BR.js
+
+description: Date messages in Portuguese-BR (Brazil).
+
+license: MIT-style license
+
+authors:
+ - Fabio Miranda Costa
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Date', {
+
+	months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
+	days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'],
+	//culture's date order: DD/MM/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		//1º, 2º, 3º, etc.
+    	return '&ordm;';
+	},
+
+	lessThanMinuteAgo: 'há menos de um minuto',
+	minuteAgo: 'há cerca de um minuto',
+	minutesAgo: 'há {delta} minutos',
+	hourAgo: 'há cerca de uma hora',
+	hoursAgo: 'há cerca de {delta} horas',
+	dayAgo: 'há um dia',
+	daysAgo: 'há {delta} dias',
+    weekAgo: 'há uma semana',
+	weeksAgo: 'há {delta} semanas',
+	monthAgo: 'há um mês',
+	monthsAgo: 'há {delta} meses',
+	yearAgo: 'há um ano',
+	yearsAgo: 'há {delta} anos',
+	lessThanMinuteUntil: 'em menos de um minuto',
+	minuteUntil: 'em um minuto',
+	minutesUntil: 'em {delta} minutos',
+	hourUntil: 'em uma hora',
+	hoursUntil: 'em {delta} horas',
+	dayUntil: 'em um dia',
+	daysUntil: 'em {delta} dias',
+	weekUntil: 'em uma semana',
+	weeksUntil: 'em {delta} semanas',
+	monthUntil: 'em um mês',
+	monthsUntil: 'em {delta} meses',
+	yearUntil: 'em um ano',
+	yearsUntil: 'em {delta} anos'
+
+});/*
+Script: Date.Russian.js
+	Date messages for Russian.
+
+	License:
+		MIT-style license.
+
+	Authors:
+		Evstigneev Pavel
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Date', {
+
+	months: ['Январь', 'Февраль', 'Март', 'Ð?прель', 'Май', 'Июнь', 'Июль', 'Ð?вгуÑ?Ñ‚', 'СентÑ?брь', 'ОктÑ?брь', 'Ð?оÑ?брь', 'Декабрь'],
+	days: ['ВоÑ?креÑ?енье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'ПÑ?тница', 'Суббота'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+
+  /*
+   *  Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/
+   *
+   *  one -> n mod 10 is 1 and n mod 100 is not 11;
+   *  few -> n mod 10 in 2..4 and n mod 100 not in 12..14;
+   *  many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14;
+   *  other -> everything else (example 3.14)
+   */
+
+  pluralize: function (n, one, few, many, other) {
+    var modulo10 = n % 10
+    var modulo100 = n % 100
+
+    if (modulo10 == 1 && modulo100 != 11) {
+      return one;
+    } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return few;
+    } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)) {
+      return many;
+    } else {
+      return other;
+    }
+  },
+
+	/* Date.Extras */
+	ordinal: '',
+	lessThanMinuteAgo: 'меньше минуты назад',
+	minuteAgo: 'минута назад',
+	minutesAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'минута', 'минуты', 'минут') + ' назад'},
+	hourAgo: 'чаÑ? назад',
+	hoursAgo: function (delta) { return  '{delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ' назад'},
+	dayAgo: 'вчера',
+	daysAgo: function (delta) { return '{delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + ' назад' },
+	lessThanMinuteUntil: 'меньше минуты назад',
+	minuteUntil: 'через минуту',
+	minutesUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	hourUntil: 'через чаÑ?',
+	hoursUntil: function (delta) { return  'через {delta} ' + this.pluralize(delta, 'чаÑ?', 'чаÑ?а', 'чаÑ?ов') + ''},
+	dayUntil: 'завтра',
+	daysUntil: function (delta) { return 'через {delta} ' + this.pluralize(delta, 'день', 'днÑ?', 'дней') + '' }
+
+});/*
+---
+
+script: Date.Spanish.US.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Date', {
+
+	months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
+	days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'],
+	//culture's date order: MM/DD/YYYY
+	dateOrder: ['date', 'month', 'year'],
+	AM: 'AM',
+	PM: 'PM',
+
+	shortDate: '%d/%m/%Y',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: '',
+
+	lessThanMinuteAgo: 'hace menos de un minuto',
+	minuteAgo: 'hace un minuto',
+	minutesAgo: 'hace {delta} minutos',
+	hourAgo: 'hace una hora',
+	hoursAgo: 'hace unas {delta} horas',
+	dayAgo: 'hace un día',
+	daysAgo: 'hace {delta} días',
+	weekAgo: 'hace una semana',
+	weeksAgo: 'hace unas {delta} semanas',
+	monthAgo: 'hace un mes',
+	monthsAgo: 'hace {delta} meses',
+	yearAgo: 'hace un año',
+	yearsAgo: 'hace {delta} años',
+	lessThanMinuteUntil: 'menos de un minuto desde ahora',
+	minuteUntil: 'un minuto desde ahora',
+	minutesUntil: '{delta} minutos desde ahora',
+	hourUntil: 'una hora desde ahora',
+	hoursUntil: 'unas {delta} horas desde ahora',
+	dayUntil: 'un día desde ahora',
+	daysUntil: '{delta} días desde ahora',
+	weekUntil: 'una semana desde ahora',
+	weeksUntil: 'unas {delta} semanas desde ahora',
+	monthUntil: 'un mes desde ahora',
+	monthsUntil: '{delta} meses desde ahora',
+	yearUntil: 'un año desde ahora',
+	yearsUntil: '{delta} años desde ahora'
+
+});/*
+---
+
+script: Date.Swedish.js
+
+description: Date messages for Swedish (SE).
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Date', {
+
+	months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'],
+	days: ['söndag', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'],
+	// culture's date order: YYYY-MM-DD
+	dateOrder: ['year', 'month', 'date'],
+	AM: '',
+	PM: '',
+
+	shortDate: '%Y-%m-%d',
+	shortTime: '%H:%M',
+
+	/* Date.Extras */
+	ordinal: function(dayOfMonth){
+		// Not used in Swedish
+		return '';
+	},
+
+	lessThanMinuteAgo: 'mindre än en minut sedan',
+	minuteAgo: 'ungefär en minut sedan',
+	minutesAgo: '{delta} minuter sedan',
+	hourAgo: 'ungefär en timme sedan',
+	hoursAgo: 'ungefär {delta} timmar sedan',
+	dayAgo: '1 dag sedan',
+	daysAgo: '{delta} dagar sedan',
+	lessThanMinuteUntil: 'mindre än en minut sedan',
+	minuteUntil: 'ungefär en minut sedan',
+	minutesUntil: '{delta} minuter sedan',
+	hourUntil: 'ungefär en timme sedan',
+	hoursUntil: 'ungefär {delta} timmar sedan',
+	dayUntil: '1 dag sedan',
+	daysUntil: '{delta} dagar sedan'
+
+});/*
+---
+
+script: Date.Ukrainian.js
+
+description: Date messages for Ukrainian.
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - /Lang
+ - /Date
+
+provides: [Date.Ukrainian]
+
+...
+*/
+
+(function(){
+	var pluralize = function(n, one, few, many, other){
+		var d = (n / 10).toInt();
+		var z = n % 10;
+		var s = (n / 100).toInt();
+
+		if(d == 1 && n > 10) return many;
+		if(z == 1) return one;
+		if(z > 0 && z < 5) return few;
+		return many;
+	};
+
+	MooTools.lang.set('uk-UA', 'Date', {
+			months: ['Січень', 'Лютий', 'Березень', 'Квітень', 'Травень', 'Червень', 'Липень', 'Серпень', 'ВереÑ?ень', 'Жовтень', 'ЛиÑ?топад', 'Грудень'],
+			days: ['Ð?еділÑ?', 'Понеділок', 'Вівторок', 'Середа', 'Четвер', 'П\'Ñ?тницÑ?', 'Субота'],
+			//culture's date order: DD/MM/YYYY
+			dateOrder: ['date', 'month', 'year'],
+			AM: 'до полуднÑ?',
+			PM: 'по полудню',
+
+			shortDate: '%d/%m/%Y',
+			shortTime: '%H:%M',
+
+			/* Date.Extras */
+			ordinal: '',
+			lessThanMinuteAgo: 'меньше хвилини тому',
+			minuteAgo: 'хвилину тому',
+			minutesAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин') + ' тому';
+			},
+			hourAgo: 'годину тому',
+			hoursAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'годину', 'години', 'годин') + ' тому';
+			},
+			dayAgo: 'вчора',
+			daysAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів') + ' тому';
+			},
+			weekAgo: 'тиждень тому',
+			weeksAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів') + ' тому';
+			},
+			monthAgo: 'міÑ?Ñ?ць тому',
+			monthsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців') + ' тому';
+			},
+			yearAgo: 'рік тому',
+			yearsAgo: function (delta){
+				return '{delta} ' + pluralize(delta, 'рік', 'роки', 'років') + ' тому';
+			},
+			lessThanMinuteUntil: 'за мить',
+			minuteUntil: 'через хвилину',
+			minutesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'хвилину', 'хвилини', 'хвилин');
+			},
+			hourUntil: 'через годину',
+			hoursUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'годину', 'години', 'годин');
+			},
+			dayUntil: 'завтра',
+			daysUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'день', 'днÑ?', 'днів');
+			},
+			weekUntil: 'через тиждень',
+			weeksUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'тиждень', 'тижні', 'тижнів');
+			},
+			monthUntil: 'через міÑ?Ñ?ць',
+			monthesUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'міÑ?Ñ?ць', 'міÑ?Ñ?ці', 'міÑ?Ñ?ців');
+			},
+			yearUntil: 'через рік',
+			yearsUntil: function (delta){
+				return 'через {delta} ' + pluralize(delta, 'рік', 'роки', 'років');
+			}
+	});
+
+})();/*
+---
+
+script: Form.Validator.Arabic.js
+
+description: Form.Validator messages in Arabic.
+
+license: MIT-style license
+
+authors:
+ - Chafik Barbar
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Arabic]
+
+...
+*/
+
+MooTools.lang.set('ar', 'Form.Validator', {
+	required:'هذا الحقل مطلوب.',
+	minLength:'رجاءً إدخال {minLength}  أحرÙ? على الأقل (تم إدخال {length} أحرÙ?).',
+	maxLength:'الرجاء عدم إدخال أكثر من {maxLength} أحرÙ? (تم إدخال {length} أحرÙ?).',
+	integer:'الرجاء إدخال عدد صحيح Ù?ÙŠ هذا الحقل. أي رقم ذو كسر عشري أو مئوي (مثال 1.25 ) غير مسموح.',
+	numeric:'الرجاء إدخال قيم رقمية Ù?ÙŠ هذا الحقل (مثال "1" أو "1.1" أو "-1" أو "-1.1").',
+	digits:'الرجاء أستخدام قيم رقمية وعلامات ترقيمية Ù?قط Ù?ÙŠ هذا الحقل (مثال, رقم هاتÙ? مع نقطة أو شحطة)',
+	alpha:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	alphanum:'الرجاء أستخدام أحرÙ? Ù?قط (ا-ÙŠ) أو أرقام (0-9) Ù?قط Ù?ÙŠ هذا الحقل. أي Ù?راغات أو علامات غير مسموحة.',
+	dateSuchAs:'الرجاء إدخال تاريخ صحيح كالتالي {date}',
+	dateInFormatMDY:'الرجاء إدخال تاريخ صحيح (مثال, 31-12-1999)',
+	email:'الرجاء إدخال بريد إلكتروني صحيح.',
+	url:'الرجاء إدخال عنوان إلكتروني صحيح مثل http://www.google.com',
+	currencyDollar:'الرجاء إدخال قيمة $ صحيحة.  مثال, 100.00$',
+	oneRequired:'الرجاء إدخال قيمة Ù?ÙŠ أحد هذه الحقول على الأقل.',
+	errorPrefix: 'خطأ: ',
+	warningPrefix: 'تحذير: '
+}).set('ar', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Catalan.js
+
+description: Date messages for Catalan.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+ - Alfons Sanchez
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Catalan]
+
+...
+*/
+
+MooTools.lang.set('ca-CA', 'Form.Validator', {
+
+	required:'Aquest camp es obligatori.',
+	minLength:'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).',
+	maxLength:'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).',
+	integer:'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.',
+	numeric:'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).',
+	alpha:'Per favor utilitza lletres nomes (a-z) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	alphanum:'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No s´admiteixen espais ni altres caracters.',
+	dateSuchAs:'Per favor introdueix una data valida com {date}',
+	dateInFormatMDY:'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Per favor, introdueix una adreça de correu electronic valida. Per exemple,  "fred at domain.com".',
+	url:'Per favor introdueix una URL valida com http://www.google.com.',
+	currencyDollar:'Per favor introdueix una quantitat valida de €. Per exemple €100,00 .',
+	oneRequired:'Per favor introdueix alguna cosa per al menys una d´aquestes entrades.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Avis: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No poden haver espais en aquesta entrada.',
+	reqChkByNode: 'No hi han elements seleccionats.',
+	requiredChk: 'Aquest camp es obligatori.',
+	reqChkByName: 'Per favor selecciona una {label}.',
+	match: 'Aquest camp necessita coincidir amb el camp {matchName}',
+	startDate: 'la data de inici',
+	endDate: 'la data de fi',
+	currendDate: 'la data actual',
+	afterDate: 'La data deu ser igual o posterior a {label}.',
+	beforeDate: 'La data deu ser igual o anterior a {label}.',
+	startMonth: 'Per favor selecciona un mes d´orige',
+	sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.'
+
+});/*
+---
+
+script: Form.Validator.Czech.js
+
+description: Form Validator messages for Czech.
+
+license: MIT-style license
+
+authors:
+ - Jan Černý chemiX
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Czech]
+
+...
+*/
+
+MooTools.lang.set('cs-CZ', 'Form.Validator', {
+
+	required:'Tato položka je povinná.',
+	minLength:'Zadejte prosím alespoň {minLength} znaků (napsáno {length} znaků).',
+	maxLength:'Zadejte prosím méně než {maxLength} znaků (nápsáno {length} znaků).',
+	integer:'Zadejte prosím celé Ä?íslo. Desetinná Ä?ísla (napÅ™. 1.25) nejsou povolena.',
+	numeric:'Zadejte jen Ä?íselné hodnoty  (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").',
+	digits:'Zadejte prosím pouze Ä?ísla a interpunkÄ?ní znaménka(například telefonní Ä?íslo s pomlÄ?kami nebo teÄ?kami je povoleno).',
+	alpha:'Zadejte prosím pouze písmena (a-z). Mezery nebo jiné znaky nejsou povoleny.',
+	alphanum:'Zadejte prosím pouze písmena (a-z) nebo Ä?íslice (0-9). Mezery nebo jiné znaky nejsou povoleny.',
+	dateSuchAs:'Zadejte prosím platné datum jako {date}',
+	dateInFormatMDY:'Zadejte prosím platné datum jako MM / DD / RRRR (tj. "12/31/1999")',
+	email:'Zadejte prosím platnou e-mailovou adresu. Například "fred at domain.com".',
+	url:'Zadejte prosím platnou URL adresu jako http://www.google.com.',
+	currencyDollar:'Zadejte prosím platnou Ä?ástku. Například $100.00.',
+	oneRequired:'Zadejte prosím alespoň jednu hodnotu pro tyto položky.',
+	errorPrefix: 'Chyba: ',
+	warningPrefix: 'Upozornění: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'V této položce nejsou povoleny mezery',
+	reqChkByNode: 'Nejsou vybrány žádné položky.',
+	requiredChk: 'Tato položka je vyžadována.',
+	reqChkByName: 'Prosím vyberte {label}.',
+	match: 'Tato položka se musí shodovat s položkou {matchName}',
+	startDate: 'datum zahájení',
+	endDate: 'datum ukonÄ?ení',
+	currendDate: 'aktuální datum',
+	afterDate: 'Datum by mělo být stejné nebo větší než {label}.',
+	beforeDate: 'Datum by mělo být stejné nebo menší než {label}.',
+	startMonth: 'Vyberte poÄ?áteÄ?ní mÄ›síc.',
+	sameMonth: 'Tyto dva datumy musí být ve stejném měsíci - změňte jeden z nich.',
+    creditcard: 'Zadané Ä?íslo kreditní karty je neplatné. Prosím opravte ho. Bylo zadáno {length} Ä?ísel.'
+
+});
+/*
+---
+
+script: Form.Validator.Chinese.js
+
+description: Form.Validator messages in chinese (both simplified and traditional).
+
+license: MIT-style license
+
+authors:
+ - 陈桂军 - guidy <at> ixuer [dot] net
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Chinese]
+
+...
+*/
+
+/*
+In Chinese:
+------------
+需�指出的是:
+简体中文适用于中国大陆,
+�体中文适用于香港�澳门和�湾�。
+简体中文和�体中文在字体和语法上有很多的��之处。
+
+我�以确�简体中文语言包的准确性,
+但对于�体中文,我�以��用户�以准确的�解,但无法��语�符�他们的阅读习惯。
+如果您�能确认的�,�以�使用简体中文语言包,因为它是最通用的。
+
+In English:
+------------
+It should be noted that:
+Simplified  Chinese apply to mainland Chinese,
+Traditional Chinese apply to Hong Kong, Macao and Taiwan Province.
+There are a lot of different from Simplified  Chinese and Traditional Chinese , Contains font and syntax .
+
+I can assure Simplified Chinese language pack accuracy .
+For Traditional Chinese, I can only guarantee that users can understand, but not necessarily in line with their reading habits.
+If you are unsure, you can only use the simplified Chinese language pack, as it is the most common.
+
+*/
+
+// Simplified Chinese
+MooTools.lang.set('zhs-CN', 'Form.Validator', {
+	required:'这是必填项。',
+	minLength:'请至少输入 {minLength} 个字符 (已输入 {length} 个)。',
+	maxLength:'最多�能输入 {maxLength} 个字符 (已输入 {length} 个)。',
+	integer:'请输入一个整数,�能包��数点。例如:"1", "200"。',
+	numeric:'请输入一个数字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'这里�能接�数字和标点的输入,标点�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'请输入 A-Z 的 26 个字�,�能包�空格或任何其他字符。',
+	alphanum:'请输入 A-Z 的 26 个字�或 0-9 的 10 个数字,�能包�空格或任何其他字符。',
+	dateSuchAs:'请输入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'请输入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'请输入�法的电�信箱地�,例如:"fred at domain.com"。',
+	url:'请输入�法的 Url 地�,例如:http://www.google.com。',
+	currencyDollar:'请输入�法的货�符�,例如:¥',
+	oneRequired:'请至少选择一项。',
+	errorPrefix: '错误:',
+	warningPrefix: '警告:'
+});
+
+// Traditional Chinese
+MooTools.lang.set('zht-CN', 'Form.Validator', {
+	required:'這是必填項。',
+	minLength:'請至少�入 {minLength} 個字符(已�入 {length} 個)。',
+	maxLength:'最多�能�入 {maxLength} 個字符(已�入 {length} 個)。',
+	integer:'請�入一個整數,�能包��數點。例如:"1", "200"。',
+	numeric:'請�入一個數字,例如:"1", "1.1", "-1", "-1.1"。',
+	digits:'這裡�能接�數字和標點的�入,標點�以是:"(", ")", ".", ":", "-", "+", "#"和空格。',
+	alpha:'請�入 A-Z 的 26 個字�,�能包�空格或任何其他字符。',
+	alphanum:'請�入 A-Z 的 26 個字�或 0-9 的 10 個數字,�能包�空格或任何其他字符。',
+	dateSuchAs:'請�入�法的日期格�,如:{date}。',
+	dateInFormatMDY:'請�入�法的日期格�,例如:MM/DD/YYYY ("12/31/1999")。',
+	email:'請�入�法的電�信箱地�,例如:"fred at domain.com"。',
+	url:'請�入�法的 Url 地�,例如:http://www.google.com。',
+	currencyYuan:'請�入�法的貨幣符號,例如:¥',
+	oneRequired:'請至少�擇一項。',
+	errorPrefix: '錯誤:',
+	warningPrefix: '警告:'
+});
+
+Form.Validator.add('validate-currency-yuan', {
+	errorMsg: function(){
+		return Form.Validator.getMsg('currencyYuan');
+	},
+	test: function(element) {
+		// [ï¿¥]1[##][,###]+[.##]
+		// [ï¿¥]1###+[.##]
+		// [ï¿¥]0.##
+		// [ï¿¥].##
+		return Form.Validator.getValidator('IsEmpty').test(element) ||  (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value'));
+	}
+});
+/*
+---
+
+script: Form.Validator.Dutch.js
+
+description: Form.Validator messages in Dutch.
+
+license: MIT-style license
+
+authors:
+ - Lennart Pilon
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Dutch]
+
+...
+*/
+
+MooTools.lang.set('nl-NL', 'Form.Validator', {
+	required:'Dit veld is verplicht.',
+	minLength:'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).',
+	maxLength:'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).',
+	integer:'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1,25) zijn niet toegestaan.',
+	numeric:'Vul alleen numerieke waarden in (bijvoorbeeld. "1" of "1.1" of "-1" of "-1.1").',
+	digits:'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met een streepje).',
+	alpha:'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.',
+	alphanum:'Vul alleen letters in (a-z) of nummers (0-9). Spaties en andere karakters zijn niet toegestaan.',
+	dateSuchAs:'Vul een geldige datum in, zoals {date}',
+	dateInFormatMDY:'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")',
+	email:'Vul een geldig e-mailadres in. Bijvoorbeeld "fred at domein.nl".',
+	url:'Vul een geldige URL in, zoals http://www.google.nl.',
+	currencyDollar:'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .',
+	oneRequired:'Vul iets in bij minimaal een van de invoervelden.',
+	warningPrefix: 'Waarschuwing: ',
+	errorPrefix: 'Fout: '
+});/*
+---
+
+script: Form.Validator.Estonian.js
+
+description: Date messages for Estonian.
+
+license: MIT-style license
+
+authors:
+ - Kevin Valdek
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Estonian]
+
+...
+*/
+
+MooTools.lang.set('et-EE', 'Form.Validator', {
+
+	required:'Väli peab olema täidetud.',
+	minLength:'Palun sisestage vähemalt {minLength} tähte (te sisestasite {length} tähte).',
+	maxLength:'Palun ärge sisestage rohkem kui {maxLength} tähte (te sisestasite {length} tähte).',
+	integer:'Palun sisestage väljale täisarv. Kümnendarvud (näiteks 1.25) ei ole lubatud.',
+	numeric:'Palun sisestage ainult numbreid väljale (näiteks "1", "1.1", "-1" või "-1.1").',
+	digits:'Palun kasutage ainult numbreid ja kirjavahemärke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).',
+	alpha:'Palun kasutage ainult tähti (a-z). Tühikud ja teised sümbolid on keelatud.',
+	alphanum:'Palun kasutage ainult tähti (a-z) või numbreid (0-9). Tühikud ja teised sümbolid on keelatud.',
+	dateSuchAs:'Palun sisestage kehtiv kuupäev kujul {date}',
+	dateInFormatMDY:'Palun sisestage kehtiv kuupäev kujul MM.DD.YYYY (näiteks: "12.31.1999").',
+	email:'Palun sisestage kehtiv e-maili aadress (näiteks: "fred at domain.com").',
+	url:'Palun sisestage kehtiv URL (näiteks: http://www.google.com).',
+	currencyDollar:'Palun sisestage kehtiv $ summa (näiteks: $100.00).',
+	oneRequired:'Palun sisestage midagi vähemalt ühele antud väljadest.',
+	errorPrefix: 'Viga: ',
+	warningPrefix: 'Hoiatus: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Väli ei tohi sisaldada tühikuid.',
+	reqChkByNode: 'Ükski väljadest pole valitud.',
+	requiredChk: 'Välja täitmine on vajalik.',
+	reqChkByName: 'Palun valige üks {label}.',
+	match: 'Väli peab sobima {matchName} väljaga',
+	startDate: 'algkuupäev',
+	endDate: 'lõppkuupäev',
+	currendDate: 'praegune kuupäev',
+	afterDate: 'Kuupäev peab olema võrdne või pärast {label}.',
+	beforeDate: 'Kuupäev peab olema võrdne või enne {label}.',
+	startMonth: 'Palun valige algkuupäev.',
+	sameMonth: 'Antud kaks kuupäeva peavad olema samas kuus - peate muutma ühte kuupäeva.'
+
+});/*
+---
+
+script: Form.Validator.German.js
+
+description: Date messages for German.
+
+license: MIT-style license
+
+authors: 
+ - Frank Rossi
+ - Ulrich Petri
+ - Fabian Beiner
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.German]
+
+...
+*/
+
+MooTools.lang.set('de-DE', 'Form.Validator', {
+
+	required: 'Dieses Eingabefeld muss ausgef&uuml;llt werden.',
+	minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).',
+	maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+	integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. &quot;1.25&quot;) sind nicht erlaubt.',
+	numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;) ein.',
+	digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).',
+	alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+	alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.',
+	dateSuchAs: 'Geben Sie bitte ein g&uuml;ltiges Datum ein (z.B. &quot;{date}&quot;).',
+	dateInFormatMDY: 'Geben Sie bitte ein g&uuml;ltiges Datum im Format TT.MM.JJJJ ein (z.B. &quot;31.12.1999&quot;).',
+	email: 'Geben Sie bitte eine g&uuml;ltige E-Mail-Adresse ein (z.B. &quot;max at mustermann.de&quot;).',
+	url: 'Geben Sie bitte eine g&uuml;ltige URL ein (z.B. &quot;http://www.google.de&quot;).',
+	currencyDollar: 'Geben Sie bitte einen g&uuml;ltigen Betrag in EURO ein (z.B. 100.00&#8364;).',
+	oneRequired: 'Bitte f&uuml;llen Sie mindestens ein Eingabefeld aus.',
+	errorPrefix: 'Fehler: ',
+	warningPrefix: 'Warnung: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.',
+	reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+	requiredChk: 'Dieses Feld muss ausgef&uuml;llt werden.',
+	reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+	match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld &uuml;bereinstimmen.',
+	startDate: 'Das Anfangsdatum',
+	endDate: 'Das Enddatum',
+	currendDate: 'Das aktuelle Datum',
+	afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein als {label}.',
+	beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein als {label}.',
+	startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+	sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eines von beiden ver&auml;ndern.',
+	creditcard: 'Die eingegebene Kreditkartennummer ist ung&uuml;ltig. Bitte &uuml;berpr&uuml;fen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.'
+});
+/*
+---
+
+script: Form.Validator.German.CH.js
+
+description: Date messages for German (Switzerland).
+ 
+license: MIT-style license
+ 
+authors:
+ - Michael van der Weg
+
+requires:
+ - /Lang
+ - /Form.Validator.German
+
+provides: [Form.Validator.German.CH]
+
+...
+*/
+ 
+MooTools.lang.set('de-CH', 'Form.Validator', {
+ 
+  required:'Dieses Feld ist obligatorisch.',
+  minLength:'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  maxLength:'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).',
+  integer:'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.',
+  numeric:'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. &quot;1&quot;, &quot;1.1&quot;, &quot;-1&quot; oder &quot;-1.1&quot;).',
+  digits:'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).',
+  alpha:'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  alphanum:'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.',
+  dateSuchAs:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel {date}',
+  dateInFormatMDY:'Geben Sie bitte ein g&uuml;ltiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. &quot;31.12.1999&quot;)',
+  email:'Geben Sie bitte eine g&uuml;ltige E-Mail Adresse ein. Wie zum Beispiel &quot;maria at bernasconi.ch&quot;.',
+  url:'Geben Sie bitte eine g&uuml;ltige URL ein. Wie zum Beispiel http://www.google.ch.',
+  currencyDollar:'Geben Sie bitte einen g&uuml;ltigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .',
+  oneRequired:'Machen Sie f&uuml;r mindestens eines der Eingabefelder einen Eintrag.',
+  errorPrefix: 'Fehler: ',
+  warningPrefix: 'Warnung: ',
+ 
+  //Form.Validator.Extras
+ 
+  noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.',
+  reqChkByNode: 'Es wurden keine Elemente gew&auml;hlt.',
+  requiredChk: 'Dieses Feld ist obligatorisch.',
+  reqChkByName: 'Bitte w&auml;hlen Sie ein {label}.',
+  match: 'Dieses Eingabefeld muss mit dem Feld {matchName} &uuml;bereinstimmen.',
+  startDate: 'Das Anfangsdatum',
+  endDate: 'Das Enddatum',
+  currendDate: 'Das aktuelle Datum',
+  afterDate: 'Das Datum sollte zur gleichen Zeit oder sp&auml;ter sein {label}.',
+  beforeDate: 'Das Datum sollte zur gleichen Zeit oder fr&uuml;her sein {label}.',
+  startMonth: 'W&auml;hlen Sie bitte einen Anfangsmonat',
+  sameMonth: 'Diese zwei Datumsangaben m&uuml;ssen im selben Monat sein - Sie m&uuml;ssen eine von beiden ver&auml;ndern.'
+ 
+});/*
+---
+
+script: Form.Validator.French.js
+
+description: Form.Validator messages in French.
+
+license: MIT-style license
+
+authors: 
+ - Miquel Hudin
+ - Nicolas Sorosac <nicolas <dot> sorosac <at> gmail <dot> com>
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.French]
+
+...
+*/
+ 
+MooTools.lang.set('fr-FR', 'Form.Validator', {
+  required:'Ce champ est obligatoire.',
+  minLength:'Veuillez saisir un minimum de {minLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  maxLength:'Veuillez saisir un maximum de {maxLength} caract&egrave;re(s) (vous avez saisi {length} caract&egrave;re(s)).',
+  integer:'Veuillez saisir un nombre entier dans ce champ. Les nombres d&eacute;cimaux (ex : "1,25") ne sont pas autoris&eacute;s.',
+  numeric:'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").',
+  digits:'Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un num&eacute;ro de t&eacute;l&eacute;phone avec des traits d\'union est autoris&eacute;).',
+  alpha:'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  alphanum:'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caract&egrave;res ne sont pas autoris&eacute;s.',
+  dateSuchAs:'Veuillez saisir une date correcte comme {date}',
+  dateInFormatMDY:'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").',
+  email:'Veuillez saisir une adresse de courrier &eacute;lectronique. Par example "fred at domaine.com".',
+  url:'Veuillez saisir une URL, comme http://www.google.com.',
+  currencyDollar:'Veuillez saisir une quantit&eacute; correcte. Par example 100,00&euro;.',
+  oneRequired:'Veuillez s&eacute;lectionner au moins une de ces options.',
+  errorPrefix: 'Erreur : ',
+  warningPrefix: 'Attention : ',
+  
+  //Form.Validator.Extras
+ 
+  noSpace: 'Ce champ n\'accepte pas les espaces.',
+  reqChkByNode: 'Aucun &eacute;l&eacute;ment n\'est s&eacute;lectionn&eacute;.',
+  requiredChk: 'Ce champ est obligatoire.',
+  reqChkByName: 'Veuillez s&eacute;lectionner un(e) {label}.',
+  match: 'Ce champ doit correspondre avec le champ {matchName}.',
+  startDate: 'date de d&eacute;but',
+  endDate: 'date de fin',
+  currendDate: 'date actuelle',
+  afterDate: 'La date doit &ecirc;tre identique ou post&eacute;rieure &agrave; {label}.',
+  beforeDate: 'La date doit &ecirc;tre identique ou ant&eacute;rieure &agrave; {label}.',
+  startMonth: 'Veuillez s&eacute;lectionner un mois de d&eacute;but.',
+  sameMonth: 'Ces deux dates doivent &ecirc;tre dans le m&ecirc;me mois - vous devez en modifier une.'
+ 
+});/*
+---
+
+script: Form.Validator.Italian.js
+
+description: Form.Validator messages in Italian.
+
+license: MIT-style license
+
+authors:
+ - Leonardo Laureti
+ - Andrea Novero
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Italian]
+
+...
+*/
+ 
+MooTools.lang.set('it-IT', 'Form.Validator', {
+
+	required:'Il campo &egrave; obbligatorio.',
+	minLength:'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).',
+	maxLength:'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).',
+	integer:'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).',
+	numeric:'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").',
+	digits:'Inserire solo numeri e caratteri di punteggiatura. Per esempio &egrave; consentito un numero telefonico con trattini o punti.',
+	alpha:'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.',
+	alphanum:'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.',
+	dateSuchAs:'Inserire una data valida del tipo {date}',
+	dateInFormatMDY:'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")',
+	email:'Inserire un indirizzo email valido. Per esempio "nome at dominio.com".',
+	url:'Inserire un indirizzo valido. Per esempio "http://www.dominio.com".',
+	currencyDollar:'Inserire un importo valido. Per esempio "$100.00".',
+	oneRequired:'Completare almeno uno dei campi richiesti.',
+	errorPrefix: 'Errore: ',
+	warningPrefix: 'Attenzione: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Non sono consentiti spazi.',
+	reqChkByNode: 'Nessuna voce selezionata.',
+	requiredChk: 'Il campo &egrave; obbligatorio.',
+	reqChkByName: 'Selezionare un(a) {label}.',
+	match: 'Il valore deve corrispondere al campo {matchName}',
+	startDate: 'data d\'inizio',
+	endDate: 'data di fine',
+	currendDate: 'data attuale',
+	afterDate: 'La data deve corrispondere o essere successiva al {label}.',
+	beforeDate: 'La data deve corrispondere o essere precedente al {label}.',
+	startMonth: 'Selezionare un mese d\'inizio',
+	sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.'
+
+});/*
+---
+
+script: Form.Validator.Norwegian.js
+
+description: Form.Validator messages in Norwegian.
+
+license: MIT-style license
+
+authors:
+ - Espen 'Rexxars' Hovlandsdal
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Norwegian]
+
+...
+*/
+
+MooTools.lang.set('no-NO', 'Form.Validator', {
+   required:'Dette feltet er påkrevd.',
+   minLength:'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).',
+   maxLength:'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).',
+   integer:'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.',
+   numeric:'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").',
+   digits:'Vennligst bruk kun nummer og skilletegn i dette feltet.',
+   alpha:'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   alphanum:'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.',
+   dateSuchAs:'Vennligst skriv inn en gyldig dato, som {date}',
+   dateInFormatMDY:'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")',
+   email:'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen at domene.no".',
+   url:'Vennligst skriv inn en gyldig URL, for eksempel http://www.google.no.',
+   currencyDollar:'Vennligst fyll ut et gyldig $ beløp. For eksempel $100.00 .',
+   oneRequired:'Vennligst fyll ut noe i minst ett av disse feltene.',
+   errorPrefix: 'Feil: ',
+   warningPrefix: 'Advarsel: '
+});/*
+---
+
+script: Form.Validator.Polish.js
+
+description: Date messages for Polish.
+
+license: MIT-style license
+
+authors:
+ - Oskar Krawczyk
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Polish]
+
+...
+*/
+
+MooTools.lang.set('pl-PL', 'Form.Validator', {
+
+	required:'To pole jest wymagane.',
+	minLength:'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostało tylko {length}).',
+	maxLength:'Dozwolone jest nie więcej niż {maxLength} znaków (wpisanych zostało {length})',
+	integer:'To pole wymaga liczb całych. Liczby dziesiętne (np. 1.25) są niedozwolone.',
+	numeric:'Prosimy używać tylko numerycznych wartości w tym polu (np. "1", "1.1", "-1" lub "-1.1").',
+	digits:'Prosimy używać liczb oraz zankow punktuacyjnych w typ polu (dla przykładu, przy numerze telefonu myślniki i kropki są dozwolone).',
+	alpha:'Prosimy używać tylko liter (a-z) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	alphanum:'Prosimy używać tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki są niedozwolone.',
+	dateSuchAs:'Prosimy podać prawidłową datę w formacie: {date}',
+	dateInFormatMDY:'Prosimy podać poprawną date w formacie DD.MM.RRRR (i.e. "12.01.2009")',
+	email:'Prosimy podać prawidłowy adres e-mail, np. "jan at domena.pl".',
+	url:'Prosimy podać prawidłowy adres URL, np. http://www.google.pl.',
+	currencyDollar:'Prosimy podać prawidłową sumę w PLN. Dla przykładu: 100.00 PLN.',
+	oneRequired:'Prosimy wypełnić chociaż jedno z pól.',
+	errorPrefix: 'BÅ‚Ä…d: ',
+	warningPrefix: 'Uwaga: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'W tym polu nie mogą znajdować się spacje.',
+	reqChkByNode: 'Brak zaznaczonych elementów.',
+	requiredChk: 'To pole jest wymagane.',
+	reqChkByName: 'Prosimy wybrać z {label}.',
+	match: 'To pole musi być takie samo jak {matchName}',
+	startDate: 'data poczÄ…tkowa',
+	endDate: 'data końcowa',
+	currendDate: 'aktualna data',
+	afterDate: 'Podana data poinna być taka sama lub po {label}.',
+	beforeDate: 'Podana data poinna być taka sama lub przed {label}.',
+	startMonth: 'Prosimy wybrać początkowy miesiąc.',
+	sameMonth: 'Te dwie daty muszą być w zakresie tego samego miesiąca - wymagana jest zmiana któregoś z pól.'
+
+});/*
+---
+
+script: Form.Validator.Portuguese.js
+
+description: Form.Validator messages in Portuguese.
+
+license: MIT-style license
+
+authors:
+ - Miquel Hudin
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Portuguese]
+
+...
+*/
+
+MooTools.lang.set('pt-PT', 'Form.Validator', {
+	required:'Este campo é necessário.',
+	minLength:'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).',
+	maxLength:'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).',
+	integer:'Digite um número inteiro neste domínio. Com números decimais (por exemplo, 1,25), não são permitidas.',
+	numeric:'Digite apenas valores numéricos neste domínio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits:'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).',
+	alpha:'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.',
+	alphanum:'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.',
+	dateSuchAs:'Digite uma data válida, como {date}',
+	dateInFormatMDY:'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")',
+	email:'Digite um endereço de email válido. Por exemplo "fred at domain.com".',
+	url:'Digite uma URL válida, como http://www.google.com.',
+	currencyDollar:'Digite um valor válido $. Por exemplo $ 100,00. ',
+	oneRequired:'Digite algo para pelo menos um desses insumos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: '
+
+}).set('pt-PT', 'Date', {
+	dateOrder: ['date', 'month', 'year', '/']
+});/*
+---
+
+script: Form.Validator.Portuguese.BR.js
+
+description: Form.Validator messages in Portuguese-BR.
+
+license: MIT-style license
+
+authors:
+ - Fábio Miranda Costa
+
+requires:
+ - /Lang
+ - /Form.Validator.Portuguese
+
+provides: [Form.Validator.Portuguese.BR]
+
+...
+*/
+
+MooTools.lang.set('pt-BR', 'Form.Validator', {
+
+	required: 'Este campo é obrigatório.',
+	minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).',
+	maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).',
+	integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).',
+	numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").',
+	digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).',
+	alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.',
+	alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.',
+	dateSuchAs: 'Digite uma data válida, como {date}',
+	dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")',
+	email: 'Digite um endereço de email válido. Por exemplo "nome at dominio.com".',
+	url: 'Digite uma URL válida. Exemplo: http://www.google.com.',
+	currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .',
+	oneRequired: 'Digite algo para pelo menos um desses campos.',
+	errorPrefix: 'Erro: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Não é possível digitar espaços neste campo.',
+	reqChkByNode: 'Não foi selecionado nenhum item.',
+	requiredChk: 'Este campo é obrigatório.',
+	reqChkByName: 'Por favor digite um {label}.',
+	match: 'Este campo deve ser igual ao campo {matchName}.',
+	startDate: 'a data inicial',
+	endDate: 'a data final',
+	currendDate: 'a data atual',
+	afterDate: 'A data deve ser igual ou posterior a {label}.',
+	beforeDate: 'A data deve ser igual ou anterior a {label}.',
+	startMonth: 'Por favor selecione uma data inicial.',
+	sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.',
+	creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.'
+
+});/*
+---
+
+script: Form.Validator.Russian.js
+
+description: Form.Validator messages in Russian (utf-8 and cp1251).
+
+license: MIT-style license
+
+authors:
+ - Chernodarov Egor
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Russian]
+
+...
+*/
+
+MooTools.lang.set('ru-RU-unicode', 'Form.Validator', {
+	required:'Это поле обÑ?зательно к заполнению.',
+	minLength:'ПожалуйÑ?та, введите хотÑ? бы {minLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	maxLength:'ПожалуйÑ?та, введите не больше {maxLength} Ñ?имволов (Ð’Ñ‹ ввели {length}).',
+	integer:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло. Дробные чиÑ?ла (например 1.25) тут не разрешены.',
+	numeric:'ПожалуйÑ?та, введите в Ñ?то поле чиÑ?ло (например "1" или "1.1", или "-1", или "-1.1").',
+	digits:'Ð’ Ñ?том поле Ð’Ñ‹ можете иÑ?пользовать только цифры и знаки пунктуации (например, телефонный номер Ñ?о знаками дефиÑ?а или Ñ? точками).',
+	alpha:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z). Пробелы и другие Ñ?имволы запрещены.',
+	alphanum:'Ð’ Ñ?том поле можно иÑ?пользовать только латинÑ?кие буквы (a-z) и цифры (0-9). Пробелы и другие Ñ?имволы запрещены.',
+	dateSuchAs:'ПожалуйÑ?та, введите корректную дату {date}',
+	dateInFormatMDY:'ПожалуйÑ?та, введите дату в формате ММ/ДД/ГГГГ (например "12/31/1999")',
+	email:'ПожалуйÑ?та, введите корректный емейл-адреÑ?. ДлÑ? примера "fred at domain.com".',
+	url:'ПожалуйÑ?та, введите правильную Ñ?Ñ?ылку вида http://www.google.com.',
+	currencyDollar:'ПожалуйÑ?та, введите Ñ?умму в долларах. Ð?апример: $100.00 .',
+	oneRequired:'ПожалуйÑ?та, выберите хоть что-нибудь в одном из Ñ?тих полей.',
+	errorPrefix: 'Ошибка: ',
+	warningPrefix: 'Внимание: '
+});
+
+//translation in windows-1251 codepage
+MooTools.lang.set('ru-RU', 'Form.Validator', {
+	required:'�òî ïîëå îáÿçàòåëüíî ê çàïîëíåíèþ.',
+	minLength:'�îæàëóéñòà, ââåäèòå õîòÿ áû {minLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	maxLength:'�îæàëóéñòà, ââåäèòå íå áîëüøå {maxLength} ñèìâîëîâ (Âû ââåëè {length}).',
+	integer:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî. Äðîáíûå ÷èñëà (íàïðèìåð 1.25) òóò íå ðàçðåøåíû.',
+	numeric:'�îæàëóéñòà, ââåäèòå â ýòî ïîëå ÷èñëî (íàïðèìåð "1" èëè "1.1", èëè "-1", èëè "-1.1").',
+	digits:' ýòîì ïîëå Âû ìîæåòå èñïîëüçîâàòü òîëüêî öèôðû è çíàêè ïóíêòóàöèè (íàïðèìåð, òåëåôîííûé íîìåð ñî çíàêàìè äåôèñà èëè ñ òî÷êàìè).',
+	alpha:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	alphanum:' ýòîì ïîëå ìîæíî èñïîëüçîâàòü òîëüêî ëàòèíñêèå áóêâû (a-z) è öèôðû (0-9). �ðîáåëû è äðóãèå ñèìâîëû çàïðåùåíû.',
+	dateSuchAs:'�îæàëóéñòà, ââåäèòå êîððåêòíóþ äàòó {date}',
+	dateInFormatMDY:'�îæàëóéñòà, ââåäèòå äàòó â ôîðìàòå ÌÌ/ÄÄ/ÃÃÃà (íàïðèìåð "12/31/1999")',
+	email:'�îæàëóéñòà, ââåäèòå êîððåêòíûé åìåéë-àäðåñ. Äëÿ ïðèìåðà "fred at domain.com".',
+	url:'�îæàëóéñòà, ââåäèòå ïðàâèëüíóþ ññûëêó âèäà http://www.google.com.',
+	currencyDollar:'�îæàëóéñòà, ââåäèòå ñóììó â äîëëàðàõ. �àïðèìåð: $100.00 .',
+	oneRequired:'�îæàëóéñòà, âûáåðèòå õîòü ÷òî-íèáóäü â îäíîì èç ýòèõ ïîëåé.',
+	errorPrefix: 'Îøèáêà: ',
+	warningPrefix: 'Âíèìàíèå: '
+});/*
+---
+
+script: Form.Validator.Spanish.js
+
+description: Date messages for Spanish.
+
+license: MIT-style license
+
+authors:
+ - Ãlfons Sanchez
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Spanish]
+
+...
+*/
+
+MooTools.lang.set('es-ES', 'Form.Validator', {
+
+	required:'Este campo es obligatorio.',
+	minLength:'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).',
+	maxLength:'Por favor introduce no m&aacute;s de {maxLength} caracteres (has introducido {length} caracteres).',
+	integer:'Por favor introduce un n&uacute;mero entero en este campo. N&uacute;meros con decimales (p.e. 1,25) no se permiten.',
+	numeric:'Por favor introduce solo valores num&eacute;ricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").',
+	digits:'Por favor usa solo n&uacute;meros y puntuaci&oacute;n en este campo (por ejemplo, un n&uacute;mero de tel&eacute;fono con guiones y puntos no esta permitido).',
+	alpha:'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.',
+	alphanum:'Por favor, usa solo letras (a-z) o n&uacute;meros (0-9) en este campo. No se admiten espacios ni otros caracteres.',
+	dateSuchAs:'Por favor introduce una fecha v&aacute;lida como {date}',
+	dateInFormatMDY:'Por favor introduce una fecha v&aacute;lida como DD/MM/YYYY (p.e. "31/12/1999")',
+	email:'Por favor, introduce una direcci&oacute;n de email v&aacute;lida. Por ejemplo,  "fred at domain.com".',
+	url:'Por favor introduce una URL v&aacute;lida como http://www.google.com.',
+	currencyDollar:'Por favor introduce una cantidad v&aacute;lida de €. Por ejemplo €100,00 .',
+	oneRequired:'Por favor introduce algo para por lo menos una de estas entradas.',
+	errorPrefix: 'Error: ',
+	warningPrefix: 'Aviso: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'No pueden haber espacios en esta entrada.',
+	reqChkByNode: 'No hay elementos seleccionados.',
+	requiredChk: 'Este campo es obligatorio.',
+	reqChkByName: 'Por favor selecciona una {label}.',
+	match: 'Este campo necesita coincidir con el campo {matchName}',
+	startDate: 'la fecha de inicio',
+	endDate: 'la fecha de fin',
+	currendDate: 'la fecha actual',
+	afterDate: 'La fecha debe ser igual o posterior a {label}.',
+	beforeDate: 'La fecha debe ser igual o anterior a {label}.',
+	startMonth: 'Por favor selecciona un mes de origen',
+	sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.'
+
+});
+/*
+---
+
+script: Form.Validator.Swedish.js
+
+description: Date messages for Swedish.
+
+license: MIT-style license
+
+authors:
+ - Martin Lundgren
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Swedish]
+
+...
+*/
+
+MooTools.lang.set('sv-SE', 'Form.Validator', {
+
+	required:'Fältet är obligatoriskt.',
+	minLength:'Ange minst {minLength} tecken (du angav {length} tecken).',
+	maxLength:'Ange högst {maxLength} tecken (du angav {length} tecken). ',
+	integer:'Ange ett heltal i fältet. Tal med decimaler (t.ex. 1,25) är inte tillåtna.',
+	numeric:'Ange endast numeriska värden i detta fält (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").',
+	digits:'Använd endast siffror och skiljetecken i detta fält (till exempel ett telefonnummer med bindestreck tillåtet).',
+	alpha:'Använd endast bokstäver (a-ö) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	alphanum:'Använd endast bokstäver (a-ö) och siffror (0-9) i detta fält. Inga mellanslag eller andra tecken är tillåtna.',
+	dateSuchAs:'Ange ett giltigt datum som t.ex. {date}',
+	dateInFormatMDY:'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")',
+	email:'Ange en giltig e-postadress. Till exempel "erik at domain.com".',
+	url:'Ange en giltig webbadress som http://www.google.com.',
+	currencyDollar:'Ange en giltig belopp. Exempelvis 100,00.',
+	oneRequired:'Vänligen ange minst ett av dessa alternativ.',
+	errorPrefix: 'Fel: ',
+	warningPrefix: 'Varning: ',
+
+	//Form.Validator.Extras
+
+	noSpace: 'Det får inte finnas några mellanslag i detta fält.',
+	reqChkByNode: 'Inga objekt är valda.',
+	requiredChk: 'Detta är ett obligatoriskt fält.',
+	reqChkByName: 'Välj en {label}.',
+	match: 'Detta fält måste matcha {matchName}',
+	startDate: 'startdatumet',
+	endDate: 'slutdatum',
+	currendDate: 'dagens datum',
+	afterDate: 'Datumet bör vara samma eller senare än {label}.',
+	beforeDate: 'Datumet bör vara samma eller tidigare än {label}.',
+	startMonth: 'Välj en start månad',
+	sameMonth: 'Dessa två datum måste vara i samma månad - du måste ändra det ena eller det andra.'
+
+});/*
+---
+
+script: Form.Validator.Ukrainian.js
+
+description: Form.Validator messages in Ukrainian (utf-8).
+
+license: MIT-style license
+
+authors:
+ - Slik
+
+requires:
+ - /Lang
+ - /Form.Validator
+
+provides: [Form.Validator.Ukrainian]
+
+...
+*/
+
+MooTools.lang.set('uk-UA', 'Form.Validator', {
+	required:'Це поле повинне бути заповненим.',
+	minLength:'Введіть хоча б {minLength} Ñ?имволів (Ви ввели {length}).',
+	maxLength:'КількіÑ?Ñ‚ÑŒ Ñ?имволів не може бути більше {maxLength} (Ви ввели {length}).',
+	integer:'Введіть в це поле чиÑ?ло. Дробові чиÑ?ла (наприклад 1.25) не дозволені.',
+	numeric:'Введіть в це поле чиÑ?ло (наприклад "1" або "1.1", або "-1", або "-1.1").',
+	digits:'Ð’ цьому полі ви можете викориÑ?товувати лише цифри Ñ– знаки пунктіації (наприклад, телефонний номер з знаками дефізу або з крапками).',
+	alpha:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	alphanum:'Ð’ цьому полі можна викориÑ?товувати лише латинÑ?ькі літери (a-z) Ñ– цифри (0-9). Пробіли Ñ– інші Ñ?имволи заборонені.',
+	dateSuchAs:'Введіть коректну дату {date}.',
+	dateInFormatMDY:'Введіть дату в форматі ММ/ДД/РРРР (наприклад "12/31/2009").',
+	email:'Введіть коректну адреÑ?у електронної пошти (наприклад "name at domain.com").',
+	url:'Введіть коректне інтернет-поÑ?иланнÑ? (наприклад http://www.google.com).',
+	currencyDollar:'Введіть Ñ?уму в доларах (наприклад "$100.00").',
+	oneRequired:'Заповніть одне з полів.',
+	errorPrefix: 'Помилка: ',
+	warningPrefix: 'Увага: '
+});
+/*
+---
+
+name: Common
+
+description: Jx namespace with methods and classes common to most Jx widgets
+
+license: MIT-style license.
+
+requires:
+ - Core/Class
+ - Core/Element
+ - Core/Browser
+ - Core/Element.Style
+ - Core/Request
+ - Core/Class.Extras
+ - More/Class.Binds
+ - Core/Array
+ - Core/Element.Event
+ - Core/Element.Dimensions
+ - More/Element.Measure
+ - More/Lang
+ - Core/Selectors
+ - Core/Slick.Finder
+ - Core/Slick.Parser
+
+provides: [Jx]
+
+css:
+ - license
+ - reset
+ - common
+
+images:
+ - a_pixel.png
+
+...
+ */
+// $Id: common.js 999 2010-11-28 21:00:43Z jonlb at comcast.net $
+/**
+ * Function: $jx
+ * dereferences a DOM Element to a JxLib object if possible and returns
+ * a reference to the object, or null if not defined.
+ */
+function $jx(id) {
+  var widget = null;
+  id = document.id(id);
+  if (id) {
+    widget = id.retrieve('jxWidget');
+    if (!widget && id != document.body) {
+      widget = $jx(id.getParent());
+    }
+  }
+  return widget;
+}
+
+/**
+ * Class: Jx
+ * Jx is a global singleton object that contains the entire Jx library
+ * within it.  All Jx functions, attributes and classes are accessed
+ * through the global Jx object.  Jx should not create any other
+ * global variables, if you discover that it does then please report
+ * it as a bug
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+/* firebug console supressor for IE/Safari/Opera */
+window.addEvent('load',
+function() {
+    if (! ("console" in window)) {
+        var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+        "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"],
+            i;
+
+        window.console = {};
+        for (i = 0; i < names.length; ++i) {
+            window.console[names[i]] = function() {};
+        }
+    }
+});
+
+
+// add mutator that sets jxFamily when creating a class so we can check
+// its type
+Class.Mutators.Family = function(self, name) {
+    if ($defined(name)) {
+        self.jxFamily = name;
+        return self;
+    }
+    else if(!$defined(this.prototype.jxFamily)) {
+        this.implement({
+            'jxFamily': self
+        });
+    }
+};
+
+// this replaces the mootools $unlink method with our own version that
+// avoids infinite recursion on Jx objects.
+function $unlink(object) {
+    if (object && object.jxFamily) {
+        return object;
+    }
+    var unlinked, p, i, l;
+    switch ($type(object)) {
+    case 'object':
+        unlinked = {};
+        for (p in object) unlinked[p] = $unlink(object[p]);
+        break;
+    case 'hash':
+        unlinked = new Hash(object);
+        break;
+    case 'array':
+        unlinked = [];
+        for (i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+        break;
+    default:
+        return object;
+    }
+    return unlinked;
+}
+
+/**
+ * Override of mootools-core 1.3's typeOf operator to prevent infinite recursion
+ * when doing typeOf on JxLib objects.
+ *
+var typeOf = this.typeOf = function(item){
+    if (item == null) return 'null';
+    if (item.jxFamily) return item.jxFamily;
+    if (item.$family) return item.$family();
+
+    if (item.nodeName){
+        if (item.nodeType == 1) return 'element';
+        if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
+    } else if (typeof item.length == 'number'){
+        if (item.callee) return 'arguments';
+        if ('item' in item) return 'collection';
+    }
+
+    return typeof item;
+};
+
+this.$type = function(object){
+    var type = typeOf(object);
+    if (type == 'elements') return 'array';
+    return (type == 'null') ? false : type;
+};
+*/
+
+/* Setup global namespace.  It is possible to set the global namespace
+ * prior to including jxlib.  This would typically be required only if
+ * the auto-detection of the jxlib base URL would fail.  For instance,
+ * if you combine jxlib with other javascript libraries into a single file
+ * build and call it something without jxlib in the file name, then the
+ * detection of baseURL would fail.  If this happens to you, try adding
+ * Jx = { baseURL: '/path/to/jxlib/'; }
+ * where the path to jxlib contains a file called a_pixel.png (it doesn't
+ * have to include jxlib, just the a_pixel.png file).
+ */
+if (typeof Jx === 'undefined') {
+  var Jx = {};
+}
+
+/**
+ * APIProperty: {String} baseURL
+ * This is the URL that Jx was loaded from, it is
+ * automatically calculated from the script tag
+ * src property that included Jx.
+ *
+ * Note that this assumes that you are loading Jx
+ * from a js/ or lib/ folder in parallel to the
+ * images/ folder that contains the various images
+ * needed by Jx components.  If you have a different
+ * folder structure, you can define Jx's base
+ * by including the following before including
+ * the jxlib javascript file:
+ *
+ * (code)
+ * Jx = {
+ *    baseURL: 'some/path'
+ * }
+ * (end)
+ */
+if (!$defined(Jx.baseURL)) {
+  (function() {
+    var aScripts = document.getElementsByTagName('SCRIPT'),
+        i, s, n, file;
+    for (i = 0; i < aScripts.length; i++) {
+      s = aScripts[i].src;
+      n = s.lastIndexOf('/');
+      file = s.slice(n+1,s.length-1);
+      if (file.contains('jxlib')) {
+        Jx.baseURL = s.slice(0,n);
+        break;
+      }
+    }
+  })();
+}
+/**
+ * APIProperty: {Image} aPixel
+ * aPixel is a single transparent pixel and is the only image we actually
+ * use directly in JxLib code.  If you want to use your own transparent pixel
+ * image or use it from a different location than the install of jxlib
+ * javascript files, you can manually declare it before including jxlib code
+ * (code)
+ * Jx = {
+ *   aPixel: new Element('img', {
+ *     alt: '',
+ *     title: '',
+ *     width: 1,
+ *     height: 1,
+ *     src: 'path/to/a/transparent.png'
+ *   });
+ * }
+ * (end)
+ */
+if (!$defined(Jx.aPixel)) {
+  Jx.aPixel = new Element('img', {
+    alt:'',
+    title:'',
+    src: Jx.baseURL +'/a_pixel.png'
+  });
+}
+
+/**
+ * APIProperty: {Boolean} isAir
+ * indicates if JxLib is running in an Adobe Air environment.  This is
+ * normally auto-detected but you can manually set it by declaring the Jx
+ * namespace before including jxlib:
+ * (code)
+ * Jx = {
+ *   isAir: true
+ * }
+ * (end)
+ */
+if (!$defined(Jx.isAir)) {
+  (function() {
+    /**
+     * Determine if we're running in Adobe AIR.
+     */
+    var aScripts = document.getElementsByTagName('SCRIPT'),
+        src = aScripts[0].src;
+    if (src.contains('app:')) {
+      Jx.isAir = true;
+    } else {
+      Jx.isAir = false;
+    }
+  })();
+}
+
+/**
+ * APIMethod: setLanguage
+ * set the current language to be used by Jx widgets.  This uses the MooTools
+ * lang module.  If an invalid or missing language is requested, the default
+ * rules of MooTools.lang will be used (revert to en-US at time of writing).
+ *
+ * Parameters:
+ * {String} language identifier, the language to set.
+ */
+Jx.setLanguage = function(lang) {
+  Jx.lang = lang;
+  MooTools.lang.setLanguage(Jx.lang);
+};
+
+/**
+ * APIProperty: {String} lang
+ * Checks to see if Jx.lang is already set. If not, it sets it to the default
+ * 'en-US'. We will then set the Motools.lang language to this setting
+ * automatically.
+ *
+ * The language can be changed on the fly at anytime by calling
+ * Jx.setLanguage().
+ * By default all Jx.Widget subclasses will listen for the langChange event of
+ * the Mootools.lang class. It will then call a method, changeText(), if it
+ * exists on the particular widget. You will be able to disable listening for
+ * these changes by setting the Jx.Widget option useLang to false.
+ */
+if (!$defined(Jx.lang)) {
+  Jx.lang = 'en-US';
+}
+
+Jx.setLanguage(Jx.lang);
+
+/**
+ * APIMethod: applyPNGFilter
+ *
+ * Static method that applies the PNG Filter Hack for IE browsers
+ * when showing 24bit PNG's.  Used automatically for img tags with
+ * a class of png24.
+ *
+ * The filter is applied using a nifty feature of IE that allows javascript to
+ * be executed as part of a CSS style rule - this ensures that the hack only
+ * gets applied on IE browsers.
+ *
+ * The CSS that triggers this hack is only in the ie6.css files of the various
+ * themes.
+ *
+ * Parameters:
+ * object {Object} the object (img) to which the filter needs to be applied.
+ */
+Jx.applyPNGFilter = function(o) {
+    var t = Jx.aPixel.src, 
+        s;
+    if (o.src != t) {
+        s = o.src;
+        o.src = t;
+        o.runtimeStyle.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + s + "',sizingMethod='scale')";
+    }
+};
+
+/**
+ * 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
+/**
+ * APIMethod: addToImgQueue
+ * Request that an image be set to a DOM IMG element src attribute.  This puts
+ * the image into a queue and there are private methods to manage that queue
+ * and limit image loading to 2 at a time.
+ *
+ * Parameters:
+ * obj - {Object} an object containing an element and src
+ * property, where element is the element to update and src
+ * is the url to the image.
+ */
+Jx.addToImgQueue = function(obj) {
+    if (Jx.imgLoaded[obj.src]) {
+        //if this image was already requested (i.e. it's in cache) just set it directly
+        obj.element.src = obj.src;
+    } else {
+        //otherwise stick it in the queue
+        Jx.imgQueue.push(obj);
+        Jx.imgLoaded[obj.src] = true;
+    }
+    //start the queue management process
+    Jx.checkImgQueue();
+};
+
+/**
+ * APIMethod: checkImgQueue
+ *
+ * An internal method that ensures no more than 2 images are loading at a
+ * time.
+ */
+Jx.checkImgQueue = function() {
+    while (Jx.imagesLoading < 2 && Jx.imgQueue.length > 0) {
+        Jx.loadNextImg();
+    }
+};
+
+/**
+ * Method: loadNextImg
+ *
+ * An internal method actually populate the DOM element with the image source.
+ */
+Jx.loadNextImg = function() {
+    var obj = Jx.imgQueue.shift();
+    if (obj) {
+        ++Jx.imagesLoading;
+        obj.element.onload = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.onerror = function() {--Jx.imagesLoading;
+            Jx.checkImgQueue();
+        };
+        obj.element.src = obj.src;
+    }
+};
+
+/**
+ * APIMethod: getNumber
+ * safely parse a number and return its integer value.  A NaN value
+ * returns 0.  CSS size values are also parsed correctly.
+ *
+ * Parameters:
+ * n - {Mixed} the string or object to parse.
+ *
+ * Returns:
+ * {Integer} the integer value that the parameter represents
+ */
+Jx.getNumber = function(n, def) {
+    var result = n === null || isNaN(parseInt(n, 10)) ? (def || 0) : parseInt(n, 10);
+    return result;
+};
+
+/**
+ * APIMethod: getPageDimensions
+ * return the dimensions of the browser client area.
+ *
+ * Returns:
+ * {Object} an object containing a width and height property
+ * that represent the width and height of the browser client area.
+ */
+Jx.getPageDimensions = function() {
+    return {
+        width: window.getWidth(),
+        height: window.getHeight()
+    };
+};
+
+/**
+ * APIMethod: type
+ * safely return the type of an object using the mootools type system
+ *
+ * Returns:
+ * {Object} an object containing a width and height property
+ * that represent the width and height of the browser client area.
+ */
+Jx.type = function(obj) {
+  return typeof obj == 'undefined' ? false : obj.jxFamily || $type(obj);
+};
+
+(function($) {
+    // Wrapper for document.id
+
+    /**
+     * Class: Element
+     *
+     * Element is a global object provided by the mootools library.  The
+     * functions documented here are extensions to the Element object provided
+     * by Jx to make cross-browser compatibility easier to achieve.  Most of
+     * the methods are measurement related.
+     *
+     * While the code in these methods has been converted to use MooTools
+     * methods, there may be better MooTools methods to use to accomplish
+     * these things.
+     * Ultimately, it would be nice to eliminate most or all of these and find
+     * the MooTools equivalent or convince MooTools to add them.
+     *
+     * NOTE: Many of these methods can be replaced with mootools-more's
+     * Element.Measure
+     */
+    Element.implement({
+        /**
+         * APIMethod: getBoxSizing
+         * return the box sizing of an element, one of 'content-box' or
+         *'border-box'.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the box sizing of.
+         *
+         * Returns:
+         * {String} the box sizing of the element.
+         */
+        getBoxSizing: function() {
+            var result = 'content-box',
+                cm,
+                sizing;
+            if (Browser.Engine.trident || Browser.Engine.presto) {
+                cm = document["compatMode"];
+                if (cm == "BackCompat" || cm == "QuirksMode") {
+                    result = 'border-box';
+                } else {
+                    result = 'content-box';
+                }
+            } else {
+                if (arguments.length === 0) {
+                    node = document.documentElement;
+                }
+                sizing = this.getStyle("-moz-box-sizing");
+                if (!sizing) {
+                    sizing = this.getStyle("box-sizing");
+                }
+                result = (sizing ? sizing: 'content-box');
+            }
+            return result;
+        },
+        /**
+         * APIMethod: getContentBoxSize
+         * return the size of the content area of an element.  This is the
+         * size of the element less margins, padding, and borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the content size of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the content area of the measured element.
+         */
+        getContentBoxSize: function() {
+            var s = this.getSizes(['padding', 'border']);
+            return {
+                width: this.offsetWidth - s.padding.left - s.padding.right - s.border.left - s.border.right,
+                height: this.offsetHeight - s.padding.bottom - s.padding.top - s.border.bottom - s.border.top
+            };
+        },
+        /**
+         * APIMethod: getBorderBoxSize
+         * return the size of the border area of an element.  This is the size
+         * of the element less margins.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the border sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the border area of the measured element.
+         */
+        getBorderBoxSize: function() {
+            return {
+                width: this.offsetWidth,
+                height: this.offsetHeight
+            };
+        },
+
+        /**
+         * APIMethod: getMarginBoxSize
+         * return the size of the margin area of an element.  This is the size
+         * of the element plus margins.
+         *
+         * Parameters:
+         * elem - {Object} the element to get the margin sizing of.
+         *
+         * Returns:
+         * {Object} an object with two properties, width and height, that
+         * are the size of the margin area of the measured element.
+         */
+        getMarginBoxSize: function() {
+            var s = this.getSizes(['margin']);
+            return {
+                width: this.offsetWidth + s.margin.left + s.margin.right,
+                height: this.offsetHeight + s.margin.top + s.margin.bottom
+            };
+        },
+        /**
+         * APIMethod: 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 = {},
+                e,
+                n;
+            which.each(function(style) {
+                result[style] = {};
+                edges.each(function(edge) {
+                    e = (style == 'border') ? edge + '-width': edge;
+                    n = this.getStyle(style + '-' + e);
+                    result[style][edge] = n === null || isNaN(parseInt(n, 10)) ? 0: parseInt(n, 10);
+                },
+                this);
+            },
+            this);
+            return result;
+        },
+        /**
+         * APIMethod: setContentBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the content
+         * area of the element is the requested size and the resulting
+         * size of the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to set the content area of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setContentBoxSize: function(size) {
+            var m,
+                width,
+                height;
+            if (this.getBoxSizing() == 'border-box') {
+                m = this.measure(function() {
+                    return this.getSizes(['padding', 'border']);
+                });
+                if ($defined(size.width)) {
+                    width = size.width + m.padding.left + m.padding.right + m.border.left + m.border.right;
+                    if (width < 0) {
+                        width = 0;
+                    }
+                    this.setStyle('width', width);
+                }
+                if ($defined(size.height)) {
+                    height = size.height + m.padding.top + m.padding.bottom + m.border.top + m.border.bottom;
+                    if (height < 0) {
+                        height = 0;
+                    }
+                    this.setStyle('height', height);
+                }
+            } else {
+                if ($defined(size.width) && size.width >= 0) {
+                  this.setStyle('width', width);
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                  this.setStyle('height', height);
+                }
+            }
+        },
+        /**
+         * APIMethod: setBorderBoxSize
+         * set either or both of the width and height of an element to
+         * the provided size.  This function ensures that the border
+         * size of the element is the requested size and the resulting
+         * content areaof the element may be larger depending on padding and
+         * borders.
+         *
+         * Parameters:
+         * elem - {Object} the element to set the border size of.
+         * size - {Object} an object with a width and/or height property that
+         * is the size to set the content area of the element to.
+         */
+        setBorderBoxSize: function(size) {
+            var m, 
+                width, 
+                height;
+            if (this.getBoxSizing() == 'content-box') {
+                m = this.measure(function() {
+                    return this.getSizes();
+                });
+
+                if ($defined(size.width)) {
+                    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.setStyle('width', width);
+                }
+                if ($defined(size.height)) {
+                    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.setStyle('height', height);
+                }
+            } else {
+                if ($defined(size.width) && size.width >= 0) {
+                  this.setStyle('width', width);
+                }
+                if ($defined(size.height) && size.height >= 0) {
+                  this.setStyle('height', height);
+                }
+            }
+        },
+
+        /**
+         * APIMethod: descendantOf
+         * determines if the element is a descendent of the reference node.
+         *
+         * Parameters:
+         * node - {HTMLElement} the reference node
+         *
+         * Returns:
+         * {Boolean} true if the element is a descendent, false otherwise.
+         */
+        descendantOf: function(node) {
+            var parent = document.id(this.parentNode);
+            while (parent != node && parent && parent.parentNode && parent.parentNode != parent) {
+                parent = document.id(parent.parentNode);
+            }
+            return parent == node;
+        },
+
+        /**
+         * APIMethod: findElement
+         * search the parentage of the element to find an element of the given
+         * tag name.
+         *
+         * Parameters:
+         * type - {String} the tag name of the element type to search for
+         *
+         * Returns:
+         * {HTMLElement} the first node (this one or first parent) with the
+         * requested tag name or false if none are found.
+         */
+        findElement: function(type) {
+            var o = this,
+                tagName = o.tagName;
+            while (o.tagName != type && o && o.parentNode && o.parentNode != o) {
+                o = document.id(o.parentNode);
+            }
+            return o.tagName == type ? o: false;
+        }
+    });
+    /**
+     * Class: Array
+     * Extensions to the javascript array object
+     */
+    Array.implement({
+        /**
+         * APIMethod: 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 = this[a];
+            this[a] = this[b];
+            this[b] = temp;
+        }
+    });
+})(document.id || $);
+// End Wrapper for document.id
+/*
+---
+
+name: Jx.Styles
+
+description: A singleton object useful for dynamically creating and manipulating CSS styles
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Styles]
+
+...
+ */
+/**
+ * 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
+ *
+ * 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(),
+    /**
+     * APIMethod: getCssRule
+     * retrieve a reference to a CSS rule in a specific style sheet based on
+     * its selector.  If the rule does not exist, create it.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * 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),
+            rule = null,
+            i;
+        if (ss.indicies) {
+            i = ss.indicies.indexOf(selector);
+            if (i == -1) {
+                rule = this.insertCssRule(selector, '', styleSheetName);
+            } else {
+                if (Browser.Engine.trident) {
+                    rule = ss.sheet.rules[i];
+                } else {
+                    rule = ss.sheet.cssRules[i];
+                }
+            }
+        }
+        return rule;
+    },
+    /**
+     * APIMethod: insertCssRule
+     * insert a new dynamic rule into the given stylesheet.  If no name is
+     * given for the stylesheet then the default stylesheet is used.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * declaration - <String> CSS-formatted rules to include.  May be empty,
+     * in which case you may want to use the returned rule object to
+     * manipulate styles
+     * 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),
+            rule,
+            text = selector + " {" + declaration + "}",
+            index;
+        if (Browser.Engine.trident) {
+            if (declaration == '') {
+                //IE requires SOME text for the declaration. Passing '{}' will
+                //create an empty rule.
+                declaration = '{}';
+            }
+            index = ss.styleSheet.addRule(selector,declaration);
+            rule = ss.styleSheet.rules[index];
+        } else {
+            ss.sheet.insertRule(text, ss.indicies.length);
+            rule = ss.sheet.cssRules[ss.indicies.length];
+        }
+        ss.indicies.push(selector);
+        return rule;
+    },
+    /**
+     * APIMethod: removeCssRule
+     * removes a CSS rule from the named stylesheet.
+     *
+     * Parameters:
+     * selector - <String> the CSS selector for the rule
+     * styleSheetName - <String> the name of the sheet to remove the rule
+     * from,  or empty to remove them from the default sheet.
+     *
+     * Returns:
+     * <Boolean> true if the rule was removed, false if it was not.
+     */
+    removeCssRule: function (selector, styleSheetName) {
+        var ss = this.getDynamicStyleSheet(styleSheetName),
+            i = ss.indicies.indexOf(selector),
+            result = false;
+        ss.indicies.splice(i, 1);
+        if (Browser.Engine.trident) {
+            ss.removeRule(i);
+            result = true;
+        } else {
+            ss.sheet.deleteRule(i);
+            result = true;
+        }
+        return result;
+    },
+    /**
+     * APIMethod: 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').inject(document.head);
+            sheet.indicies = [];
+            this.dynamicStyleMap.set(name, sheet);
+        }
+        return this.dynamicStyleMap.get(name);
+    },
+    /**
+     * APIMethod: enableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to enable
+     */
+    enableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = false;
+    },
+    /**
+     * APIMethod: disableStyleSheet
+     * enable a style sheet
+     *
+     * Parameters:
+     * name - <String> the title of the stylesheet to disable
+     */
+    disableStyleSheet: function (name) {
+        this.getDynamicStyleSheet(name).disabled = true;
+    },
+    /**
+     * APIMethod: removeStyleSheet
+     * Removes a style sheet
+     *
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    removeStyleSheet: function (name) {
+      this.disableStyleSheet(name);
+      this.getDynamicStyleSheet(name).dispose();
+      this.dynamicStyleMap.erase(name);
+    },
+    /**
+     * APIMethod: isStyleSheetDefined
+     * Determined if the passed in name is a defined dynamic style sheet.
+     *
+     * Parameters:
+     * name = <String> the title of the stylesheet to remove
+     */
+    isStyleSheetDefined: function (name) {
+      return this.dynamicStyleMap.has(name);
+    }
+}))();/*
+---
+
+name: Jx.Object
+
+description: Base class for all other object in the JxLib framework.
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Object]
+
+...
+ */
+// $Id: object.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * Class: Jx.Object
+ * Base class for all other object in the JxLib framework. This class
+ * implements both mootools mixins Events and Options so the rest of the
+ * classes don't need to.
+ *
+ * The Initialization Pipeline:
+ * Jx.Object provides a default initialize method to construct new instances
+ * of objects that inherit from it.  No sub-class should override initialize
+ * unless you know exactly what you're doing.  Instead, the initialization
+ * pipeline provides an init() method that is intended to be overridden in
+ * sub-classes to provide class-specific initialization as part of the
+ * initialization pipeline.
+ *
+ * The basic initialization pipeline for a Jx.Object is to parse the
+ * parameters provided to initialize(), separate out options from other formal
+ * parameters based on the parameters property of the class, call init() and
+ * initialize plugins.
+ *
+ * Parsing Parameters:
+ * Because each sub-class no longer has an initialize method, it no longer has
+ * direct access to parameters passed to the constructor.  Instead, a
+ * sub-class is expected to provide a parameters attribute with an array of
+ * parameter names in the order expected.  Jx.Object will enumerate the
+ * attributes passed to its initialize method and automatically place them
+ * in the options object under the appropriate key (the value from the
+ * array).  Parameters not found will not be present or will be null.
+ *
+ * The default parameters are a single options object which is merged with
+ * the options attribute of the class.
+ *
+ * Calling Init:
+ * Jx.Object fires the event 'preInit' before calling the init() method,
+ * calls the init() method, then fires the 'postInit' event.  It is expected
+ * that most sub-class specific initialization will happen in the init()
+ * method.  A sub-class may hook preInit and postInit events to perform tasks
+ * in one of two ways.
+ *
+ * First, simply send onPreInit and onPostInit functions via the options
+ * object as follows (they could be standalone functions or functions of
+ * another object setup using .bind())
+ *
+ * (code)
+ * var preInit = function () {}
+ * var postInit = function () {}
+ *
+ * var options = {
+ *   onPreInit: preInit,
+ *   onPostInit: postInit,
+ *   ...other options...
+ * };
+ *
+ * var dialog = new Jx.Dialog(options);
+ * (end)
+ *
+ * The second method you can use is to override the initialize method
+ *
+ * (code)
+ * var MyClass = new Class({
+ *   Family: 'MyClass',
+ *   initialize: function() {
+ *     this.addEvent('preInit', this.preInit.bind(this));
+ *     this.addEvent('postInit', this.postInit.bind(this));
+ *     this.parent.apply(this, arguments);
+ *   },
+ *   preInit: function() {
+ *     // something just before init() is called
+ *   },
+ *   postInit: function() {
+ *     // something just after init() is called
+ *   },
+ *   init: function() {
+ *     this.parent();
+ *     // initialization code here
+ *   }
+ * });
+ * (end)
+ *
+ * When the object finishes initializing itself (including the plugin
+ * initialization) it will fire off the initializeDone event. You can hook
+ * into this event in the same way as the events mentioned above.
+ *
+ * Plugins:
+ * Plugins provide pieces of additional, optional, functionality. They are not
+ * necessary for the proper function of an object. All plugins should be
+ * located in the Jx.Plugin namespace and they should be further segregated by
+ * applicable object. While all objects can support plugins, not all of them
+ * have the automatic instantiation of applicable plugins turned on. In order
+ * to turn this feature on for an object you need to set the pluginNamespace
+ * property of the object. The following is an example of setting the
+ * property:
+ *
+ * (code)
+ * var MyClass = new Class({
+ *   Extends: Jx.Object,
+ *   pluginNamespace: 'MyClass'
+ * };
+ * (end)
+ *
+ * The absence of this property does not mean you cannot attach a plugin to an
+ * object. It simply means that you can't have Jx.Object create the
+ * plugin for you.
+ *
+ * There are four ways to attach a plugin to an object. First, simply
+ * instantiate the plugin yourself and call its attach() method (other class
+ * options left out for the sake of simplicity):
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid();
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * APlugin.attach(MyGrid);
+ * (end)
+ *
+ * Second, you can instantiate the plugin first and pass it to the object
+ * through the plugins array in the options object.
+ *
+ * (code)
+ * var APlugin = new Jx.Plugin.Grid.Selector();
+ * var MyGrid = new Jx.Grid({plugins: [APlugin]});
+ * (end)
+ *
+ * The third way is to pass the information needed to instantiate the plugin
+ * in the plugins array of the options object:
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: [{
+ *      name: 'Selector',
+ *      options: {}    //options needed to create this plugin
+ *   },{
+ *      name: 'Sorter',
+ *      options: {}
+ *   }]
+ * });
+ * (end)
+ *
+ * The final way, if the plugin has no options, is to pass the name of the
+ * plugin as a simple string in the plugins array.
+ *
+ * (code)
+ * var MyGrid = new Jx.Grid({
+ *   plugins: ['Selector','Sorter']
+ * });
+ * (end)
+ *
+ * Part of the process of initializing plugins is to call prePluginInit() and
+ * postPluginInit(). These events provide you access to the object just before
+ * and after the plugins are initialized and/or attached to the object using
+ * methods 2 and 3 above. You can hook into these in the same way that you
+ * hook into the preInit() and postInit() events.
+ *
+ * Destroying Jx.Object Instances:
+ * Jx.Object provides a destroy method that cleans up potential memory leaks
+ * when you no longer need an object.  Sub-classes are expected to implement
+ * a cleanup() method that provides specific cleanup code for each
+ * sub-class.  Remember to call this.parent() when providing a cleanup()
+ * method. Destroy will also fire off 2 events: preDestroy and postDestroy.
+ * You can hook into these methods in the same way as the init or plugin
+ * events.
+ *
+ * The Family Attribute:
+ * the Family attribute of a class is used internally by JxLib to identify Jx
+ * objects within mootools.  The actual value of Family is unimportant to Jx.
+ * If you do not provide a Family, a class will inherit it's base class family
+ * up to Jx.Object.  Family is useful when debugging as you will be able to
+ * identify the family in the firebug inspector, but is not as useful for
+ * coding purposes as it does not allow for inheritance.
+ *
+ * Events:
+ *
+ * preInit
+ * postInit
+ * prePluginInit
+ * postPluginInit
+ * initializeDone
+ * preDestroy
+ * postDestroy
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Object = new Class({
+    Family: "Jx.Object",
+    Implements: [Options, Events],
+    plugins: null,
+    pluginNamespace: 'Other',
+    /**
+     * Constructor: Jx.Object
+     * create a new instance of Jx.Object
+     *
+     * Parameters:
+     * options - {Object} optional parameters for creating an object.
+     */
+    parameters: ['options'],
+
+    options: {
+      /**
+       * Option: useLang
+       * Turns on this widget's ability to react to changes in
+       * the default language. Handy for changing text out on the fly.
+       *
+       * TODO: Should this be enabled or disabled by default?
+       */
+      useLang: true,
+      /**
+       * Option: plugins
+       * {Array} an array of plugins to add to the object.
+       */
+      plugins: null
+    },
+
+    bound: null,
+
+    initialize: function(){
+        this.plugins = new Hash();
+        this.bound = {};
+        //normalize arguments
+        var numArgs = arguments.length,
+            options = {},
+            parameters = this.parameters,
+            numParams,
+            index;
+
+        if (numArgs > 0) {
+            if (numArgs === 1
+                    && (Jx.type(arguments[0])==='object' || Jx.type(arguments[0])==='Hash')
+                    && parameters.length === 1
+                    && parameters[0] === 'options') {
+                options = arguments[0];
+            } else {
+                numParams = parameters.length;
+                index;
+                if (numParams <= numArgs) {
+                    index = numParams;
+                } else {
+                    index = numArgs;
+                }
+                for (var i = 0; i < index; i++) {
+                    if (parameters[i] === 'options') {
+                        $extend(options, arguments[i]);
+                    } else {
+                        options[parameters[i]] = arguments[i];
+                    }
+                }
+            }
+        }
+
+        this.setOptions(options);
+
+        this.bound.changeText = this.changeText.bind(this);
+        if (this.options.useLang) {
+            MooTools.lang.addEvent('langChange', this.bound.changeText);
+        }
+
+        this.fireEvent('preInit');
+        this.init();
+        this.fireEvent('postInit');
+        this.fireEvent('prePluginInit');
+        this.initPlugins();
+        this.fireEvent('postPluginInit');
+        this.fireEvent('initializeDone');
+    },
+
+    /**
+     * Method: initPlugins
+     * internal function to initialize plugins on object creation
+     */
+    initPlugins: function () {
+        var p;
+        // 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
+                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()])) {
+                        p = new Jx.Plugin[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                      } else {
+                        p = new Jx.Adaptor[this.pluginNamespace][plugin.name.capitalize()](plugin.options);
+                      }
+                        p.attach(this);
+                    } else if (Jx.type(plugin) === 'string') {
+                        //this is a name for a plugin.
+                      if ($defined(Jx.Plugin[this.pluginNamespace][plugin.capitalize()])) {
+                        p = new Jx.Plugin[this.pluginNamespace][plugin.capitalize()]();
+                      } else {
+                        p = new Jx.Adaptor[this.pluginNamespace][plugin.capitalize()]();
+                      }
+                        p.attach(this);
+                    }
+                }, this);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     * destroy a Jx.Object, safely cleaning up any potential memory
+     * leaks along the way.  Uses the cleanup method of an object to
+     * actually do the cleanup.
+     * Emits the preDestroy event before cleanup and the postDestroy event
+     * after cleanup.
+     */
+    destroy: function () {
+        this.fireEvent('preDestroy');
+        this.cleanup();
+        this.fireEvent('postDestroy');
+    },
+
+    /**
+     * Method: cleanup
+     * to be implemented by subclasses to do the actual work of destroying
+     * an object.
+     */
+    cleanup: function () {
+        //detach plugins
+        if (this.plugins.getLength > 0) {
+            this.plugins.each(function (plugin) {
+                plugin.detach();
+                plugin.destroy();
+            }, this);
+        }
+        this.plugins.empty();
+        if (this.options.useLang && $defined(this.bound.changeText)) {
+            MooTools.lang.removeEvent('langChange', this.bound.changeText);
+        }
+        this.bound = null;
+    },
+
+    /**
+     * Method: init
+     * virtual initialization method to be implemented by sub-classes
+     */
+    init: $empty,
+
+    /**
+     * APIMethod: registerPlugin
+     * This method is called by a plugin that has its attach method
+     * called.
+     *
+     * Parameters:
+     * plugin - the plugin to register with this object
+     */
+    registerPlugin: function (plugin) {
+        if (!this.plugins.has(plugin.name)) {
+            this.plugins.set(plugin.name,  plugin);
+        }
+    },
+    /**
+     * APIMethod: deregisterPlugin
+     * his method is called by a plugin that has its detach method
+     * called.
+     *
+     * Parameters:
+     * plugin - the plugin to deregister.
+     */
+    deregisterPlugin: function (plugin) {
+        if (this.plugins.has(plugin.name)) {
+            this.plugins.erase(plugin.name);
+        }
+    },
+
+    /**
+     * APIMethod: getPlugin
+     * Allows a developer to get a reference to a plugin with only the
+     * name of the plugin.
+     *
+     * Parameters:
+     * name - the name of the plugin as defined in the plugin's name property
+     */
+    getPlugin: function (name) {
+        if (this.plugins.has(name)) {
+            return this.plugins.get(name);
+        }
+    },
+
+    /**
+     * APIMethod: getText
+     *
+     * returns the text for a jx.widget used in a label.
+     *
+     * Parameters:
+     * val - <String> || <Function> || <Object> = { set: '', key: ''[, value: ''] } for a MooTools.lang object
+     */
+    getText: function(val) {
+      var result = '';
+      if (Jx.type(val) == 'string' || Jx.type(val) == 'number') {
+        result = val;
+      } else if (Jx.type(val) == 'function') {
+        result = val();
+      } else if (Jx.type(val) == 'object' && $defined(val.set) && $defined(val.key)) {
+        // COMMENT: just an idea how a localization object could be stored to the instance if needed somewhere else and options change?
+        this.i18n = val;
+        if($defined(val.value)) {
+          result = MooTools.lang.get(val.set, val.key)[val.value];
+        }else{
+          result = MooTools.lang.get(val.set, val.key);
+        }
+      }
+      return result;
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText : $empty,
+
+    /**
+     * Method: generateId
+     * Used to generate a unique ID for Jx Objects.
+     */
+    generateId: function(prefix){
+        prefix = (prefix) ? prefix : 'jx-';
+        var uid = $uid(this);
+        delete this.uid;
+        return prefix + uid;
+    }
+});
+/*
+---
+
+name: Locale.English.US
+
+description: Default translations of text strings used in JX for US english (en-US)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.English.US]
+
+...
+ */
+MooTools.lang.set('en-US', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Working ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'close this notice'
+	},
+	progressbar: {
+		messageText: 'Loading...',
+		progressText: '{progress} of {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Browse...'
+	},
+	'formatter.boolean': {
+		'true': 'Yes',
+		'false': 'No'
+	},
+	'formatter.currency': {
+		sign: '$'
+	},
+	'formatter.number': {
+		decimalSeparator: '.',
+    thousandsSeparator: ','
+	},
+	splitter: {
+		barToolTip: 'drag this bar to resize'
+	},
+  panelset: {
+    barToolTip: 'drag this bar to resize'
+  },
+	panel: {
+		collapseTooltip: 'Collapse/Expand Panel',
+    collapseLabel: 'Collapse',
+    expandLabel: 'Expand',
+    maximizeTooltip: 'Maximize Panel',
+    maximizeLabel: 'Maximize',
+    restoreTooltip: 'Restore Panel',
+    restoreLabel: 'Restore',
+    closeTooltip: 'Close Panel',
+    closeLabel: 'Close'
+	},
+	confirm: {
+		affirmativeLabel: 'Yes',
+    negativeLabel: 'No'
+	},
+	dialog: {
+		resizeToolTip: 'Resize dialog'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Cancel'
+	},
+	upload: {
+		buttonText: 'Upload Files'
+	},
+	'plugin.resize': {
+	  tooltip: 'Drag to resize, double click to auto-size.'
+	},
+  'plugin.editor': {
+    submitButton: 'Save',
+    cancelButton: 'Cancel'
+  }
+});/*
+---
+
+name: Jx.Widget
+
+description: Base class for all widgets (visual classes) in the JxLib Framework.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Stack
+ - Locale.English.US
+
+provides: [Jx.Widget]
+
+css:
+ - chrome
+
+images:
+ - spinner_16.gif
+ - spinner_24.gif
+
+optional:
+ - More/Spinner
+
+...
+ */
+// $Id: widget.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * 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
+ *
+ * o using an existing element, by object reference
+ *
+ * o using an HTML string
+ *
+ * o using a URL to get the content remotely
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Busy States:
+ *
+ * Any widget can be set as temporarily busy by calling the setBusy(true)
+ * method and then as idle by calling setBusy(false).  By default, busy
+ * widgets display an event mask that prevents them from being clicked and
+ * a spinner image with a message.  By default, there are two configurations
+ * for the spinner image and message, one for 'small' widgets like buttons
+ * and inputs, and one for larger widgets like panels and dialogs.  The
+ * framework automatically chooses the most appropriate configuration so you
+ * don't need to worry about it unless you want to customize it.
+ *
+ * You can disable this behaviour entirely by setting busyMask: false in the
+ * widget options when creating the widget.
+ *
+ * The mask and spinner functionality is provided by the MooTools Spinner
+ * class.  You can use any options documented for Spinner or Mask by setting
+ * the maskOptions option when creating a widget.
+ *
+ * Events:
+ * Jx.Widget has several events called during it's lifetime (in addition to
+ * the ones for its base class <Jx.Object>).
+ *
+ * preRender - called before rendering begins
+ * postRender - called after rendering is done
+ * deferRender - called when the deferRender option is set to true. The first
+ *      two events (pre- and post- render will NOT be called if deferRender is
+ *      set to true).
+ * contentLoaded - called after content has been loaded successfully
+ * contentLoadFailed - called if content can not be loaded for some reason
+ * addTo - called when a widget is added to another element or widget
+ * busy - called just before the busy mask is rendered/removed
+ *
+ * MooTools.Lang Keys:
+ * widget.busyMessage - sets the message of the waiter component when used
+ */
+Jx.Widget = new Class({
+    Family: "Jx.Widget",
+    Extends: Jx.Object,
+
+    options: {
+        /* Option: id
+         * (optional) {String} an HTML ID to assign to the widget
+         */
+        id: null,
+        /**
+         * 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,
+        /**
+         * Option: loadOnDemand
+         * {boolean} ajax content will only be loaded if the action is requested
+         * (like loading the content into a tab when activated)
+         */
+        loadOnDemand : false,
+        /**
+         * Option: cacheContent
+         * {boolean} determine whether the content should be loaded every time
+         * or if it's being cached
+         */
+        cacheContent : true,
+        /**
+         * Option: template
+         * the default HTML structure of this widget.  The default template
+         * is just a div with a class of jxWidget in the base class
+         */
+        template: '<div class="jxWidget"></div>',
+        /**
+         * Option: busyClass
+         * {String} a CSS class name to apply to busy mask when a widget is
+         * set as busy.  The default is 'jxBusy'.
+         */
+        busyClass: 'jxBusy',
+        /**
+         * Option: busyMask
+         * {Object} an object of options to pass to the MooTools Spinner
+         * when masking a busy object.  Set to false if you do not want
+         * to use the busy mask.
+         */
+        busyMask: {
+          'class': 'jxSpinner jxSpinnerLarge',
+          img: {'class':'jxSpinnerImage'},
+          content: {'class':'jxSpinnerContent'},
+          messageContainer: {'class':'jxSpinnerMessage'},
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          },
+          fx: true
+        },
+        /**
+         * Option: deferRender
+         * Used to defer rendering of a widget to a later time. Useful when
+         * we need data or other information not at hand at the moment
+         * of Widget instantiation. If set to true, the user will need to call
+         * render() at some later time. The only drawback to doing so will be
+         * the loss of preRender and postRender events.
+         */
+        deferRender: false
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxWidget'
+    }),
+
+    /**
+     * Property: busy
+     * {Boolean} is the widget currently busy?  This should be considered
+     * an internal property, use the API methods <Jx.Widget::setBusy> and
+     * <Jx.Widget::isBusy> to manage the busy state of a widget.
+     */
+    busy: false,
+
+    /**
+     * Property: domObj
+     * The HTMLElement that represents this widget.
+     */
+    domObj: null,
+
+    /**
+     * Property: contentIsLoaded
+     * {Boolean} 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,
+
+    /**
+     * Method: init
+     * sets up the base widget code and runs the render function.  Called
+     * by the Jx.Object framework for object initialization, should not be
+     * called directly.
+     */
+    init: function(){
+        if (!this.options.deferRender) {
+            this.fireEvent('preRender');
+            this.render();
+            this.fireEvent('postRender');
+        } else {
+            this.fireEvent('deferRender');
+        }
+    },
+
+    /**
+     * APIMethod: loadContent
+     *
+     * triggers loading of content based on options set for the current
+     * object.
+     *
+     * Parameters:
+     * element - {Object} the element to insert the content into
+     *
+     * Events:
+     *
+     * ContentLoader adds the following events to an object.  You can
+     * register for these events using the addEvent method or by providing
+     * callback functions via the on{EventName} properties in the options
+     * object
+     *
+     * contentLoaded - called when the content has been loaded.  If the
+     *     content is not asynchronous then this is called before loadContent
+     *     returns.
+     * contentLoadFailed - called if the content fails to load, primarily
+     *     useful when using the contentURL method of loading content.
+     */
+    loadContent: function(element) {
+        var c,
+            options = this.options,
+            timeout;
+        element = document.id(element);
+        if (options.content) {
+            if (options.content.domObj) {
+                c = document.id(options.content.domObj);
+            } else {
+                c = document.id(options.content);
+            }
+            if (c) {
+                if (options.content.addTo) {
+                    options.content.addTo(element);
+                } else {
+                    element.appendChild(c);
+                }
+                this.contentIsLoaded = true;
+            } else {
+                element.innerHTML = options.content;
+                this.contentIsLoaded = true;
+            }
+        } else if (options.contentURL) {
+            this.contentIsLoaded = false;
+            this.req = new Request({
+                url: options.contentURL,
+                method:'get',
+                evalScripts:true,
+                onRequest:(function() {
+                  if(options.loadOnDemand) {
+                      this.setBusy(true);
+                  }
+                }).bind(this),
+                onSuccess:(function(html) {
+                    element.innerHTML = html;
+                    this.contentIsLoaded = true;
+                    if (Jx.isAir){
+                        $clear(this.reqTimeout);
+                    }
+                    this.setBusy(false);
+                    this.fireEvent('contentLoaded', this);
+                }).bind(this),
+                onFailure: (function(){
+                    this.contentIsLoaded = true;
+                    this.fireEvent('contentLoadFailed', this);
+                    this.setBusy(false);
+                }).bind(this),
+                headers: {'If-Modified-Since': 'Sat, 1 Jan 2000 00:00:00 GMT'}
+            });
+            this.req.send();
+            if (Jx.isAir) {
+                timeout = $defined(options.timeout) ? options.timeout : 10000;
+                this.reqTimeout = this.checkRequest.delay(timeout, this);
+            }
+        } else {
+            this.contentIsLoaded = true;
+        }
+        if (options.contentId) {
+            element.id = this.options.contentId;
+        }
+        if (this.contentIsLoaded) {
+            this.fireEvent('contentLoaded', this);
+        }
+    },
+
+    /**
+     * APIMethod: position
+     * positions an element relative to another element
+     * based on the provided options.  Positioning rules are
+     * a string with two space-separated values.  The first value
+     * references the parent element and the second value references
+     * the thing being positioned.  In general, multiple rules can be
+     * considered by passing an array of rules to the horizontal and
+     * vertical options.  The position method will attempt to position
+     * the element in relation to the relative element using the rules
+     * specified in the options.  If the element does not fit in the
+     * viewport using the rule, then the next rule is attempted.  If
+     * all rules fail, the last rule is used and element may extend
+     * outside the viewport.  Horizontal and vertical rules are
+     * processed independently.
+     *
+     * Horizontal Positioning:
+     * Horizontal values are 'left', 'center', 'right', and numeric values.
+     * Some common rules are:
+     * o 'left left' is interpreted as aligning the left
+     * edge of the element to be positioned with the left edge of the
+     * reference element.
+     * o 'right right' aligns the two right edges.
+     * o 'right left' aligns the left edge of the element to the right of
+     * the reference element.
+     * o 'left right' aligns the right edge of the element to the left
+     * edge of the reference element.
+     *
+     * Vertical Positioning:
+     * Vertical values are 'top', 'center', 'bottom', and numeric values.
+     * Some common rules are:
+     * o 'top top' is interpreted as aligning the top
+     * edge of the element to be positioned with the top edge of the
+     * reference element.
+     * o 'bottom bottom' aligns the two bottom edges.
+     * o 'bottom top' aligns the top edge of the element to the bottom of
+     * the reference element.
+     * o 'top bottom' aligns the bottom edge of the element to the top
+     * edge of the reference element.
+     *
+     * Parameters:
+     * element - the element to position
+     * relative - the element to position relative to
+     * options - the positioning options, see list below.
+     *
+     * Options:
+     * horizontal - the horizontal positioning rule to use to position the
+     *    element.  Valid values are 'left', 'center', 'right', and a numeric
+     *    value.  The default value is 'center center'.
+     * vertical - the vertical positioning rule to use to position the
+     *    element.  Valid values are 'top', 'center', 'bottom', and a numeric
+     *    value.  The default value is 'center center'.
+     * offsets - an object containing numeric pixel offset values for the
+     *    object being positioned as top, right, bottom and left properties.
+     */
+    position: function(element, relative, options) {
+        element = document.id(element);
+        relative = document.id(relative);
+        var hor = $splat(options.horizontal || ['center center']),
+            ver = $splat(options.vertical || ['center center']),
+            offsets = $merge({top:0,right:0,bottom:0,left:0}, options.offsets || {}),
+            coords = relative.getCoordinates(), //top, left, width, height,
+            page, 
+            scroll,
+            size,
+            left,
+            rigbht,
+            top,
+            bottom,
+            n,
+            parts;
+        if (!document.id(element.parentNode) || element.parentNode ==  document.body) {
+            page = Jx.getPageDimensions();
+            scroll = document.id(document.body).getScroll();
+        } else {
+            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
+            // appear in the right part of the page.
+            coords.left += scroll.x;
+            coords.top += scroll.y;
+        } else if (element.parentNode == relative) {
+            // if the element is opening *inside* its relative, we want
+            // it to position correctly within it so top/left becomes
+            // the reference system.
+            coords.left = 0;
+            coords.top = 0;
+        }
+        size = element.getMarginBoxSize(); //width, height
+        if (!hor.some(function(opt) {
+            parts = opt.split(' ');
+            if (parts.length != 2) {
+                return false;
+            }
+            if (!isNaN(parseInt(parts[0],10))) {
+                n = parseInt(parts[0],10);
+                if (n>=0) {
+                    left = n;
+                } else {
+                    left = coords.left + coords.width + n;
+                }
+            } else {
+                switch(parts[0]) {
+                    case 'right':
+                        left = coords.left + coords.width;
+                        break;
+                    case 'center':
+                        left = coords.left + Math.round(coords.width/2);
+                        break;
+                    case 'left':
+                    default:
+                        left = coords.left;
+                        break;
+                }
+            }
+            if (!isNaN(parseInt(parts[1],10))) {
+                n = parseInt(parts[1],10);
+                if (n<0) {
+                    right = left + n;
+                    left = right - size.width;
+                } else {
+                    left += n;
+                    right = left + size.width;
+                }
+                right = coords.left + coords.width + parseInt(parts[1],10);
+                left = right - size.width;
+            } else {
+                switch(parts[1]) {
+                    case 'left':
+                        left -= offsets.left;
+                        right = left + size.width;
+                        break;
+                    case 'right':
+                        left += offsets.right;
+                        right = left;
+                        left = left - size.width;
+                        break;
+                    case 'center':
+                    default:
+                        left = left - Math.round(size.width/2);
+                        right = left + size.width;
+                        break;
+                }
+            }
+            return (left >= scroll.x && right <= scroll.x + page.width);
+        })) {
+            // all failed, snap the last position onto the page as best
+            // we can - can't do anything if the element is wider than the
+            // space available.
+            if (right > page.width) {
+                left = scroll.x + page.width - size.width;
+            }
+            if (left < 0) {
+                left = 0;
+            }
+        }
+        element.setStyle('left', left);
+
+        if (!ver.some(function(opt) {
+          parts = opt.split(' ');
+          if (parts.length != 2) {
+            return false;
+          }
+          if (!isNaN(parseInt(parts[0],10))) {
+            top = parseInt(parts[0],10);
+          } else {
+            switch(parts[0]) {
+              case 'bottom':
+                top = coords.top + coords.height;
+                break;
+              case 'center':
+                top = coords.top + Math.round(coords.height/2);
+                break;
+              case 'top':
+              default:
+                top = coords.top;
+                break;
+            }
+          }
+          if (!isNaN(parseInt(parts[1],10))) {
+              var n = parseInt(parts[1],10);
+              if (n>=0) {
+                  top += n;
+                  bottom = top + size.height;
+              } else {
+                  bottom = top + n;
+                  top = bottom - size.height;
+              }
+          } else {
+              switch(parts[1]) {
+                  case 'top':
+                      top -= offsets.top;
+                      bottom = top + size.height;
+                      break;
+                  case 'bottom':
+                      top += offsets.bottom;
+                      bottom = top;
+                      top = top - size.height;
+                      break;
+                  case 'center':
+                  default:
+                      top = top - Math.round(size.height/2);
+                      bottom = top + size.height;
+                      break;
+              }
+          }
+          return (top >= scroll.y && bottom <= scroll.y + page.height);
+      })) {
+          // all failed, snap the last position onto the page as best
+          // we can - can't do anything if the element is higher than the
+          // space available.
+          if (bottom > page.height) {
+              top = scroll.y + page.height - size.height;
+          }
+          if (top < 0) {
+              top = 0;
+          }
+      }
+      element.setStyle('top', top);
+
+      /* update the jx layout if necessary */
+      var jxl = element.retrieve('jxLayout');
+      if (jxl) {
+          jxl.options.left = left;
+          jxl.options.top = top;
+      }
+    },
+
+    /**
+     * Method: makeChrome
+     * create chrome on an element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to put the chrome on.
+     */
+    makeChrome: function(element) {
+        var c = new Element('div', {
+                'class':'jxChrome',
+                events: {
+                  contextmenu: function(e) { e.stop(); }
+                }
+              }),
+            src;
+
+        /* add to element so we can get the background image style */
+        element.adopt(c);
+
+        /* pick up any offset because of chrome, set
+         * through padding on the chrome object.  Other code can then
+         * make use of these offset values to fix positioning.
+         */
+        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 */
+        /* the app: protocol check is for adobe air support */
+        src = c.getStyle('backgroundImage');
+        if (src != null) {
+          if (!(src.contains('http://') || src.contains('https://') || src.contains('file://') || src.contains('app:/'))) {
+              src = null;
+          } else {
+              src = src.slice(4,-1);
+              /* this only seems to be IE and Opera, but they add quotes
+               * around the url - yuck
+               */
+              if (src.charAt(0) == '"') {
+                  src = src.slice(1,-1);
+              }
+
+              /* and remove the background image */
+              c.setStyle('backgroundImage', 'none');
+
+              /* make chrome */
+              ['TR','TL','BL','BR'].each(function(s){
+                  c.adopt(
+                      new Element('div',{
+                          'class':'jxChrome'+s
+                      }).adopt(
+                      new Element('img',{
+                          'class':'png24',
+                          src:src,
+                          alt: '',
+                          title: ''
+                      })));
+              }, this);
+          }
+        }
+        /* create a shim so selects don't show through the chrome */
+        if ($defined(window.IframeShim)) {
+          this.shim = new IframeShim(c, {className: 'jxIframeShim'});
+        }
+
+        /* remove from DOM so the other resizing logic works as expected */
+        c.dispose();
+        this.chrome = c;
+    },
+
+    /**
+     * APIMethod: showChrome
+     * show the chrome on an element.  This creates the chrome if necessary.
+     * If the chrome has been previously created and not removed, you can
+     * call this without an element and it will just resize the chrome within
+     * its existing element.  You can also pass in a different element from
+     * which the chrome was previously attached to and it will move the chrome
+     * to the new element.
+     *
+     * Parameters:
+     * element - {HTMLElement} the element to show the chrome on.
+     */
+    showChrome: function(element) {
+        element = document.id(element) || document.id(this);
+        if (element) {
+            if (!this.chrome) {
+                this.makeChrome(element);
+                element.addClass('jxHasChrome');
+            }
+            this.resizeChrome(element);
+            if (this.shim) {
+              this.shim.show();
+            }
+            if (element && this.chrome.parentNode !== element) {
+                element.adopt(this.chrome);
+                this.chrome.setStyle('z-index',-1);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: hideChrome
+     * removes the chrome from the DOM.  If you do this, you can't
+     * call showChrome with no arguments.
+     */
+    hideChrome: function() {
+        if (this.chrome) {
+            if (this.shim) {
+              this.shim.hide();
+            }
+            this.chrome.parentNode.removeClass('jxHasChrome');
+            this.chrome.dispose();
+        }
+    },
+
+    /**
+     * APIMethod: resizeChrome
+     * manually resize the chrome on an element.
+     *
+     * Parameters:
+     * element: {DOMElement} the element to resize the chrome for
+     */
+    resizeChrome: function(o) {
+        if (this.chrome && Browser.Engine.trident4) {
+            this.chrome.setContentBoxSize(document.id(o).getBorderBoxSize());
+            if (this.shim) {
+              this.shim.position();
+            }
+        }
+    },
+
+    /**
+     * APIMethod: addTo
+     * adds the object to the DOM relative to another element.  If you use
+     * 'top' or 'bottom' then the element is added to the relative
+     * element (becomes a child node).  If you use 'before' or 'after'
+     * then the element is inserted adjacent to the reference node.
+     *
+     * Parameters:
+     * reference - {Object} the DOM element or id of a DOM element
+     * to append the object relative to
+     * where - {String} where to append the element in relation to the
+     * reference node.  Can be 'top', 'bottom', 'before' or 'after'.
+     * The default is 'bottom'.
+     *
+     * Returns:
+     * the object itself, which is useful for chaining calls together
+     */
+    addTo: function(reference, where) {
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            if (reference instanceof Jx.Widget && $defined(reference.add)) {
+                reference.add(el);
+            } else {
+                ref = document.id(reference);
+                el.inject(ref,where);
+            }
+            this.fireEvent('addTo',this);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: toElement
+     * return a DOM element reference for this widget, by default this
+     * returns the local domObj reference.  This is used by the mootools
+     * framework with the document.id() or $() methods allowing you to
+     * manipulate a Jx.Widget sub class as if it were a DOM element.
+     *
+     * (code)
+     * var button = new Jx.Button({label: 'test'});
+     * $(button).inject('someElement');
+     * (end)
+     */
+    toElement: function() {
+        return this.domObj;
+    },
+
+    /**
+     * 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(),
+            element,
+            el;
+        if ($defined(container)){
+            element = container.set('html',template);
+        } else {
+            element = new Element('div',{html:template});
+        }
+        classes.each(function(klass){
+            el = element.getElement('.'+klass);
+            if ($defined(el)){
+                h.set(klass,el);
+            }
+        });
+        return h;
+    },
+
+    /**
+     * APIMethod: dispose
+     * remove the widget from the DOM
+     */
+    dispose: function(){
+        var el = document.id(this.addable) || document.id(this.domObj);
+        if (el) {
+            el.dispose();
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the widget and clean up any potential memory leaks
+     */
+    cleanup: function(){
+        if ($defined(this.domObj)) {
+            this.domObj.eliminate('jxWidget');
+            this.domObj.destroy();
+        }
+        if ($defined(this.addable)) {
+            this.addable.destroy();
+        }
+        if ($defined(this.domA)) {
+            this.domA.destroy();
+        }
+        if ($defined(this.classes)) {
+          this.classes.each(function(v, k) {
+            this[k] = null;
+          }, this);
+        }
+        this.elements.empty();
+        this.elements = null;
+        this.parent();
+    },
+
+    /**
+     * Method: render
+     * render the widget, internal function called by the framework.
+     */
+    render: function() {
+        this.elements = this.processElements(this.options.template,
+            this.classes);
+        if ($defined(this.domObj)) {
+          if ( $defined(this.options.id)) {
+            this.domObj.set('id', this.options.id);
+          }
+          //TODO: Should we autogenerate an id when one is not provided? like so...
+          // this.domObj.set('id',this.generateId());
+          this.domObj.store('jxWidget', this);
+        }
+    },
+
+    /**
+     * Property: elements
+     * a hash of elements extracted by processing the widget template
+     */
+    elements: null,
+
+    /**
+     * Method: processElements
+     * process the template of the widget and populate the elements hash
+     * with any objects.  Also set any object references based on the classes
+     * hash.
+     */
+    processElements: function(template, classes) {
+        var keys = classes.getValues();
+        elements = this.processTemplate(template, keys);
+        classes.each(function(value, key) {
+            if (key != 'elements' && elements.get(value)) {
+                this[key] = elements.get(value);
+            }
+        }, this);
+        return elements;
+    },
+
+    /**
+     * APIMethod: isBusy
+     * indicate if the widget is currently busy or not
+     *
+     * Returns:
+     * {Boolean} true if busy, false otherwise.
+     */
+    isBusy: function() {
+      return this.busy;
+    },
+
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy         - {Boolean} true to set the widget as busy, false to set it as idle.
+     * message      - {String||Jx Localized Object} (Optional) set a custom message directly
+     *                next to the loading icon. Default is {set:'Jx',key:'widget',value:'busyMessage'}
+     * forceMessage - {Boolean} force displaying a message for larger areas than 60px of height
+     */
+    setBusy: function(state, message, forceMessage) {
+      if (this.busy == state) {
+        return;
+      }
+      var options = this.options,
+          z,
+          size,
+          opts,
+          domObj = this.domObj;
+      message = $defined(message) ? message : {
+        set:'Jx',
+        key:'widget',
+        value:'busyMessage'
+      };
+      forceMessage = $defined(forceMessage) ? forceMessage : false;
+      this.busy = state;
+      this.fireEvent('busy', state);
+      if (state) {
+        if (options.busyClass) {
+          domObj.addClass(options.busyClass);
+        }
+        if (options.busyMask && domObj.spin) {
+          /* put the spinner above the element in the z-index */
+          z = Jx.getNumber(domObj.getStyle('z-index'));
+          opts = {
+            style: {
+              'z-index': z+1
+            }
+          };
+          /* switch to the small size if the element is less than
+           * 60 pixels high
+           */
+          size = domObj.getBorderBoxSize();
+          if (size.height < 60 || forceMessage) {
+            opts['class'] = 'jxSpinner jxSpinnerSmall';
+            opts.img = null;
+            opts.message = new Element('p',{
+              'class':'jxSpinnerMessage',
+              html: '<span class="jxSpinnerImage"></span>'+this.getText(message)
+            });
+          }
+          opts = $merge(options.busyMask, opts);
+          domObj.get('spinner', opts).show(!options.busyMask.fx);
+        }
+      } else {
+        if (options.busyClass) {
+          domObj.removeClass(options.busyClass);
+        }
+        if (options.busyMask && this.domObj.unspin) {
+          domObj.get('spinner').hide(!options.busyMask.fx);
+        }
+      }
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - {string} the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    },
+
+    /**
+     * APIMethod: stack
+     * stack this widget in the z-index of the DOM relative to other stacked
+     * objects.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to stack.  By default, the
+     * element to stack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    stack: function(el) {
+      Jx.Stack.stack(el || document.id(this));
+    },
+
+    /**
+     * APIMethod: unstack
+     * remove this widget from the stack.
+     *
+     * Parameters:
+     * el - {DOMElement} optional, the element to unstack.  By default, the
+     * element to unstack is the one returned by the toElement method which
+     * is typically this.domObj unless the method has been overloaded.
+     */
+    unstack: function(el) {
+      Jx.Stack.unstack(el = el || document.id(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.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);
+            }
+        }
+    });
+}/*
+---
+
+name: Jx.Selection
+
+description: A class to manage selection across multiple list objects
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Selection]
+
+...
+ */
+// $Id: selection.js 976 2010-09-02 18:57:12Z pagameba $
+/**
+ * Class: Jx.Selection
+ *
+ * Manage selection of objects.
+ *
+ * Example:
+ * (code)
+ * var selection = new Jx.Selection();
+ * (end)
+ *
+ * Events:
+ * select - fired when an item is added to the selection.  This event may be
+ *    changed by passing the eventToFire option when creating the selection
+ *    object.
+ * unselect - fired when an item is removed from the selection.  This event
+ *    may be changed by passing the eventToFire option when creating the
+ *    selection object.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+
+Jx.Selection = new Class({
+    Family: 'Jx.Selection',
+    Extends: Jx.Object,
+    options: {
+        /**
+         * Option: eventToFire
+         * Allows the developer to change the event that is fired in case one
+         * object is using multiple selectionManager instances.  The default
+         * is to use 'select' and 'unselect'.  To modify the event names,
+         * pass different values:
+         * (code)
+         * new Jx.Selection({
+         *   eventToFire: {
+         *     select: 'newSelect',
+         *     unselect: 'newUnselect'
+         *   }
+         * });
+         * (end)
+         */
+        eventToFire: {
+            select: 'select',
+            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
+    },
+
+    /**
+     * Property: selection
+     * {Array} an array holding the current selection
+     */
+    selection: null,
+
+    /**
+     * Constructor: Jx.Selection
+     * create a new instance of Jx.Selection
+     *
+     * Parameters:
+     * options - {Object} options for the new instance
+     */
+    init: function () {
+        this.selection = [];
+        this.parent();
+    },
+
+    cleanup: function() {
+      this.selection = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: defaultSelect
+     * select an item if the selection does not yet contain the minimum
+     * number of selected items.  Uses <Jx.Selection::select> to select
+     * the item, so the same criteria is applied to the item if it is
+     * to be selected.
+     */
+    defaultSelect: function(item) {
+        if (this.selection.length < this.options.minimumSelection) {
+            this.select(item);
+        }
+    },
+
+    /**
+     * APIMethod: select
+     * select an item.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    select: function (item) {
+        var options = this.options,
+            selection = this.selection;
+        item = document.id(item);
+        if (options.selectMode === 'multiple') {
+            if (selection.contains(item)) {
+                this.unselect(item);
+            } else {
+                document.id(item).addClass(options.selectClass);
+                selection.push(item);
+                this.fireEvent(options.eventToFire.select, item);
+            }
+        } else if (options.selectMode == 'single') {
+            if (!this.selection.contains(item)) {
+                document.id(item).addClass(options.selectClass);
+                selection.push(item);
+                if (selection.length > 1) {
+                    this.unselect(selection[0]);
+                }
+                this.fireEvent(options.eventToFire.select, item);
+            } else {
+                if (options.selectToggle) {
+                  this.unselect(item);
+                }
+            }
+        }
+    },
+
+    /**
+     * APIMethod: unselect
+     * remove an item from the selection.  The item must already be in the
+     * selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     */
+    unselect: function (item) {
+        var selection = this.selection,
+            options = this.options;
+        if (selection.contains(item) &&
+            selection.length > options.minimumSelection) {
+            document.id(item).removeClass(options.selectClass);
+            selection.erase(item);
+            this.fireEvent(options.eventToFire.unselect, [item, this]);
+        }
+    },
+
+    /**
+     * APIMethod: selected
+     * returns the items in the current selection.
+     *
+     * Returns:
+     * {Array} an array of DOM elements in the current selection
+     */
+    selected: function () {
+        return this.selection;
+    },
+
+    /**
+     * APIMethod: isSelected
+     * test if an item is in the current selection.
+     *
+     * Parameters:
+     * item - {DOMElement} a DOM element or an element ID.
+     *
+     * Returns:
+     * {Boolean} true if the current selection contains the item, false
+     * otherwise
+     */
+    isSelected: function(item) {
+        return this.selection.contains(item);
+    }
+});/*
+---
+
+name: Jx.List
+
+description: A class that is used to manage lists of DOM elements
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Selection
+
+provides: [Jx.List]
+
+...
+ */
+// $Id: list.js 976 2010-09-02 18:57:12Z pagameba $
+/**
+ * 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)
+ * var list = new Jx.List('container',{
+ *   hover: true,
+ *   select: true,
+ *   onSelect: function(el) {
+ *     alert(el.get('html'));
+ *   }
+ * });
+ * list.add(new Element('li', {html:'1'}));
+ * list.add(new Element('li', {html:'2'}));
+ * list.add(new Element('li', {html:'3'}));
+ *
+ * (end)
+ *
+ * Events:
+ * 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,
+    /**
+     * Constructor: Jx.List
+     * create a new instance of Jx.List
+     *
+     * Parameters:
+     * container - {Mixed} an element reference or id of an element that will
+     * contain the items in the list
+     * options - {Object} an object containing optional parameters
+     * selection - {<Jx.Selection>} null or a Jx.Selection object. If the
+     * select option is set to true, then list will use this selection object
+     * to track selections or create its own if no selection object is
+     * supplied.
+     */
+    parameters: ['container', 'options', 'selection'],
+    /* does this object own the selection object (and should clean it up) */
+    ownsSelection: false,
+    /**
+     * APIProperty: container
+     * 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: {
+        /**
+         * Option: items
+         * an array of items to add to the list right away
+         */
+        items: null,
+        /**
+         * Option: hover
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined hoverClass if set and mouseenter/mouseleave
+         * events will be emitted when the user hovers over and out of elements
+         */
+        hover: false,
+        /**
+         * Option: hoverClass
+         * the CSS class name to add to the wrapper element when the mouse is
+         * over an item
+         */
+        hoverClass: 'jxHover',
+
+        /**
+         * Option: press
+         * {Boolean} default false.  If set to true, the wrapper element will
+         * obtain the defined pressClass if set and mousedown/mouseup
+         * events will be emitted when the user clicks on elements
+         */
+        press: false,
+        /**
+         * Option: 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 false.  If set to true, the wrapper element will
+         * obtain the defined selectClass if set and select/unselect events
+         * will be emitted when items are selected and unselected.  For other
+         * selection objects, see <Jx.Selection>
+         */
+        select: false
+    },
+
+    /**
+     * Method: init
+     * internal method to initialize this object
+     */
+    init: function() {
+        this.container = document.id(this.options.container);
+        this.container.store('jxList', this);
+
+        var target = this,
+            options = this.options,
+            isEnabled = function(el) {
+                var item = el.retrieve('jxListTargetItem') || el;
+                return !item.hasClass('jxDisabled');
+            },
+            isSelectable = function(el) {
+                var item = el.retrieve('jxListTargetItem') || el;
+                return !item.hasClass('jxUnselectable');
+            };
+        this.bound = $merge(this.bound, {
+            mousedown: function() {
+                if (isEnabled(this)) {
+                    this.addClass(options.pressClass);
+                    target.fireEvent('mousedown', this, target);
+                }
+            },
+            mouseup: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(options.pressClass);
+                    target.fireEvent('mouseup', this, target);
+                }
+            },
+            mouseenter: function() {
+                if (isEnabled(this)) {
+                    this.addClass(options.hoverClass);
+                    target.fireEvent('mouseenter', this, target);
+                }
+            },
+            mouseleave: function() {
+                if (isEnabled(this)) {
+                    this.removeClass(options.hoverClass);
+                    target.fireEvent('mouseleave', this, target);
+                }
+            },
+            keydown: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.addClass('jxPressed');
+                }
+            },
+            keyup: function(e) {
+                if (e.key == 'enter' && isEnabled(this)) {
+                    this.removeClass('jxPressed');
+                }
+            },
+            click: function (e) {
+                if (target.selection &&
+                    isEnabled(this) &&
+                    isSelectable(this)) {
+                    target.selection.select(this, target);
+                }
+                target.fireEvent('click', this, target);
+            },
+            select: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('select', itemTarget);
+                }
+            },
+            unselect: function(item) {
+                if (isEnabled(item)) {
+                    var itemTarget = item.retrieve('jxListTargetItem') || item;
+                    target.fireEvent('unselect', itemTarget);
+                }
+            },
+            contextmenu: function(e) {
+              var cm = this.retrieve('jxContextMenu');
+              if (cm) {
+                cm.show(e);
+                this.removeClass(options.pressClass);
+              }
+              e.stop();
+            }
+        });
+
+        if (options.selection) {
+            this.setSelection(options.selection);
+            options.select = true;
+        } else if (options.select) {
+            this.selection = new Jx.Selection(options);
+            this.ownsSelection = true;
+        }
+
+        if ($defined(options.items)) {
+            this.add(options.items);
+        }
+    },
+
+    /**
+     * Method: cleanup
+     * destroy the list and release anything it references
+     */
+    cleanup: function() {
+        this.container.getChildren().each(function(item){
+            this.remove(item);
+        }, this);
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents();
+            this.selection.destroy();
+        }
+        this.setSelection(null);
+        this.container.eliminate('jxList');
+        var bound = this.bound;
+        bound.mousedown=null;
+        bound.mouseup=null;
+        bound.mouseenter=null;
+        bound.mouseleave=null;
+        bound.keydown=null;
+        bound.keyup=null;
+        bound.click=null;
+        bound.select=null;
+        bound.unselect=null;
+        bound.contextmenu=null;
+        this.parent();
+    },
+
+    /**
+     * 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 (Jx.type(item) == 'array') {
+            item.each(function(what){
+              this.add(what, position);
+            }.bind(this) );
+            return;
+        }
+        /* the element being wrapped */
+        var el = document.id(item),
+            target = el.retrieve('jxListTarget') || el,
+            bound = this.bound,
+            options = this.options,
+            container = this.container;
+        if (target) {
+            target.store('jxListTargetItem', el);
+            target.addEvents({
+              contextmenu: this.bound.contextmenu
+            });
+            if (options.press && options.pressClass) {
+                target.addEvents({
+                    mousedown: bound.mousedown,
+                    mouseup: bound.mouseup,
+                    keyup: bound.keyup,
+                    keydown: bound.keydown
+                });
+            }
+            if (options.hover && options.hoverClass) {
+                target.addEvents({
+                    mouseenter: bound.mouseenter,
+                    mouseleave: bound.mouseleave
+                });
+            }
+            if (this.selection) {
+                target.addEvents({
+                    click: bound.click
+                });
+            }
+            if ($defined(position)) {
+                if ($type(position) == 'number') {
+                    if (position < container.childNodes.length) {
+                        el.inject(container.childNodes[position],'before');
+                    } else {
+                        el.inject(container, 'bottom');
+                    }
+                } else if (container.hasChild(position)) {
+                    el.inject(position,'after');
+                }
+                this.fireEvent('add', item, this);
+            } else {
+                el.inject(container, 'bottom');
+                this.fireEvent('add', item, this);
+            }
+            if (this.selection) {
+                this.selection.defaultSelect(el);
+            }
+        }
+    },
+    /**
+     * APIMethod: 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) {
+        var el = document.id(item),
+            target;
+        if (el && this.container.hasChild(el)) {
+            this.unselect(el, true);
+            el.dispose();
+            target = el.retrieve('jxListTarget') || el;
+            target.removeEvents(this.bound);
+            this.fireEvent('remove', item, this);
+            return item;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: 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
+     * context - {object} the context to execute the function in, null by
+     * default.
+     */
+    each: function(f, context) {
+        $A(this.container.childNodes).each(f, context);
+    },
+    /**
+     * 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);
+    },
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object that this list will use for selection
+     * events.
+     *
+     * Parameters:
+     * {<Jx.Selection>} the selection object, or null to remove it.
+     */
+    setSelection: function(selection) {
+        var sel = this.selection;
+        if (sel == selection) return;
+
+        if (sel) {
+            sel.removeEvents(this.bound);
+            if (this.ownsSelection) {
+                sel.destroy();
+                this.ownsSelection = false;
+            }
+        }
+
+        this.selection = selection;
+        if (selection) {
+            selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+    }
+
+});/*
+---
+
+name: Jx.Stack
+
+description: A singleton object for managing a global z-index stack for widgets that need to order themselves in the z-index of the page relative to other such widgets.
+
+license: MIT-style license.
+
+requires:
+ - Jx
+
+provides: [Jx.Stack]
+
+...
+ */
+/**
+ * Class: Jx.Stack
+ * Manage the zIndex of widgets
+ *
+ * This is a singleton and should be called directly, like so:
+ *
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010 Paul Spencer
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Stack = new(new Class({
+  /**
+   * Property: els
+   * {Array} the elements in the stack
+   */
+  els: [],
+
+  /**
+   * Property: base
+   * {Integer} the base z-index value of the first element in the stack
+   */
+  base: 1000,
+
+  /**
+   * Property: increment
+   * {Integer} the amount to increment the z-index between elements of the
+   * stack
+   */
+  increment: 100,
+
+  /**
+   * APIMethod: stack
+   * push an element onto the stack and set its z-index appropriately
+   *
+   * Parameters:
+   * el - {DOMElement} a DOM element to push on the stack
+   */
+  stack: function(el) {
+    this.unstack(el);
+    this.els.push(el);
+    this.setZIndex(el, this.els.length-1);
+  },
+
+  /**
+   * APIMethod: unstack
+   * pull an element off the stack and reflow the z-index of the remaining
+   * elements in the stack if necessary
+   *
+   * Parameters:
+   * el - {DOMElement} the DOM element to pull off the stack
+   */
+  unstack: function(el) {
+    var elements = this.els;
+    if (elements.contains(el)) {
+      el.setStyle('z-index', '');
+      var idx = elements.indexOf(el);
+      elements.erase(el);
+      for (var i=idx; i<elements.length; i++) {
+        this.setZIndex(elements[i], i);
+      }
+    }
+  },
+
+  /**
+   * Method: setZIndex
+   * set the z-index of an element based on its position in the stack
+   *
+   * Parameters:
+   * el - {DOMElement} the element to set the z-index for
+   * idx - {Integer} optional, the index to assume for this object
+   */
+  setZIndex: function(obj, idx) {
+    idx = idx || this.els.indexOf(obj);
+    if (idx !== false) {
+      document.id(obj).setStyle('z-index', this.base + (idx*this.increment));
+    }
+  }
+
+}))();/*
+name: Locale.German
+
+description: Default translations of text strings used in JX for German (Germany) (de-DE)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.German]
+
+...
+ */
+
+MooTools.lang.set('de-DE', 'Date', {
+  // need to overwrite 'M&auml;rz' to 'März' for jx.select fields
+  months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']
+});
+
+MooTools.lang.set('de-DE', 'Jx', {
+
+	'widget': {
+		busyMessage: 'Arbeite ...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'Notiz schließen'
+	},
+	progressbar: {
+		messageText: 'Lade...',
+		progressText: '{progress} von {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Durchsuchen...'
+	},
+	'formatter.boolean': {
+		'true': 'Ja',
+		'false': 'Nein'
+	},
+	'formatter.currency': {
+		sign: '€'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: '.'
+	},
+	splitter: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panelset: {
+		barToolTip: 'Ziehen Sie diese Leiste um die Größe zu verändern'
+	},
+	panel: {
+        collapseTooltip: 'Panel ein-/ausklappen', //colB
+        collapseLabel: 'Einklappen',  //colM
+        expandLabel: 'Ausklappen', //colM
+        maximizeTooltip: 'Panel maximieren',
+        maximizeLabel: 'maximieren',
+        restoreTooltip: 'Panel wieder herstellen', //maxB
+        restoreLabel: 'wieder herstellen', //maxM
+        closeTooltip: 'Panel schließen', //closeB
+        closeLabel: 'Schließen' //closeM
+	},
+	confirm: {
+		affirmativeLabel: 'Ja',
+    negativeLabel: 'Nein'
+	},
+	dialog: {
+		label: 'Neues Fenster'
+	},
+	message: {
+		okButton: 'Ok'
+	},
+	prompt: {
+		okButton: 'Ok',
+		cancelButton: 'Abbrechen'
+	},
+	upload: {
+		buttonText: 'Dateien hochladen'
+	},
+	'plugin.resize': {
+	  tooltip: 'Klicken um Größe zu verändern. Doppelklick für automatische Anpassung.'
+	},
+  'plugin.editor': {
+    submitButton: 'Speichern',
+    cancelButton: 'Abbrechen'
+  }
+});/*
+---
+
+name: Locale.Russian
+
+description: Default translations of text strings used in JX for Russia (Russia) (ru-RU)
+
+license: MIT-style license.
+
+requires:
+ - More/Lang
+
+provides: [Locale.Russian]
+
+...
+ */
+MooTools.lang.set('ru-RU-unicode', 'Jx', {
+	
+	'widget': {
+		busyMessage: 'Обработка...'
+	},
+	'colorpalette': {
+		alphaLabel: 'alpha (%)'
+	},
+	notice: {
+		closeTip: 'закрыть Ñ?то Ñ?ообщение'
+	},
+	progressbar: {
+		messageText: 'Загрузка...',
+		progressText: '{progress} из {total}'
+	},
+	field: {
+		requiredText: '*'
+	},
+	file: {
+		browseLabel: 'Выбрать...'
+	},
+	'formatter.boolean': {
+		'true': 'Да',
+		'false': 'Ð?ет'
+	},
+	'formatter.currency': {
+		sign: 'Ñ€.'
+	},
+	'formatter.number': {
+		decimalSeparator: ',',
+    thousandsSeparator: ' '
+	},
+	splitter: {
+		barToolTip: 'потÑ?ни, чтобы изменить размер'
+	},
+	panelset: {
+		barToolTip: 'потÑ?ни, чтобы изменить размер'
+	},
+	panel: {
+		collapseTooltip: 'Свернуть/Развернуть Панель',
+    collapseLabel: 'Свернуть',
+    expandLabel: 'Развернуть',
+    maximizeTooltip: 'Увеличить Панель',
+    maximizeLabel: 'Увеличить',
+    restoreTooltip: 'ВоÑ?Ñ?тановить Панель',
+    restoreLabel: 'ВоÑ?Ñ?тановить',
+    closeTooltip: 'Закрыть Панель',
+    closeLabel: 'Закрыть'
+	},
+	confirm: {
+		affirmativeLabel: 'Да',
+    negativeLabel: 'Ð?ет'
+	},
+	dialog: {
+		resizeToolTip: 'Изменить размер'
+	},
+	message: {
+		okButton: 'Ок'
+	},
+	prompt: {
+		okButton: 'Ок',
+		cancelButton: 'Отмена'
+	},
+	upload: {
+		buttonText: 'Загрузка файла'
+	},
+	'plugin.resize': {
+	  tooltip: 'ПотÑ?ни, чтобы изменить, двойной щелчок длÑ? авто размера.'
+	},
+  'plugin.editor': {
+    submitButton: 'Сохранить',
+    cancelButton: 'Отмена'
+  }
+});/*
+---
+
+name: Jx.Record
+
+description: The basic record implementation. A store uses records to handle and manipulate data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Record]
+
+...
+ */
+// $Id: record.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Record
+ *
+ * Extends: <Jx.Object>
+ *
+ * This class is used as a representation (or container) for a single row
+ * of data in a <Jx.Store>. It is not usually directly instantiated by the
+ * developer but rather by the store itself.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Record = new Class({
+
+    Extends: Jx.Object,
+    Family: 'Jx.Record',
+
+    options: {
+        /**
+         * Option: separator
+         * The separator to pass to the comparator
+         * constructor (<Jx.Compare>) - defaults to '.'
+         */
+        separator : '.',
+
+        primaryKey: null
+    },
+    /**
+     * Property: data
+     * The data for this record
+     */
+    data: null,
+    /**
+     * Property: state
+     * used to determine the state of this record. When not null (meaning no
+     * changes were made) this should be one of
+     *
+     * - Jx.Record.UPDATE
+     * - Jx.Record.DELETE
+     * - Jx.Record.INSERT
+     */
+    state: null,
+    /**
+     * Property: columns
+     * Holds a reference to the columns for this record. These are usually
+     * passed to the record from the store. This should be an array of objects
+     * where the objects represent the columns. The object should take the form:
+     *
+     * (code)
+     * {
+     *     name: <column name>,
+     *     type: <column type>,
+     *     ..additional options required by the record implementation...
+     * }
+     * (end)
+     *
+     * The type of the column should be one of alphanumeric, numeric, date,
+     * boolean, or currency.
+     */
+    columns: null,
+
+    parameters: ['store', 'columns', 'data', 'options'],
+
+    init: function () {
+        this.parent();
+        if ($defined(this.options.columns)) {
+            this.columns = this.options.columns;
+        }
+
+        if ($defined(this.options.data)) {
+            this.processData(this.options.data);
+        } else {
+            this.data = new Hash();
+        }
+
+        if ($defined(this.options.store)) {
+            this.store = this.options.store;
+        }
+
+    },
+    /**
+     * APIMethod: get
+     * returns the value of the requested column. Can be programmed to handle
+     * pseudo-columns (such as the primaryKey column implemented in this base
+     * record).
+     *
+     * Parameters:
+     * column - the string, index, or object of the requested column
+     */
+    get: function (column) {
+        var type = Jx.type(column);
+        if (type !== 'object') {
+            if (column === 'primaryKey') {
+                column = this.resolveCol(this.options.primaryKey);
+            } else {
+                column = this.resolveCol(column);
+            }
+        }
+        if (this.data.has(column.name)) {
+            return this.data.get(column.name);
+        } else {
+            return null;
+        }
+    },
+    /**
+     * APIMethod: set
+     * Sets a given value into the requested column.
+     *
+     *  Parameters:
+     *  column - the object, index, or string name of the target column
+     *  data - the data to add to the column
+     */
+    set: function (column, data) {
+        var type = Jx.type(column),
+            oldValue;
+        if (type !== 'object') {
+            column = this.resolveCol(column);
+        }
+
+        if (!$defined(this.data)) {
+            this.data = new Hash();
+        }
+
+        oldValue = this.get(column);
+        this.data.set(column.name, data);
+        this.state = Jx.Record.UPDATE;
+        return [column.name, oldValue, data];
+        //this.store.fireEvent('storeColumnChanged', [this, column.name, oldValue, data]);
+
+    },
+    /**
+     * APIMethod: equals
+     * Compares the value of a particular column with a given value
+     *
+     * Parameters:
+     * column - the column to compare with (either column name or index)
+     * value - the value to compare to.
+     *
+     * Returns:
+     * True | False depending on the outcome of the comparison.
+     */
+    equals: function (column, value) {
+        if (column === 'primaryKey') {
+            column = this.resolveCol(this.options.primaryKey);
+        } else {
+            column = this.resolveCol(column);
+        }
+        if (!this.data.has(column.name)) {
+            return null;
+        } else {
+            if (!$defined(this.comparator)) {
+                this.comparator = new Jx.Compare({
+                    separator : this.options.separator
+                });
+            }
+            var fn = this.comparator[column.type].bind(this.comparator);
+            return (fn(this.get(column), value) === 0);
+        }
+    },
+    /**
+     * Method: processData
+     * This method takes the data passed in and puts it into the form the
+     * record needs it in. This default implementation does nothing but
+     * assign the data to the data property but it can be overridden in
+     * subclasses to massge the data in any way needed.
+     *
+     * Parameters:
+     * data - the data to process
+     */
+    processData: function (data) {
+        this.data = $H(data);
+    },
+
+    /**
+     * Method: resolveCol
+     * Determines which column is being asked for and returns it.
+     *
+     * Parameters:
+     * col - a number referencing a column in the store
+     *
+     * Returns:
+     * the column object referred to
+     */
+    resolveCol : function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.columns[col];
+        } else if (t === 'string') {
+            this.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;
+    },
+    /**
+     * APIMethod: asHash
+     * Returns the data for this record as a Hash
+     */
+    asHash: function() {
+        return this.data;
+    }
+});
+
+Jx.Record.UPDATE = 1;
+Jx.Record.DELETE = 2;
+Jx.Record.INSERT = 3;/*
+---
+
+name: Jx.Store
+
+description: An implementation of a basic data store.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Record
+
+provides: [Jx.Store]
+
+...
+ */
+// $Id: store.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store
+ *
+ * Extends: <Jx.Object>
+ *
+ * This class is the  store. It keeps track of data. It
+ * allows adding, deleting, iterating, sorting etc...
+ *
+ * For the most part the store is pretty "dumb" meaning it
+ * starts with very limited functionality. Actually, it can't
+ * even load data by itself. Instead, it needs to have protocols,
+ * strategies, and a record class passed to it that it can use.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store = new Class({
+
+    Family: 'Jx.Store',
+    Extends: Jx.Object,
+
+    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: protocol
+         * The protocol to use for communication in this store. The store
+         * itself doesn't actually use it but it is accessed by the strategies
+         * to do their work. This option is required and the store won't work
+         * without it.
+         */
+        protocol: null,
+        /**
+         * Option: strategies
+         * This is an array of instantiated strategy objects that will work
+         * on this store. They provide many services such as loading data,
+         * paging data, saving, and sorting (and anything else you may need
+         * can be written). If none are passed in it will use the default
+         * Jx.Store.Strategy.Full
+         */
+        strategies: null,
+        /**
+         * Option: record
+         * This is a Jx.Store.Record instance or one of its subclasses. This is
+         * the class that will be used to hold each individual record in the
+         * store. Don't pass in a instance of the class but rather the class
+         * name itself. If none is passed in it will default to Jx.Record
+         */
+        record: null,
+        /**
+         * Option: recordOptions
+         * Options to pass to each record as it's created.
+         */
+        recordOptions: {
+            primaryKey: null
+        }
+    },
+
+    /**
+     * 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,
+    /**
+     * APIProperty: id
+     * The id of this store.
+     */
+    id : null,
+    /**
+     * Property: loaded
+     * Tells whether the store has been loaded or not
+     */
+    loaded: false,
+    /**
+     * Property: ready
+     * Used to determine if the store is completely initialized.
+     */
+    ready: false,
+    
+    /**
+     * Property: deleted
+     * track deleted records before they are purged
+     */
+    deleted: null,
+
+    /**
+     * Method: init
+     * initialize the store, should be called by sub-classes
+     */
+    init: function () {
+        this.parent();
+
+        this.deleted = [];
+        
+        if ($defined(this.options.id)) {
+            this.id = this.options.id;
+        }
+
+        if (!$defined(this.options.protocol)) {
+            this.ready = false;
+            return;
+        } else {
+            this.protocol = this.options.protocol;
+        }
+
+        this.strategies = new Hash();
+
+        if ($defined(this.options.strategies)) {
+            this.options.strategies.each(function(strategy){
+                this.addStrategy(strategy);
+            },this);
+        } else {
+            var strategy = new Jx.Store.Strategy.Full();
+            this.addStrategy(strategy);
+        }
+
+        if ($defined(this.options.record)) {
+            this.record = this.options.record;
+        } else {
+            this.record = Jx.Record;
+        }
+
+
+    },
+
+    /**
+     * Method: cleanup
+     * avoid memory leaks when a store is destroyed, should be called
+     * by sub-classes if overridden
+     */
+    cleanup: function () {
+        this.strategies.each(function(strategy){
+            strategy.destroy();
+        },this);
+        this.strategies = null;
+        this.protocol.destroy();
+        this.protocol = null;
+        this.record = null;
+    },
+    /**
+     * APIMethod: getStrategy
+     * returns the named strategy if it is present, null otherwise.
+     *
+     * Parameters:
+     * name - the name of the strategy we're looking for
+     */
+    getStrategy: function (name) {
+        if (this.strategies.has(name)) {
+            return this.strategies.get(name);
+        }
+        return null;
+    },
+    /**
+     * APIMethod: addStrategy
+     * Allows the addition of strategies after store initialization. Handy to
+     * have if some other class needs a strategy that is not present.
+     *
+     * Parameters:
+     * strategy - the strategy to add to the store
+     */
+    addStrategy: function (strategy) {
+        this.strategies.set(strategy.name, strategy);
+        strategy.setStore(this);
+        strategy.activate();
+    },
+    /**
+     * APIMethod: load
+     * used to load the store. It simply fires an event that the strategies
+     * are listening for.
+     *
+     * Parameters:
+     * params - a hash of parameters passed to the strategy for determining
+     *     what records to load.
+     */
+    load: function (params) {
+        this.fireEvent('storeLoad', params);
+    },
+    /**
+     * APIMethod: empty
+     * Clears the store of data
+     */
+    empty: function () {
+        if ($defined(this.data)) {
+            this.data.empty();
+        }
+    },
+
+    /**
+     * 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)) {
+            return this.index < this.data.length - 1;
+        }
+        return null;
+    },
+
+    /**
+     * APIMethod: hasPrevious
+     * Determines if there are records before the current
+     * one.
+     *
+     * Returns: true | false
+     */
+    hasPrevious : function () {
+        if ($defined(this.data)) {
+            return this.index > 0;
+        }
+        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) && $defined(this.data[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.length) {
+                this.index = this.data.length - 1;
+            }
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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);
+            return true;
+        } 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);
+            return true;
+        } 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.length - 1;
+            this.fireEvent('storeMove', this);
+            return true;
+        } 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.length;
+        }
+        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)) {
+            return this.index;
+        }
+        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.length) {
+            this.index = index;
+            this.fireEvent('storeMove', this);
+            return true;
+        } else if (!$defined(this.data)) {
+            return null;
+        } else {
+            return false;
+        }
+    },
+    /**
+     * APIMethod: each
+     * allows iteration through the store's records.
+     * NOTE: this function is untested
+     *
+     * Parameters:
+     * fn - the function to execute for each record
+     * bind - the scope of the function
+     * ignoreDeleted - flag that tells the function whether to ignore records
+     *                  marked as deleted.
+     */
+    each: function (fn, bind, ignoreDeleted) {
+        if ($defined(this.data)) {
+          var data;
+          if (ignoreDeleted) {
+              data = this.data.filter(function (record) {
+                  return record.state !== Jx.Record.DELETE;
+              }, this);
+          } else {
+              data = this.data;
+          }
+          data.each(fn, bind);
+        }
+    },
+    /**
+     * APIMethod: get
+     * gets the data for the specified column
+     *
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of
+     *          the column) or an integer (the index of the column in the
+     *          record).
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    get: function (column, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        return this.data[index].get(column);
+    },
+    /**
+     * APIMethod: set
+     * Sets the passed data for a particular column on the indicated record.
+     *
+     * Parameters:
+     * column - indicator of the column to set. Either a string (the name of
+     *          the column) or an integer (the index of the column in the
+     *          record).
+     * data - the data to set in the column of the record
+     * index - the index of the record in the internal array. Optional.
+     *          defaults to the current index.
+     */
+    set: function (column, data, index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var ret = this.data[index].set(column, data);
+        ret.reverse();
+        ret.push(index);
+        ret.reverse();
+        //fire event with array [index, column, oldvalue, newValue]
+        this.fireEvent('storeColumnChanged', ret);
+    },
+    /**
+     * APIMethod: refresh
+     * Simply fires the storeRefresh event for strategies to listen for.
+     */
+    refresh: function () {
+        this.fireEvent('storeRefresh', this);
+    },
+    /**
+     * APIMethod: addRecord
+     * Adds given data to the end of the current store.
+     *
+     * Parameters:
+     * data - The data to use in creating a record. This should be in whatever
+     *        form Jx.Store.Record, or the current subclass, needs it in.
+     * position - whether the record is added to the 'top' or 'bottom' of the
+     *      store.
+     * insert - flag whether this is an "insert"
+     */
+    addRecord: function (data, position, insert) {
+        if (!$defined(this.data)) {
+            this.data = [];
+        }
+
+        position = $defined(position)? position : 'bottom';
+
+        var record = data;
+        if (!(data instanceof Jx.Record)) {
+            record = new (this.record)(this, this.options.columns, data, this.options.recordOptions);
+        }
+        if (insert) {
+            record.state = Jx.Record.INSERT;
+        }
+        if (position === 'top') {
+            //some literature claims that .shift() and .unshift() don't work reliably in IE
+            //so we do it this way.
+            this.data.reverse();
+            this.data.push(record);
+            this.data.reverse();
+        } else {
+            this.data.push(record);
+        }
+        this.fireEvent('storeRecordAdded', [this, record, position]);
+    },
+    /**
+     * APIMethod: addRecords
+     * Used to add multiple records to the store at one time.
+     *
+     * Parameters:
+     * data - an array of data to add.
+     * position - 'top' or 'bottom'. Indicates whether to add at the top or
+     * the bottom of the store
+     */
+    addRecords: function (data, position) {
+        var def = $defined(data),
+            type = Jx.type(data);
+        if (def && type === 'array') {
+            this.fireEvent('storeBeginAddRecords', this);
+            //if position is top, reverse the array or we'll add them in the
+            // wrong order.
+            if (position === 'top') {
+                data.reverse();
+            }
+            data.each(function(d){
+                this.addRecord(d, position);
+            },this);
+            this.fireEvent('storeEndAddRecords', this);
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * APIMethod: getRecord
+     * Returns the record at the given index or the current store index
+     *
+     * Parameters:
+     * index - the index from which to return the record. Optional. Defaults
+     * to the current store index
+     */
+    getRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+
+        if (Jx.type(index) === 'number') {
+            if ($defined(this.data) && $defined(this.data[index])) {
+                return this.data[index];
+            }
+        } else {
+            //Not sure what the point of this part is. It compares the
+            //record to the index directly as if we passed in the record which
+            //means we already have the record... huh???
+            var r;
+            this.data.each(function(record){
+                if (record === index) {
+                    r = record;
+                }
+            },this);
+            return r;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: replaceRecord
+     * Replaces the record at an existing index with a new record containing
+     * the passed in data.
+     *
+     * Parameters:
+     * data - the data to use in creating the new record
+     * index - the index at which to place the new record. Optional.
+     *          defaults to the current store index.
+     */
+    replace: function(data, index) {
+        if ($defined(data)) {
+            if (!$defined(index)) {
+                index = this.index;
+            }
+            var record = new this.record(this.options.columns,data),
+            oldRecord = this.data[index];
+            this.data[index] = record;
+            this.fireEvent('storeRecordReplaced', [oldRecord, record]);
+            return true;
+        }
+        return false;
+    },
+    /**
+     * APIMethod: deleteRecord
+     * Marks a record for deletion and removes it from the regular array of
+     * records. It adds it to a special holding array so it can be disposed
+     * of later.
+     *
+     * Parameters:
+     * index - the index at which to place the new record. Optional.
+     *          defaults to the current store index.
+     */
+    deleteRecord: function(index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        var record = this.data[index];
+        record.state = Jx.Record.DELETE;
+        // Set to Null or slice it out and compact the array???
+        //this.data[index] = null;
+        this.data.splice(index,1);
+        // TODO: I moved this to a property that is always an array so I don't
+        // get an error in the save strategy.
+        // if (!$defined(this.deleted)) {
+        //     this.deleted = [];
+        // }
+        this.deleted.push(record);
+        this.fireEvent('storeRecordDeleted', [this, record]);
+    },
+    /**
+     * APIMethod: insertRecord
+     * Shortcut to addRecord which facilitates marking a record as inserted.
+     *
+     * Parameters:
+     * data - the data to use in creating this inserted record. Should be in
+     *          whatever form the current implementation of Jx.Record needs
+     * position - where to place the record. Should be either 'top' or
+     *    'bottom'.
+     */
+    insertRecord: function (data, position) {
+        this.addRecord(data, position, true);
+    },
+
+    /**
+     * 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 (or index) of the column to search by
+     * value - the value to look for
+     */
+    findByColumn: function (column, value) {
+        if (typeof StopIteration === "undefined") {
+            StopIteration = new Error("StopIteration");
+        }
+
+        var index;
+        try {
+            this.data.each(function(record, idx){
+                if (record.equals(column, value)) {
+                    index = idx;
+                    throw StopIteration;
+                }
+            },this);
+        } catch (error) {
+            if (error !== StopIteration) {
+                throw error;
+            }
+            return index;
+        }
+        return null;
+    },
+    /**
+     * APIMethod: removeRecord
+     * removes (but does not mark for deletion) a record at the given index
+     * or the current store index if none is passed in.
+     *
+     * Parameters:
+     * index - Optional. The store index of the record to remove.
+     */
+    removeRecord: function (index) {
+        if (!$defined(index)) {
+            index = this.index;
+        }
+        this.data.splice(index,1);
+        this.fireEvent('storeRecordRemoved', [this, index])
+    },
+    /**
+     * APIMethod: removeRecords
+     * Used to remove multiple contiguous records from a store.
+     *
+     * Parameters:
+     * first - where to start removing records (zero-based)
+     * last - where to stop removing records (zero-based, inclusive)
+     */
+    removeRecords: function (first, last) {
+        for (var i = first; i <= last; i++) {
+            this.removeRecord(first);
+        }
+        this.fireEvent('storeMultipleRecordsRemoved', [this, first, last]);
+    },
+
+    /**
+   * APIMethod: parseTemplate
+   * parses the provided template to determine which store columns are
+   * required to complete it.
+   *
+   * Parameters:
+   * template - the template to parse
+   */
+  parseTemplate: function (template) {
+      //we parse the template based on the columns in the data store looking
+      //for the pattern {column-name}. If it's in there we add it to the
+      //array of ones to look fo
+      var arr = [],
+          s;
+      this.options.columns.each(function (col) {
+          s = '{' + col.name + '}';
+          if (template.contains(s)) {
+              arr.push(col.name);
+          }
+      }, this);
+      return arr;
+  },
+
+  /**
+   * APIMethod: fillTemplate
+   * Actually does the work of getting the data from the store
+   * and creating a single item based on the provided template
+   *
+   * Parameters:
+   * index - the index of the data in the store to use in populating the
+   *          template.
+   * template - the template to fill
+   * columnsNeeded - the array of columns needed by this template. should be
+   *      obtained by calling parseTemplate().
+     * obj - an object with some prefilled keys to use in substituting.
+     *      Ones that are also in the store will be overwritten.
+   */
+  fillTemplate: function (index, template, columnsNeeded, obj) {
+      var record = null,
+          itemObj;
+      if ($defined(index)) {
+          if (index instanceof Jx.Record) {
+              record = index;
+          } else {
+              record = this.getRecord(index);
+          }
+        } else {
+            record = this.getRecord(this.index);
+        }
+
+      //create the item
+      itemObj = $defined(obj) ? obj : {};
+      columnsNeeded.each(function (col) {
+          itemObj[col] = record.get(col);
+      }, this);
+      return template.substitute(itemObj);
+  }
+});/*
+---
+
+name: Jx.Compare
+
+description: Class that provides functions for comparing various data types. Used by the Jx.Sort class and it's descendants
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - More/Date.Extras
+
+provides: [Jx.Compare]
+
+...
+ */
+// $Id: compare.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Compare
+ *
+ * Extends: <Jx.Object>
+ *
+ * Class that holds functions for doing comparison operations.
+ * This class requires the mootools-more Date() extensions.
+ *
+ * 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({
+    Family: 'Jx.Compare',
+    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') {
+            var neg = false;
+            if (val.substr(0,1) == '-') {
+                neg = true;
+            }
+            val = parseFloat(val.replace(/^[^\d\.]*([\d., ]+).*/g, "$1").replace(new RegExp("[^\\\d" + this.options.separator + "]", "g"), '').replace(/,/, '.')) || 0;
+            if (neg) {
+                val = val * -1;
+            }
+        }
+        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),
+            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;
+    }
+
+});/*
+---
+
+name: Jx.Sort
+
+description: Base class for the sort algorithm implementations
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Jx.Compare
+
+provides: [Jx.Sort]
+
+...
+ */
+// $Id: sort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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;
+    }
+});
+/*
+---
+
+name: Jx.Sort.Mergesort
+
+description: An implementation of the merge sort algorithm
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Mergesort]
+
+...
+ */
+// $Id: mergesort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Mergesort',
+    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,
+            left = arr.slice(0, middle),
+            right = arr.slice(middle),
+            result;
+        left = this.mergeSort(left);
+        right = this.mergeSort(right);
+        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;
+    }
+
+});
+/*
+---
+
+name: Jx.Sort.Heapsort
+
+description: An implementation of the heap sort algorithm
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Heapsort]
+
+...
+ */
+// $Id: heapsort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Heapsort',
+    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,
+            end;
+
+        if (count === 1) {
+            return this.data;
+        }
+
+        if (count > 2) {
+            this.heapify(count);
+
+            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,
+            child;
+
+        while (root * 2 <= end) {
+            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;
+            }
+        }
+    }
+
+});
+/*
+---
+
+name: Jx.Sort.Quicksort
+
+description: An implementation of the quick sort algorithm.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Quicksort]
+
+...
+ */
+// $Id: quicksort.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * 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({
+    Family: 'Jx.Sort.Quicksort',
+    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,
+            pivotValue = (this.data[pivotIndex]).get(this.col),
+            index = left,
+            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,
+            shift = 1,
+            endIndex,
+            medianIndex;
+        while (shift <= (right - left)) {
+            for (i = left; i <= right; i += shift * 5) {
+                endIndex = (i + shift * 5 - 1 < right) ? i + shift * 5 - 1 : right;
+                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),
+            k = Math.round(left + groups / 2 * shift),
+            i,
+            minIndex,
+            v,
+            minValue,
+            j;
+        if (k > this.data.length - 1) {
+            k = this.data.length - 1;
+        }
+        for (i = left; i < k; i += shift) {
+            minIndex = i;
+            v = this.data[minIndex];
+            minValue = v.get(this.col);
+
+            for (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;
+    }
+});
+/*
+---
+
+name: Jx.Sort.Nativesort
+
+description: An implementation of the Javascript native sorting with the Jx.Sort interface
+
+license: MIT-style license.
+
+requires:
+ - Jx.Sort
+
+provides: [Jx.Sort.Nativesort]
+
+...
+ */
+// $Id: nativesort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Sort.Nativesort',
+    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;
+    }
+
+});
+/*
+---
+
+name: Jx.Store.Response
+
+description: The object used to return response information to strategies.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Response]
+
+...
+ */
+// $Id: response.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Response
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * This class is used by the protocol to send information back to the calling 
+ * strategy (or other caller).
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Response = new Class({
+
+    Family: 'Jx.Store.Response',
+    Extends: Jx.Object,
+
+    /**
+     * Property: code
+     * This is the success/failure code
+     */
+    code: null,
+    /**
+     * Property: data
+     * The data passed received by the protocol.
+     */
+    data: null,
+    /**
+     * Property: meta
+     * The metadata received by the protocol
+     */
+    meta: null,
+    /**
+     * Property: requestType
+     * one of 'read', 'insert', 'delete', or 'update'
+     */
+    requestType: null,
+    /**
+     * Property: requestParams
+     * The parameters passed to the method that created this response
+     */
+    requestParams: null,
+    /**
+     * Property: request
+     * the mootools Request object used in this operation (if one is actually
+     * used)
+     */
+    request: null,
+    /**
+     * Property: error
+     * the error data received from the called page if any.
+     */
+    error: null,
+    /**
+     * APIMethod: success
+     * determines if this response represents a successful response
+     */
+    success: function () {
+        return this.code > 0;
+    }
+});
+
+Jx.Store.Response.WAITING = 2;
+Jx.Store.Response.SUCCESS = 1;
+Jx.Store.Response.FAILURE = 0;
+/*
+---
+
+name: Jx.Store.Protocol
+
+description: Base class for all store protocols.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Response
+
+provides: [Jx.Store.Protocol]
+
+...
+ */
+// $Id: protocol.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store.Protocol
+ *
+ * Extends: <Jx.Object>
+ *
+ * Base class for all protocols. Protocols are used for communication, primarily,
+ * in Jx.Store. It may be possible to adapt them to be used in other places but
+ * that is not their intended function.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol = new Class({
+
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Protocol',
+
+    parser: null,
+
+    options: {
+      combine: {
+        insert: false,
+        update: false,
+        'delete': false
+      }
+    },
+
+    init: function () {
+        this.parent();
+
+        if ($defined(this.options.parser)) {
+            this.parser = this.options.parser;
+        }
+    },
+
+    cleanup: function () {
+        this.parser = null;
+        this.parent();
+    },
+
+    /**
+     * APIMethod: read
+     * Supports reading data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * options - optional options for configuring the request
+     */
+    read: $empty,
+    /**
+     * APIMethod: insert
+     * Supports inserting data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to use in creating the record in the form of one or more
+     *        Jx.Store.Record instances
+     * options - optional options for configuring the request
+     */
+    insert: $empty,
+    /**
+     * APIMethod: update
+     * Supports updating data at a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    update: $empty,
+    /**
+     * APIMethod: delete
+     * Supports deleting data from a location. Abstract method that subclasses
+     * should implement.
+     *
+     * Parameters:
+     * data - the data to update (one or more Jx.Store.Record objects)
+     * options - optional options for configuring the request
+     */
+    "delete": $empty,
+    /**
+     * APIMethod: abort
+     * used to abort any of the above methods (where practical). Abstract method
+     * that subclasses should implement.
+     */
+    abort: $empty,
+    /**
+     * APIMethod: combineRequests
+     * tests whether the protocol supports combining multiple records for a given operation
+     * 
+     * Parameter:
+     * operation - {String} the operation to test for multiple record support
+     * 
+     * Returns {Boolean} true if the operation supports it, false otherwise
+     */
+    combineRequests: function(op) {
+      return $defined(this.options.combine[op]) ? this.options.combine[op] : false;
+    }
+});/*
+---
+
+name: Jx.Store.Protocol.Local
+
+description: Store protocol used to load data that is already present in a page as an object.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Protocol
+
+provides: [Jx.Store.Protocol.Local]
+
+...
+ */
+// $Id: protocol.local.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Store.Protocol.Local
+ * 
+ * Extends: Jx.Store.Protocol
+ * 
+ * Based on the Protocol base class, the local protocol uses data that it is
+ * handed upon instantiation to process requests.
+ * 
+ * Constructor Parameters:
+ * data - The data to use 
+ * options - any options for the base protocol class
+ * 
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * inspired by the openlayers.org implementation of a similar system
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Local = new Class({
+    
+    Extends: Jx.Store.Protocol,
+    
+    parameters: ['data', 'options'],
+    /**
+     * Property: data
+     * The data passed to the protocol
+     */
+    data: null,
+    
+    init: function () {
+        this.parent();
+        
+        if ($defined(this.options.data)) {
+            this.data = this.parser.parse(this.options.data);
+        }
+    },
+    /**
+     * APIMethod: read
+     * process requests for data and sends the appropriate response via the
+     * dataLoaded event.
+     * 
+     * Parameters: 
+     * options - options to use in processing the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response(),
+            page = options.data.page,
+            itemsPerPage = options.data.itemsPerPage,
+            start,
+            end,
+            data = this.data;
+
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+        
+        
+        if ($defined(data)) {
+            if (page <= 1 && itemsPerPage === -1) {
+                //send them all
+                resp.data = data;
+                resp.meta = { count: data.length };
+            } else {
+                start = (page - 1) * itemsPerPage;
+                end = start + itemsPerPage;
+                resp.data = data.slice(start, end);
+                resp.meta = { 
+                    page: page, 
+                    itemsPerPage: itemsPerPage,
+                    totalItems: data.length,
+                    totalPages: Math.ceil(data.length/itemsPerPage)
+                };
+            }
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        } else {
+            resp.code = Jx.Store.Response.SUCCESS;
+            this.fireEvent('dataLoaded', resp);
+        }                        
+    }
+    
+    /**
+     * The following methods are not implemented as they make no sense for a
+     * local protocol:
+     * - create
+     * - update 
+     * - delete
+     * - commit
+     * - abort
+     */
+});/*
+---
+
+name: Jx.Store.Protocol.Ajax
+
+description: Store protocol used to load data from a remote data source via Ajax.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Protocol
+ - more/Request.Queue
+
+provides: [Jx.Store.Protocol.Ajax]
+
+...
+ */
+// $Id: protocol.ajax.js 1006 2011-01-01 22:43:42Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Protocol.Ajax
+ *
+ * Extends: <Jx.Store.Protocol>
+ *
+ * This protocol is used to send and receive data via AJAX. It also has the
+ * capability to use a REST-style API.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Protocol.Ajax = new Class({
+
+    Extends: Jx.Store.Protocol,
+
+    options: {
+        /**
+         * Option: requestOptions
+         * Options to pass to the mootools Request class
+         */
+        requestOptions: {
+            method: 'get'
+        },
+        /**
+         * Option: rest
+         * Flag indicating whether this protocol is operating against a RESTful
+         * web service
+         */
+        rest: false,
+        /**
+         * Option: urls
+         * This is a hash of the urls to use for each method. If the rest
+         * option is set to true the only one needed will be the urls.rest.
+         * These can be overridden if needed by passing an options object into
+         * the various methods with the appropriate urls.
+         */
+        urls: {
+            rest: null,
+            insert: null,
+            read: null,
+            update: null,
+            'delete': null
+        },
+        /**
+         * Option: queue
+         * an object containing options suitable for <Request.Queue>.
+         * By default, autoAdvance is set to true and concurrent is set to 1.
+         */
+        queue: {
+          autoAdvance: true,
+          concurrent: 1
+        }
+    },
+    
+    queue: null,
+
+    init: function() {
+        if (!$defined(Jx.Store.Protocol.Ajax.UniqueId)) {
+          Jx.Store.Protocol.Ajax.UniqueId = 1;
+        }
+      
+        this.queue = new Request.Queue({
+          autoAdvance: this.options.queue.autoAdvance,
+          concurrent: this.options.queue.concurrent
+        });
+        this.parent();
+    },
+    /**
+     * APIMethod: read
+     * Send a read request via AJAX
+     *
+     * Parameters:
+     * options - the options to pass to the request.
+     */
+    read: function (options) {
+        var resp = new Jx.Store.Response(),
+            temp = {},
+            opts,
+            req,
+            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();
+        resp.requestType = 'read';
+        resp.requestParams = arguments;
+
+
+        // set up options
+        if (this.options.rest) {
+            temp.url = this.options.urls.rest;
+        } else {
+            temp.url = this.options.urls.read;
+        }
+
+        opts = $merge(this.options.requestOptions, temp, options);
+        opts.onSuccess = this.handleResponse.bind(this,resp);
+
+        req = new Request(opts);
+        resp.request = req;
+        
+        this.queue.addRequest(uniqueId, req);
+        req.send();
+
+        resp.code = Jx.Store.Response.WAITING;
+
+        return resp;
+
+    },
+    /**
+     * Method: handleResponse
+     * Called as an event handler for a returning request. Parses the request's
+     * response into the actual response object.
+     *
+     * Parameters:
+     * response - the response related to teh returning request.
+     */
+    handleResponse: function (response) {
+        var req = response.request,
+            str = req.xhr.responseText,
+            data = this.parser.parse(str);
+        if ($defined(data)) {
+            if ($defined(data.success) && data.success) {
+                if ($defined(data.data)) {
+                    response.data = data.data;
+                }
+                if ($defined(data.meta)) {
+                    response.meta = data.meta;
+                }
+                response.code = Jx.Store.Response.SUCCESS;
+            } else {
+                response.code = Jx.Store.Response.FAILURE;
+                response.error = $defined(data.error) ? data.error : null;
+            }
+        } else {
+            response.code = Jx.Store.Response.FAILURE;
+        }
+        this.fireEvent('dataLoaded', response);
+    },
+    /**
+     * APIMethod: insert
+     * Takes a Jx.Record instance and saves it
+     *
+     * Parameters:
+     * record - a Jx.Store.Record or array of them
+     * options - options to pass to the request
+     */
+    insert: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+        } else {
+            options = $merge({url: this.options.urls.insert},options);
+        }
+        this.options.requestOptions.method = 'POST';
+        return this.run(record, options, "insert");
+    },
+    /**
+     * APIMethod: update
+     * Takes a Jx.Record and updates it via AJAX
+     *
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    update: function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'PUT';
+        } else {
+            options = $merge({url: this.options.urls.update},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "update");
+    },
+    /**
+     * APIMethod: delete
+     * Takes a Jx.Record and deletes it via AJAX
+     *
+     * Parameters:
+     * record - a Jx.Record instance
+     * options - Options to pass to the request
+     */
+    "delete": function (record, options) {
+        if (this.options.rest) {
+            options = $merge({url: this.options.urls.rest},options);
+            this.options.requestOptions.method = 'DELETE';
+        } else {
+            options = $merge({url: this.options.urls['delete']},options);
+            this.options.requestOptions.method = 'POST';
+        }
+        return this.run(record, options, "delete");
+    },
+    /**
+     * APIMethod: abort
+     * aborts the request related to the passed in response.
+     *
+     * Parameters:
+     * response - the response with the request to abort
+     */
+    abort: function (response) {
+        response.request.cancel();
+
+    },
+    /**
+     * Method: run
+     * called by update, delete, and insert methods that actually does the work
+     * of kicking off the request.
+     *
+     * Parameters:
+     * record - The Jx.Record to work with
+     * options - Options to pass to the request
+     * method - The name of the method calling this function
+     */
+    run: function (record, options, method) {
+        var resp = new Jx.Store.Response(),
+            opts,
+            req,
+            data,
+            uniqueId = Jx.Store.Protocol.Ajax.UniqueId();
+        
+        if (Jx.type(record) == 'array') {
+          if (!this.combineRequests(method)) {
+            record.each(function(r) {
+              this.run(r, options, method);
+            }, this);
+          } else {
+            data = [];
+            record.each(function(r) {
+              data.push(this.parser.encode(r));
+            }, this);
+          }
+        } else {
+          data = this.parser.encode(record);
+        }
+
+        this.options.requestOptions.data = $merge(this.options.requestOptions.data, {
+          data: data
+        });
+
+        resp.requestType = method;
+        resp.requestParams = [record, options, method];
+
+        //set up options
+        opts = $merge(this.options.requestOptions, options);
+        opts.onSuccess = this.handleResponse.bind(this,resp);
+        req = new Request(opts);
+        resp.request = req;
+        this.queue.addRequest(uniqueId, req);
+        req.send();
+
+        resp.code = Jx.Store.Response.WAITING;
+
+        return resp;
+    }
+    
+});
+/**
+ * Method: uniqueId
+ * returns a unique identifier to be used with queued requests
+ */
+Jx.Store.Protocol.Ajax.UniqueId = (function() {
+  var uniqueId = 1;
+  return function() {
+    return 'req-'+(uniqueId++);
+  };
+})();
+/*
+---
+
+name: Jx.Store.Strategy
+
+description: Base class for all store strategies.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Strategy]
+
+
+...
+ */
+// $Id: strategy.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all Jx.Store strategies
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Strategy',
+    /**
+     * APIProperty: store
+     * The store this strategy is associated with
+     */
+    store: null,
+    /**
+     * APIProperty: active
+     * whether this strategy has been activated or not.
+     */
+    active: null,
+    
+    /**
+     * Method: init
+     * initialize the strategy, should be called by subclasses
+     */
+    init: function () {
+        this.parent();
+        this.active = false;
+    },
+    /**
+     * APIMethod: setStore
+     * Associates this strategy with a particular store.
+     */
+    setStore: function (store) {
+        if (store instanceof Jx.Store) {
+            this.store = store;
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if (!this.active) {
+            this.active = true;
+            return true;
+        }
+        return false;
+    },
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if (this.active) {
+            this.active = false;
+            return true;
+        }
+        return false;
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Full
+
+description: Strategy for loading the full data set from a source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Full]
+
+...
+ */
+// $Id: strategy.full.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Full
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * This is a strategy for loading all of the data from a source at one time.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Strategy.Full = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'full',
+    
+    options:{},
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+        
+    },
+    /**
+     * APIMethod: load
+     * Called as the eventhandler for the store load method. Can also
+     * be called independently to load data into the current store.
+     * 
+     * Parameters:
+     * params - a hash of parameters to use in loading the data.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        var opts = {}
+        if ($defined(params)) {
+            opts.data = params;
+        } else {
+            opts.data = {};
+        }
+        opts.data.page = 0;
+        opts.data.itemsPerPage = -1;
+        this.store.protocol.read(opts);
+    },
+    
+    /**
+     * Method: loadStore
+     * Called as the event handler for the protocol's dataLoaded event. Checks
+     * the response for success and loads the data into the store if needed.
+     * 
+     * Parameters:
+     * resp - the response from the protocol
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            this.store.empty();
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.store.addRecords(resp.data);
+            this.store.loaded = true;
+            this.store.fireEvent('storeDataLoaded',this.store);
+        } else {
+            this.store.loaded = false;
+            this.store.fireEvent('storeDataLoadFailed', [this.store, resp]);
+        }
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the meta property of the response object and puts the data 
+     * where it belongs.
+     * 
+     * Parameters:
+     * meta - the meta data object from the response.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Paginate
+
+description: Strategy for loading data in pages and moving between them. This strategy makes sure the store only contains the current page's data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Paginate]
+
+
+...
+ */
+// $Id: strategy.paginate.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Paginate
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Store strategy for paginating results in a store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Paginate = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'paginate',
+    
+    options: {
+        /**
+         * Option: getPaginationParams
+         * a function that returns an object that holds the parameters
+         * necessary for getting paginated data from a protocol.
+         */
+        getPaginationParams: function () {
+            return {
+                page: this.page,
+                itemsPerPage: this.itemsPerPage
+            };
+        },
+        /**
+         * Option: startingItemsPerPage
+         * Used to set the intial itemsPerPage for the strategy. the pageSize 
+         * can be changed using the setPageSize() method.
+         */
+        startingItemsPerPage: 25,
+        /**
+         * Option: startingPage
+         * The page to start on. Defaults to 1 but can be set to any other 
+         * page.
+         */
+        startingPage: 1,
+        /**
+         * Option: expirationInterval
+         * The interval, in milliseconds (1000 = 1 sec), to hold a page of
+         * data before it expires. If the page is expired, the next time the
+         * page is accessed it must be retrieved again. Default is 5 minutes
+         * (1000 * 60 * 5)
+         */
+        expirationInterval: (1000 * 60 * 5),
+        /**
+         * Option: ignoreExpiration
+         * Set to TRUE to ignore the expirationInterval setting and never
+         * expire pages.
+         */
+        ignoreExpiration: false
+    },
+    /**
+     * Property: data
+     * holds the pages of data keyed by page number.
+     */
+    data: null,
+    /**
+     * property: cacheTimer
+     * holds one or more cache timer ids - one per page. Each page is set to 
+     * expire after a certain amount of time.
+     */
+    cacheTimer: null,
+    /**
+     * Property: page
+     * Tracks the page the store currently holds.
+     */
+    page: null,
+    /**
+     * Property: itemsPerPage
+     * The number of items on each page
+     */
+    itemsPerPage: null,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.data = new Hash();
+        this.cacheTimer = new Hash();
+        //set up bindings that we need here
+        this.bound.load = this.load.bind(this);
+        this.bound.loadStore = this.loadStore.bind(this);
+        this.itemsPerPage = this.options.startingItemsPerPage;
+        this.page = this.options.startingPage;
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        this.store.addEvent('storeLoad', this.bound.load);
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        this.store.removeEvent('storeLoad', this.bound.load);
+    },
+    /**
+     * APIMethod: load
+     * Called to load data into the store
+     * 
+     * Parameters:
+     * params - a Hash of parameters to use in getting data from the protocol.
+     */
+    load: function (params) {
+        this.store.fireEvent('storeBeginDataLoad', this.store);
+        this.store.protocol.addEvent('dataLoaded', this.bound.loadStore);
+        this.params = params;
+        var opts = {
+            data: $merge(params, this.options.getPaginationParams.apply(this))
+        };
+        this.store.protocol.read(opts);
+    },
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.data.set(this.page,resp.data);
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.empty();
+        this.store.loaded = false;
+        if (!this.options.ignoreExpiration) {
+            var id = this.expirePage.delay(this.options.expirationInterval, this, this.page);
+            this.cacheTimer.set(this.page,id);
+        }
+        this.store.addRecords(data);
+        this.store.loaded = true;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    /**
+     * Method: parseMetaData
+     * Takes the metadata returned from the protocol and places it in the
+     * appropriate Vplaces.
+     * 
+     * Parameters:
+     * meta - the meta data object returned from the protocol.
+     */
+    parseMetaData: function (meta) {
+        if ($defined(meta.columns)) {
+            this.store.options.columns = meta.columns;
+        }
+        if ($defined(meta.totalItems)) {
+            this.totalItems = meta.totalItems;
+        }
+        if ($defined(meta.totalPages)) {
+            this.totalPages = meta.totalPages;
+        }
+        if ($defined(meta.primaryKey)) {
+            this.store.options.recordOptions.primaryKey = meta.primaryKey;
+        }
+            
+    },
+    /**
+     * Method: expirePage
+     * Is called when a pages cache timer expires. Will expire the page by 
+     * erasing the page and timer. This will force a reload of the data the 
+     * next time the page is accessed.
+     * 
+     * Parameters:
+     * page - the page number to expire.
+     */
+    expirePage: function (page) {
+        this.data.erase(page);
+        this.cacheTimer.erase(page);
+    },
+    /**
+     * APIMethod: setPage
+     * Allows a caller (i.e. a paging toolbar) to move to a specific page.
+     * 
+     * Parameters:
+     * page - the page to move to. Can be any absolute page number, any number
+     *        prefaced with '-' or '+' (i.e. '-1', '+3'), 'first', 'last', 
+     *        'next', or 'previous'
+     */
+    setPage: function (page) {
+        if (Jx.type(page) === 'string') {
+            switch (page) {
+                case 'first':
+                    this.page = 1;
+                    break;
+                case 'last':
+                    this.page = this.totalPages;
+                    break;
+                case 'next':
+                    this.page++;
+                    break;
+                case 'previous':
+                    this.page--;
+                    break;
+                default:
+                    this.page = this.page + Jx.getNumber(page);
+                    break;
+            }
+        } else {
+            this.page = page;
+        }
+        if (this.cacheTimer.has(this.page)) {
+            $clear(this.cacheTimer.get(this.page));
+            this.cacheTimer.erase(this.page);
+        }
+        if (this.data.has(this.page)){
+            this.loadData(this.data.get(this.page));
+        } else {
+            this.load(this.params);
+        }
+    },
+    /**
+     * APIMethod: getPage
+     * returns the current page
+     */
+    getPage: function () {
+        return this.page;
+    },
+    /**
+     * APIMethod: getNumberOfPages
+     * returns the total number of pages.
+     */
+    getNumberOfPages: function () {
+        return this.totalPages;
+    },
+    /**
+     * APIMethod: setPageSize
+     * sets the current size of the pages. Calling this will expire every page 
+     * and force the current one to reload with the new size.
+     */
+    setPageSize: function (size) {
+        //set the page size 
+        this.itemsPerPage = size;
+        //invalidate all pages cached and reload the current one only
+        this.cacheTimer.each(function(val){
+            $clear(val);
+        },this);
+        this.cacheTimer.empty();
+        this.data.empty();
+        this.load();
+    },
+    /**
+     * APIMethod: getPageSize
+     * returns the current page size
+     */
+    getPageSize: function () {
+        return this.itemsPerPage;
+    },
+    /**
+     * APIMethod: getTotalCount
+     * returns the total number of items as received from the protocol.
+     */
+    getTotalCount: function () {
+        return this.totalItems;
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Progressive
+
+description: Strategy based on Strategy.Paginate but loads data progressively without removing old or curent data from the store.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy.Paginate
+
+provides: [Jx.Store.Strategy.Progressive]
+
+...
+ */
+/**
+ * Class: Jx.Store.Strategy.Progressive
+ *
+ * Extends: <Jx.Store.Strategy.Paginate>
+ *
+ * Store strategy for progressively obtaining results in a store. You can
+ * continually call nextPage() to get the next page and the store will retain
+ * all current data. You can set a maximum number of records the store should
+ * hold and whether it should dropRecords when that max is hit.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Progressive = new Class({
+    
+    Extends: Jx.Store.Strategy.Paginate,
+    
+    name: 'progressive',
+    
+    options: {
+        /**
+         * Option: maxRecords
+         * The maximum number of records we want in the store at any one time.
+         */
+        maxRecords: 1000,
+        /**
+         * Option: dropRecords
+         * Whether the strategy should drop records when the maxRecords limit 
+         * is reached. if this is false then maxRecords is ignored and data is
+         * always added to the bottom of the store. 
+         */
+        dropRecords: true
+    },
+    /**
+     * Property: startingPage
+     */
+    startingPage: 0,
+    /**
+     * Property: maxPages
+     */
+    maxPages: null,
+    /**
+     * Property: loadedPages
+     */
+    loadedPages: 0,
+    /**
+     * Property: loadAt
+     * Options are 'top' or 'bottom'. Defaults to 'bottom'.
+     */
+    loadAt: 'bottom',
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        if (this.options.dropPages) {
+            this.maxPages = Math.ceil(this.options.maxRecords/this.itemsPerPage);
+        }
+    },
+    
+    /**
+     * Method: loadStore
+     * Used to assist in the loading of data into the store. This is 
+     * called as a response to the protocol finishing.
+     * 
+     *  Parameters:
+     *  resp - the response object
+     */
+    loadStore: function (resp) {
+        this.store.protocol.removeEvent('dataLoaded', this.bound.loadStore);
+        if (resp.success()) {
+            if ($defined(resp.meta)) {
+                this.parseMetaData(resp.meta);
+            }
+            this.loadData(resp.data);
+        } else {
+            this.store.fireEvent('storeDataLoadFailed', this.store);
+        }
+    },
+    
+    /**
+     * Method: loadData
+     * This method does the actual work of loading data to the store. It is
+     * called when either the protocol finishes or setPage() has the data and
+     * it's not expired.
+     * 
+     * Parameters:
+     * data - the data to load into the store.
+     */
+    loadData: function (data) {
+        this.store.loaded = false;
+        this.store.addRecords(data, this.loadAt);
+        this.store.loaded = true;
+        this.loadedPages++;
+        this.store.fireEvent('storeDataLoaded',this.store);
+    },
+    
+    /**
+     * APIMethod: nextPage
+     * Allows a caller (i.e. a paging toolbar) to load more data to the end of 
+     * the store
+     * 
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    nextPage: function (params) {
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.options.dropRecords && this.totalPages > this.startingPage + this.loadedPages) {
+            this.loadAt = 'bottom';
+            if (this.loadedPages >= this.maxPages) {
+                //drop records before getting more
+                this.startingPage++;
+                this.store.removeRecords(0,this.itemsPerPage - 1);
+                this.loadedPages--;
+            }
+        }
+        this.page = this.startingPage + this.loadedPages + 1;
+        this.load($merge(this.params, params));
+    },
+    /**
+     * APIMethod: previousPage
+     * Allows a caller to move back to the previous page.
+     *
+     * Parameters:
+     * params - a hash of parameters to pass to the request if needed.
+     */
+    previousPage: function (params) {
+        //if we're not dropping pages there's nothing to do
+        if (!this.options.dropRecords) {
+            return;
+        }
+        
+        if (!$defined(params)) {
+            params = {};
+        }
+        if (this.startingPage > 0) {
+            this.loadAt = 'top';
+            if (this.loadedPages >= this.maxPages) {
+                //drop off end before loading previous pages
+                this.startingPage--;
+                this.store.removeRecords(this.options.maxRecords - this.itemsPerPage, this.options.maxRecords);
+                this.loadedPages--;
+            }
+            this.page = this.startingPage;
+            this.load($merge(this.params, params));
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Save
+
+description: Strategy used for saving data back to a source. Can be called manually or setup to automatically save on every change.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+
+provides: [Jx.Store.Strategy.Save]
+
+...
+ */
+// $Id: strategy.save.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Store.Strategy.Save 
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * A Store strategy class for saving data via protocols
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Save = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'save',
+    
+    options: {
+        /**
+         * Option: autoSave
+         * Whether the strategy should be watching the store to save changes
+         * automatically. Set to True to watch events, set it to a number of 
+         * milliseconds to have the strategy save every so many seconds
+         */
+        autoSave: false
+    },
+    /**
+     * Property: failedChanges
+     * an array holding all failed requests
+     */
+    failedChanges: [],
+    /**
+     * Property: successfulChanges
+     * an array holding all successful requests
+     */
+    successfulChanges: [],
+    /**
+     * Property: totalChanges
+     * The total number of changes being processed. Used to determine
+     * when to fire off the storeChangesCompleted event on the store
+     */
+    totalChanges: 0,
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.bound.save = this.saveRecord.bind(this);
+        this.bound.update = this.updateRecord.bind(this);
+        this.bound.completed = this.onComplete.bind(this);
+        this.parent();
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        this.parent();
+        if (Jx.type(this.options.autoSave) === 'number') {
+            this.periodicalId = this.save.periodical(this.options.autoSave, this);
+        } else if (this.options.autoSave) {
+            this.store.addEvent('storeRecordAdded', this.bound.save);
+            this.store.addEvent('storeColumnChanged', this.bound.update);
+            this.store.addEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        this.parent();
+        if ($defined(this.periodicalId)) {
+            $clear(this.periodicalId);
+        } else if (this.options.autoSave) {
+            this.store.removeEvent('storeRecordAdded', this.bound.save);
+            this.store.removeEvent('storeColumnChanged', this.bound.update);
+            this.store.removeEvent('storeRecordDeleted', this.bound.save);
+        }
+        
+    },
+    
+    /**
+     * APIMethod: updateRecord
+     * called by event handlers when store data is updated
+     *
+     * Parameters:
+     * index - {Integer} the row that was affected
+     * column - {String} the column that was affected
+     * oldValue - {Mixed} the previous value
+     * newValue - {Mixed} the new value
+     */
+    updateRecord: function(index, column, oldValue, newValue) {
+      var resp = this.saveRecord(this.store, this.store.getRecord(index));
+      // no response if updating or record state not set
+      if (resp) {
+        resp.index = index;
+      }
+    },
+    /**
+     * APIMethod: saveRecord
+     * Called by event handlers when a store record is added, or deleted. 
+     * If deleted, the record will be removed from the deleted array.
+     * 
+     * Parameters:
+     * record - The Jx.Record instance that was changed
+     * store - The instance of the store
+     */
+    saveRecord: function (store, record) {
+        //determine the status and route based on that
+        if (!this.updating && $defined(record.state)) {
+            if (this.totalChanges === 0) {
+                store.protocol.addEvent('dataLoaded', this.bound.completed);
+            }
+            this.totalChanges++;
+            var ret;
+            switch (record.state) {
+                case Jx.Record.UPDATE:
+                    ret = store.protocol.update(record);
+                    break;
+                case Jx.Record.DELETE:
+                    ret = store.protocol['delete'](record);
+                    break;
+                case Jx.Record.INSERT:
+                    ret = store.protocol.insert(record);
+                    break;
+                default:
+                  break;
+            }
+            return ret;
+        }
+    },
+    /**
+     * APIMethod: save
+     * Called manually when the developer wants to save all data changes 
+     * in one shot. It will empty the deleted array and reset all other status 
+     * flags
+     */
+    save: function () {
+        //go through all of the data and figure out what needs to be acted on
+        if (this.store.loaded) {
+            var records = [];
+            records[Jx.Record.UPDATE] = [];
+            records[Jx.Record.INSERT] = [];
+            
+            this.store.data.each(function (record) {
+                if ($defined(record) && $defined(record.state)) {
+                    records[record.state].push(record);
+                }
+            }, this);
+            records[Jx.Record.DELETE] = this.store.deleted;
+            
+            if (!this.updating) {
+              if (this.totalChanges === 0) {
+                  store.protocol.addEvent('dataLoaded', this.bound.completed);
+              }
+              this.totalChanges += records[Jx.Record.UPDATE].length + 
+                                   records[Jx.Record.INSERT].length +
+                                   records[Jx.Record.DELETE].length;
+              if (records[Jx.Record.UPDATE].length) {
+                this.store.protocol.update(records[Jx.Record.UPDATE]);
+              }
+              if (records[Jx.Record.INSERT].length) {
+                this.store.protocol.insert(records[Jx.Record.INSERT]);
+              }
+              if (records[Jx.Record.DELETE].length) {
+                this.store.protocol['delete'](records[Jx.Record.DELETE]);
+              }
+            }
+            
+            // records.flatten().each(function (record) {
+            //     this.saveRecord(this.store, record);
+            // }, this);
+        }
+        
+    },
+    /**
+     * Method: onComplete
+     * Handles processing of the response(s) from the protocol. Each 
+     * update/insert/delete will have an individual response. If any responses 
+     * come back failed we will hold that response and send it to the caller
+     * via the fired event. This method is responsible for updating the status
+     * of each record as it returns and on inserts, it updates the primary key
+     * of the record. If it was a delete it will remove it permanently from
+     * the store's deleted array (provided it returns successful - based on
+     * the success attribute of the meta object). When all changes have been 
+     * accounted for the method fires a finished event and passes all of the 
+     * failed responses to the caller so they can be handled appropriately.
+     * 
+     * Parameters:
+     * response - the response returned from the protocol
+     */
+    onComplete: function (response) {
+        if (!response.success() || ($defined(response.meta) && !response.meta.success)) {
+            this.failedChanges.push(response);
+        } else {
+            //process the response
+            var records = [response.requestParams[0]].flatten(),
+                responseData = $defined(response.data) ? [response.data].flatten() : null;
+            records.each(function(record, index) {
+              if (response.requestType === 'delete') {
+                  this.store.deleted.erase(record);
+              } else { 
+                  if (response.requestType === 'insert' || response.requestType == 'update') {
+                      if (responseData && $defined(responseData[index])) {
+                          this.updating = true;
+                          $H(responseData[index]).each(function (val, key) {
+                              var d = record.set(key, val);
+                              if (d[1] != val) {
+                                d.unshift(index);
+                                record.store.fireEvent('storeColumnChanged', d);
+                              }
+                          });
+                          this.updating = false;
+                      }
+                  }
+                  record.state = null;
+              } 
+              this.totalChanges--;
+          }, this);
+          this.successfulChanges.push(response);
+        }
+        if (this.totalChanges === 0) {
+            this.store.protocol.removeEvent('dataLoaded', this.bound.completed);
+            this.store.fireEvent('storeChangesCompleted', {
+                successful: this.successfulChanges,
+                failed: this.failedChanges
+            });
+        }
+    }
+});/*
+---
+
+name: Jx.Store.Strategy.Sort
+
+description: Strategy used for sorting results in a store after they are loaded.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Strategy
+ - Jx.Sort.Mergesort
+ - Jx.Compare
+
+provides: [Jx.Store.Strategy.Sort]
+...
+ */
+// $Id: strategy.sort.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Strategy.Sort
+ * 
+ * Extends: <Jx.Store.Strategy>
+ * 
+ * Strategy used for sorting stores. It can either be called manually or it
+ * can listen for specific events from the store.
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Strategy.Sort = new Class({
+    
+    Extends: Jx.Store.Strategy,
+    
+    name: 'sort',
+    
+    options: {
+        /**
+         * Option: sortOnStoreEvents
+         * an array of events this strategy should listen for on the store and
+         * sort when it sees them.
+         */
+        sortOnStoreEvents: ['storeColumnChanged','storeDataLoaded'],
+        /**
+         * 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 : []
+    },
+    
+    /**
+     * Property: sorters
+     * an object listing the different sorters available
+     */
+    sorters : {
+        quick : "Quicksort",
+        merge : "Mergesort",
+        heap : "Heapsort",
+        'native' : "Nativesort"
+    },
+    
+    /**
+     * Method: init
+     * initialize this strategy
+     */
+    init: function () {
+        this.parent();
+        this.bound.sort = this.sort.bind(this);
+    },
+    
+    /**
+     * APIMethod: activate
+     * activates the strategy if it isn't already active.
+     */
+    activate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.addEvent(ev, this.bound.sort);
+            },this);
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * deactivates the strategy if it is already active.
+     */
+    deactivate: function () {
+        if ($defined(this.options.sortOnStoreEvents)) {
+            this.options.sortOnStoreEvents.each(function (ev) {
+                this.store.removeEvent(ev, this.bound.sort);
+            },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.store.count()) {
+            this.store.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.store.data = this.doSort(c[0], sort, this.store.data, true);
+        
+            if (c.length > 1) {
+                this.store.data = this.subSort(this.store.data, 0, 1);
+            }
+        
+            if ($defined(dir) && dir === 'desc') {
+                this.store.data.reverse();
+            }
+        
+            this.store.fireEvent('storeSortFinished', 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: resolveCol
+     * resolves the given column identifier and resolves it to the 
+     * actual column object in the store.
+     * 
+     * Parameters:
+     * col - the name or index of the required column.
+     */
+    resolveCol: function (col) {
+        var t = Jx.type(col);
+        if (t === 'number') {
+            col = this.store.options.columns[col];
+        } else if (t === 'string') {
+            this.store.options.columns.each(function (column) {
+                if (column.name === col) {
+                    col = column;
+                }
+            }, this);
+        }
+        return col;   
+    }
+});/*
+---
+
+name: Jx.Store.Parser
+
+description: Base class for all data parsers. Parsers are used by protocols to get data received or sent in the proper formats.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store
+
+provides: [Jx.Store.Parser]
+
+...
+ */
+// $Id: parser.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Store.Parser
+ * 
+ * Extends: <Jx.Object>
+ * 
+ * Base class for all parsers
+ *
+ * License: 
+ * Copyright (c) 2009, Jon Bomgardner.
+ * 
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Store.Parser = new Class({
+    
+    Extends: Jx.Object,
+    Family: 'Jx.Store.Parser',
+    
+    /**
+     * APIMethod: parse
+     * Reads data passed to it by a protocol and parses it into a specific
+     * format needed by the store/record.
+     * 
+     * Parameters:
+     * data - string of data to parse
+     */
+    parse: $empty,
+    /**
+     * APIMethod: encode
+     * Takes an Jx.Record object and encodes it into a format that can be transmitted 
+     * by a protocol.
+     * 
+     * Parameters:
+     * object - an object to encode
+     */
+    encode: $empty
+});/*
+---
+
+name: Jx.Store.Parser.JSON
+
+description: Parser for reading and writting JSON formatted data.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Store.Parser
+ - Core/JSON
+
+provides: [Jx.Store.Parser.JSON]
+
+...
+ */
+// $Id: parser.json.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Store.Parser.JSON
+ *
+ * Extends: <Jx.Store.Parser>
+ *
+ * A Parser that handles encoding and decoding JSON strings
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Store.Parser.JSON = new Class({
+
+    Extends: Jx.Store.Parser,
+
+    options: {
+        /**
+         * Option: secure
+         * Whether to use secure decoding. When using secure decoding the
+         * parser will return null if any invalid JSON characters are in the
+         * passed in string. Defaults to false.
+         */
+        secure: false
+    },
+    /**
+     * APIMethod: parse
+     * Turns a string into a JSON object if possible.
+     *
+     * Parameters:
+     * data - the string representation of the data we're parsing
+     */
+    parse: function (data) {
+        var type = Jx.type(data);
+
+        if (type === 'string') {
+            return JSON.decode(data, this.options.secure);
+        }
+        //otherwise just return the data object
+        return data;
+    },
+
+    /**
+     * APIMethod: encode
+     * Takes an object and turns it into JSON.
+     *
+     * Parameters:
+     * object - the object to encode
+     */
+    encode: function (object) {
+        var data;
+        if (object instanceof Jx.Record) {
+            data = object.asHash();
+        } else {
+            data = object;
+        }
+
+        return JSON.encode(data);
+    }
+});/*
+---
+
+name: Jx.Button
+
+description: Jx.Button creates a clickable element that can be added to a web page.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+optional:
+ - Core/Drag
+
+provides: [Jx.Button]
+
+css:
+ - button
+
+images:
+ - button.png
+
+...
+ */
+// $Id: button.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button
+ *
+ * Extends: <Jx.Widget>
+ *
+ * Jx.Button creates a clickable element that can be added to a web page.
+ * When the button is clicked, it fires a 'click' event.
+ *
+ * When you construct a new instance of Jx.Button, the button does not
+ * automatically get inserted into the web page.  Typically a button
+ * is used as part of building another capability such as a Jx.Toolbar.
+ * However, if you want to manually insert the button into your application,
+ * you may use the <Jx.Button::addTo> method to append or insert the button into the
+ * page.
+ *
+ * There are two modes for a button, normal and toggle.  A toggle button
+ * has an active state analogous to a checkbox.  A toggle button generates
+ * different events (down and up) from a normal button (click).  To create
+ * a toggle button, pass toggle: true to the Jx.Button constructor.
+ *
+ * To use a Jx.Button in an application, you should to register for the
+ * 'click' event.  You can pass a function in the 'onClick' option when
+ * constructing a button or you can call the addEvent('click', myFunction)
+ * method.  The addEvent method can be called several times, allowing more
+ * than one function to be called when a button is clicked.  You can use the
+ * removeEvent('click', myFunction) method to stop receiving click events.
+ *
+ * Example:
+ *
+ * (code)
+ * var button = new Jx.Button(options);
+ * button.addTo('myListItem'); // the id of an LI in the page.
+ * (end)
+ *
+ * (code)
+ * Example:
+ * var options = {
+ *     imgPath: 'images/mybutton.png',
+ *     tooltip: 'click me!',
+ *     label: 'click me',
+ *     onClick: function() {
+ *         alert('you clicked me');
+ *     }
+ * };
+ * var button = new Jx.Button(options);
+ * button.addEvent('click', anotherFunction);
+ *
+ * function anotherFunction() {
+ *   alert('a second alert for a single click');
+ * }
+ * (end)
+ *
+ * Events:
+ * click - the button was pressed and released (only if type is not 'toggle').
+ * down - the button is down (only if type is 'toggle')
+ * up - the button is up (only if the type is 'toggle').
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button = new Class({
+    Family: 'Jx.Button',
+    Extends: Jx.Widget,
+
+    options: {
+        /* 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
+         * 16.  If not provided, then the button will have no icon.
+         */
+        image: '',
+        /* Option: tooltip
+         * optional.  A string value to use as the alt/title attribute of the
+         * <A> tag that wraps the button, resulting in a tooltip that appears
+         * when the user hovers the mouse over a button in most browsers.  If
+         * not provided, the button will have no tooltip.
+         */
+        tooltip: '',
+        /* Option: label
+         * optional, default is no label.  A string value that is used as a
+         * label on the button. - use an object for localization: { set: 'Examples', key: 'lanKey', value: 'langValue' }
+         * see widget.js for details
+         */
+        label: '',
+        /* Option: toggle
+         * default true, whether the button is a toggle button or not.
+         */
+        toggle: false,
+        /* Option: toggleClass
+         * A class to apply to the button if it is a toggle button,
+         * 'jxButtonToggle' by default.
+         */
+        toggleClass: 'jxButtonToggle',
+        /* Option: pressedClass
+         * A class to apply to the button when it is pressed,
+         * 'jxButtonPressed' by default.
+         */
+        pressedClass: 'jxButtonPressed',
+        /* Option: activeClass
+         * A class to apply to the buttonwhen it is active,
+         * 'jxButtonActive' by default.
+         */
+        activeClass: 'jxButtonActive',
+
+        /* Option: active
+         * optional, default false.  Controls the initial state of toggle
+         * buttons.
+         */
+        active: false,
+        /* Option: enabled
+         * whether the button is enabled or not.
+         */
+        enabled: true,
+        /* Option: href
+         * set an href on the button's action object, typically an <a> tag.
+         * Default is javascript:void(0) and use onClick.
+         */
+        href: 'javascript:void(0);',
+        /* Option: target
+         * for buttons that have an href, allow setting the target
+         */
+        target: '',
+        /* 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.
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * used to auto-populate this object with element references when
+     * processing templates
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * create a new button.
+     */
+    render: function() {
+        this.parent();
+        var options = this.options,
+            hasFocus,
+            mouseDown;
+        /* is the button toggle-able? */
+        if (options.toggle) {
+            this.domObj.addClass(options.toggleClass);
+        }
+
+        // the clickable part of the button
+        if (this.domA) {
+            this.domA.set({
+                target: options.target,
+                href: options.href,
+                title: this.getText(options.tooltip),
+                alt: this.getText(options.tooltip)
+            });
+            this.domA.addEvents({
+                click: this.clicked.bindWithEvent(this),
+                drag: (function(e) {e.stop();}).bindWithEvent(this),
+                mousedown: (function(e) {
+                    this.domA.addClass(options.pressedClass);
+                    hasFocus = true;
+                    mouseDown = true;
+                    this.focus();
+                }).bindWithEvent(this),
+                mouseup: (function(e) {
+                    this.domA.removeClass(options.pressedClass);
+                    mouseDown = false;
+                }).bindWithEvent(this),
+                mouseleave: (function(e) {
+                    this.domA.removeClass(options.pressedClass);
+                }).bindWithEvent(this),
+                mouseenter: (function(e) {
+                    if (hasFocus && mouseDown) {
+                        this.domA.addClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keydown: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.addClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                keyup: (function(e) {
+                    if (e.key == 'enter') {
+                        this.domA.removeClass(options.pressedClass);
+                    }
+                }).bindWithEvent(this),
+                blur: function() { hasFocus = false; }
+            });
+
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if (this.domImg) {
+            if (options.image || !options.label) {
+                this.domImg.set({
+                    title: this.getText(options.tooltip),
+                    alt: this.getText(options.tooltip)
+                });
+                if (options.image && options.image.indexOf(Jx.aPixel.src) == -1) {
+                    this.domImg.setStyle('backgroundImage',"url("+options.image+")");
+                }
+                if (options.imageClass) {
+                    this.domImg.addClass(options.imageClass);
+                }
+            } else {
+                //remove the image if we don't need it
+                this.domImg.setStyle('display','none');
+            }
+        }
+
+        if (this.domLabel) {
+            if (options.label || this.domA.hasClass('jxDiscloser')) {
+                this.setLabel(options.label);
+            } else {
+                //this.domLabel.removeClass('jx'+this.type+'Label');
+                this.domLabel.setStyle('display','none');
+            }
+        }
+
+        if (options.id) {
+            this.domObj.set('id', options.id);
+        }
+
+        //update the enabled state
+        this.setEnabled(options.enabled);
+
+        //update the active state if necessary
+        if (options.active) {
+            options.active = false;
+            this.setActive(true);
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     *
+     * Parameters:
+     * evt - {Event} the user click event
+     */
+    clicked : function(evt) {
+        var options = this.options;
+        if (options.enabled && !this.isBusy()) {
+            if (options.toggle) {
+                this.setActive(!options.active);
+            } else {
+                this.fireEvent('click', {obj: this, event: evt});
+            }
+        }
+        //return false;
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the button is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the button is enabled or not
+     */
+    isEnabled: function() {
+        return this.options.enabled;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the button.
+     *
+     * Parameters:
+     * enabled - {Boolean} the new enabled state of the button
+     */
+    setEnabled: function(enabled) {
+        this.options.enabled = enabled;
+        if (enabled) {
+            this.domObj.removeClass('jxDisabled');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    /**
+     * APIMethod: isActive
+     * For toggle buttons, this returns true if the toggle button is
+     * currently active and false otherwise.
+     *
+     * Returns:
+     * {Boolean} the active state of a toggle button
+     */
+    isActive: function() {
+        return this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the button
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the button
+     */
+    setActive: function(active) {
+        var options = this.options;
+        if (options.enabled && !this.isBusy()) {
+          if (options.active == active) {
+              return;
+          }
+          options.active = active;
+          if (this.domA) {
+              if (options.active) {
+                  this.domA.addClass(options.activeClass);
+              } else {
+                  this.domA.removeClass(options.activeClass);
+              }
+          }
+          this.fireEvent(active ? 'down':'up', this);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this button to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this button
+     */
+    setImage: function(path) {
+        this.options.image = path;
+        if (this.domImg) {
+            this.domImg.setStyle('backgroundImage',
+                                 "url("+path+")");
+            this.domImg.setStyle('display', path ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     * sets the text of the button.
+     *
+     * Parameters:
+     * label - {String} the new label for the button
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html', this.getText(label));
+            this.domLabel.setStyle('display', label || this.domA.hasClass('jxDiscloser') ? null : 'none');
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     * returns the text of the button.
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the button
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.domA) {
+            this.domA.set({
+                'title':this.getText(tooltip),
+                'alt':this.getText(tooltip)
+            });
+        }
+        //need to account for the tooltip on the image as well
+        if (this.domImg) {
+            //check if title and alt are set...
+            var t = this.domImg.get('title');
+            if ($defined(t)) {
+                //change it...
+                this.domImg.set({
+                    'title':this.getText(tooltip),
+                    'alt':this.getText(tooltip)
+                });
+            }
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this button
+     */
+    focus: function() {
+        if (this.domA) {
+            this.domA.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this button
+     */
+    blur: function() {
+        if (this.domA) {
+            this.domA.blur();
+        }
+    },
+
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the button on langChange Event for
+     * Internationalization
+     */
+    changeText : function(lang) {
+        this.parent();
+        this.setLabel(this.options.label);
+        this.setTooltip(this.options.tooltip);
+    }
+});
+/*
+---
+
+name: Jx.Button.Flyout
+
+description: Flyout buttons expose a panel when the user clicks the button.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+
+provides: [Jx.Button.Flyout]
+
+images:
+ - flyout_chrome.png
+ - emblems.png
+
+...
+ */
+// $Id: flyout.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Flyout
+ *
+ * Extends: <Jx.Button>
+ *
+ * Flyout buttons expose a panel when the user clicks the button.  The
+ * panel can have arbitrary content.  You must provide any necessary
+ * code to hook up elements in the panel to your application.
+ *
+ * When the panel is opened, the 'open' event is fired.  When the panel is
+ * closed, the 'close' event is fired.  You can register functions to handle
+ * these events in the options passed to the constructor (onOpen, onClose).
+ *
+ * The user can close the flyout panel by clicking the button again, by
+ * clicking anywhere outside the panel and other buttons, or by pressing the
+ * 'esc' key.
+ *
+ * Flyout buttons implement <Jx.ContentLoader> which provides the hooks to
+ * insert content into the Flyout element.  Note that the Flyout element
+ * is not appended to the DOM until the first time it is opened, and it is
+ * removed from the DOM when closed.
+ *
+ * It is generally best to specify a width and height for your flyout content
+ * area through CSS to ensure that it works correctly across all browsers.
+ * You can do this for all flyouts using the .jxFlyout CSS selector, or you
+ * can apply specific styles to your content elements.
+ *
+ * A flyout closes other flyouts when it is opened.  It is possible to embed
+ * 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.
+ *
+ * Example:
+ * (code)
+ * var flyout = new Jx.Button.Flyout({
+ *      label: 'flyout',
+ *      content: 'flyoutContent',
+ *      onOpen: function(flyout) {
+ *          console.log('flyout opened');
+ *      },
+ *      onClose: function(flyout) {
+ *          console.log('flyout closed');
+ *      }
+ * });
+ * (end)
+ *
+ * Events:
+ * open - this event is triggered when the flyout is opened.
+ * close - this event is triggered when the flyout is closed.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Flyout = new Class({
+    Family: 'Jx.Button.Flyout',
+    Extends: Jx.Button,
+    Binds: ['keypressHandler', 'clickHandler'],
+    options: {
+        /* Option: template
+         * the HTML structure of the flyout button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"><span class="jxButtonLabel "></span></a></span>',
+        /* Option: contentTemplate
+         * the HTML structure of the flyout content area
+         */
+        contentTemplate: '<div class="jxFlyout"><div class="jxFlyoutContent"></div></div>',
+        /* Option: position
+         * where to position the flyout, see Jx.Widget::position
+         * for details on how to specify this option
+         */
+        position: {
+          horizontal: ['left left', 'right right'],
+          vertical: ['bottom top', 'top bottom']
+        },
+        /* Option: positionElement
+         * the element to position the flyout relative to, by default
+         * it is the domObj of this button and should only be changed
+         * if you really know what you are doing
+         */
+        positionElement: null
+    },
+
+    /**
+     * Property: contentClasses
+     * the classes array for processing the contentTemplate
+     */
+    contentClasses: new Hash({
+        contentContainer: 'jxFlyout',
+        content: 'jxFlyoutContent'
+    }),
+
+    /**
+     * Property: content
+     * the HTML element that contains the flyout content
+     */
+    content: null,
+    /**
+     * Method: render
+     * construct a new instance of a flyout button.
+     */
+    render: function() {
+        var options = this.options;
+        if (!Jx.Button.Flyout.Stack) {
+            Jx.Button.Flyout.Stack = [];
+        }
+        this.parent();
+        this.processElements(options.contentTemplate, this.contentClasses);
+
+        if (options.contentClass) {
+            this.content.addClass(options.contentClass);
+        }
+
+        this.content.store('jxFlyout', this);
+        if(!options.loadOnDemand || options.active) {
+          this.loadContent(this.content);
+        }else{
+          this.addEvent('contentLoaded', function(ev) {
+            this.show(ev);
+          }.bind(this));
+        }
+    },
+    cleanup: function() {
+      this.content.eliminate('jxFlyout');
+      this.content.destroy();
+      this.contentClasses.each(function(v,k){
+        this[k] = null;
+      }, this);
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * Override <Jx.Button::clicked> to hide/show the content area of the
+     * flyout.
+     *
+     * Parameters:
+     * e - {Event} the user event
+     */
+    clicked: function(e) {
+        var options = this.options;
+        if (!options.enabled) {
+            return;
+        }
+        if (this.contentIsLoaded && options.cacheContent) {
+          this.show(e);
+        // load on demand or reload content if caching is disabled
+        } else if (options.loadOnDemand || !options.cacheContent) {
+          this.loadContent(this.content);
+        } else {
+          this.show(e);
+        }
+    },
+   /**
+    * Private Method: show
+    * Shows the Flyout after the content is loaded asynchronously
+    *
+    * Parameters:
+    * e - {Event} - the user or contentLoaded event
+    */
+    show: function(e) {
+        var node,
+            flyout,
+            owner = this.owner,
+            stack = Jx.Button.Flyout.Stack,
+            options = this.options;
+       /* find out what we are contained by if we don't already know */
+        if (!owner) {
+            this.owner = owner = document.body;
+            var node = document.id(this.domObj.parentNode);
+            while (node != document.body && owner == document.body) {
+                var flyout = node.retrieve('jxFlyout');
+                if (flyout) {
+                    this.owner = owner = flyout;
+                    break;
+                } else {
+                    node = document.id(node.parentNode);
+                }
+            }
+        }
+        if (stack[stack.length - 1] == this) {
+            this.hide();
+            return;
+        } else if (owner != document.body) {
+            /* if we are part of another flyout, close any open flyouts
+             * inside the parent and register this as the current flyout
+             */
+            if (owner.currentFlyout == this) {
+                /* if the flyout to close is this flyout,
+                 * hide this and return */
+                this.hide();
+                return;
+            } else if (owner.currentFlyout) {
+                owner.currentFlyout.hide();
+            }
+            owner.currentFlyout = this;
+        } else {
+            /* if we are at the top level, close the entire stack before
+             * we open
+             */
+            while (stack.length) {
+                stack[stack.length - 1].hide();
+            }
+        }
+        // now we go on the stack.
+        stack.push(this);
+        this.fireEvent('beforeOpen');
+
+        options.active = true;
+        this.domA.addClass(options.activeClass);
+        this.contentContainer.setStyle('visibility','hidden');
+        document.id(document.body).adopt(this.contentContainer);
+        this.content.getChildren().each(function(child) {
+            if (child.resize) {
+                child.resize();
+            }
+        });
+        this.showChrome(this.contentContainer);
+
+        var rel = options.positionElement || this.domObj;
+        var pos = $merge(options.position, {
+          offsets: this.chromeOffsets
+        });
+        this.position(this.contentContainer, rel, pos);
+
+        /* 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(document.id(this.content).getMarginBoxSize());
+
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','');
+
+        document.addEvent('keydown', this.keypressHandler);
+        document.addEvent('click', this.clickHandler);
+        this.fireEvent('open', this);
+    },
+
+    /**
+     * APIMethod: hide
+     * Closes the flyout if open
+     */
+    hide: function() {
+        if (this.owner != document.body) {
+            this.owner.currentFlyout = null;
+        }
+        Jx.Button.Flyout.Stack.pop();
+        this.setActive(false);
+        this.contentContainer.dispose();
+        this.unstack(this.contentContainer);
+        document.removeEvent('keydown', this.keypressHandler);
+        document.removeEvent('click', this.clickHandler);
+        this.fireEvent('close', this);
+    },
+    /**
+     * Method: clickHandler
+     * hide flyout if the user clicks outside of the flyout
+     */
+    clickHandler: function(e) {
+        e = new Event(e);
+        var elm = document.id(e.target),
+            flyout = Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1];
+        if (!elm.descendantOf(flyout.content) &&
+            !elm.descendantOf(flyout.domObj)) {
+            flyout.hide();
+        }
+    },
+    /**
+     * Method: keypressHandler
+     * hide flyout if the user presses the ESC key
+     */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            Jx.Button.Flyout.Stack[Jx.Button.Flyout.Stack.length - 1].hide();
+        }
+    }
+});/*
+---
+
+name: Jx.ColorPalette
+
+description: A Jx.ColorPalette presents a user interface for selecting colors.  This is typically combined with a Jx.Button.Color which embeds the color palette in a flyout.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.ColorPalette]
+
+css:
+ - color
+
+images:
+ - grid.png
+
+...
+ */
+// $Id: colorpalette.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.ColorPalette
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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> suJx.Tooltipbclass is provided
+ * (<Jx.Button.Color>) that embeds a colour panel inside a button for easy use
+ * in toolbars.
+ *
+ * Colour changes are propogated via a change event.  To be notified
+ * of changes in a Jx.ColorPalette, use the addEvent method.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - triggered when the color changes.
+ * click - the user clicked on a color swatch (emitted after a change event)
+ *
+ * MooTools.lang keys:
+ * - colorpalette.alphaLabel
+ * 
+ * 
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ColorPalette = new Class({
+    Family: 'Jx.ColorPalette',
+    Extends: Jx.Widget,
+    /**
+     * Property: {HTMLElement} domObj
+     * the HTML element representing the color panel
+     */
+    domObj: null,
+    options: {
+        /* Option: parent
+         * default null, the DOM element to add the palette to.
+         */
+        parent: null,
+        /* Option: color
+         * default #000000, the initially selected color
+         */
+        color: '#000000',
+        /* Option: alpha
+         * default 100, the initial alpha value
+         */
+        alpha: 1,
+        /* Option: hexColors
+         * an array of hex colors for creating the palette, defaults to a
+         * set of web safe colors.
+         */
+        hexColors: ['00', '33', '66', '99', 'CC', 'FF']
+    },
+    /**
+     * Method: render
+     * initialize a new instance of Jx.ColorPalette
+     */
+    render: function() {
+        this.domObj = new Element('div', {
+            id: this.options.id,
+            'class':'jxColorPalette'
+        });
+
+        var top = new Element('div', {'class':'jxColorBar'});
+        var d = new Element('div', {'class':'jxColorPreview'});
+
+        this.selectedSwatch = new Element('div', {'class':'jxColorSelected'});
+        this.previewSwatch = new Element('div', {'class':'jxColorHover'});
+        d.adopt(this.selectedSwatch);
+        d.adopt(this.previewSwatch);
+
+        top.adopt(d);
+
+        this.colorInputLabel = new Element('label', {
+          'class':'jxColorLabel', 
+          html:'#'
+        });
+        top.adopt(this.colorInputLabel);
+
+        var cc = this.changed.bind(this);
+        this.colorInput = new Element('input', {
+            'class':'jxHexInput',
+            'type':'text',
+            'maxLength':6,
+            events: {
+                'keyup':cc,
+                'blur':cc,
+                'change':cc
+            }
+        });
+
+        top.adopt(this.colorInput);
+
+        this.alphaLabel = new Element('label', {'class':'jxAlphaLabel', 'html':this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}) });
+        top.adopt(this.alphaLabel);
+
+        this.alphaInput = new Element('input', {
+            'class':'jxAlphaInput',
+            'type':'text',
+            'maxLength':3,
+            events: {
+                'keyup': this.alphaChanged.bind(this)
+            }
+        });
+        top.adopt(this.alphaInput);
+
+        this.domObj.adopt(top);
+
+        var swatchClick = this.swatchClick.bindWithEvent(this);
+        var swatchOver = this.swatchOver.bindWithEvent(this);
+
+        var table = new Element('table', {'class':'jxColorGrid'});
+        var tbody = new Element('tbody');
+        table.adopt(tbody);
+        for (var i=0; i<12; i++) {
+            var tr = new Element('tr');
+            for (var j=-3; j<18; j++) {
+                var bSkip = false;
+                var r, g, b;
+                /* hacky approach to building first three columns
+                 * because I couldn't find a good way to do it
+                 * programmatically
+                 */
+
+                if (j < 0) {
+                    if (j == -3 || j == -1) {
+                        r = g = b = 0;
+                        bSkip = true;
+                    } else {
+                        if (i<6) {
+                            r = g = b = i;
+                        } else {
+                            if (i == 6) {
+                                r = 5; g = 0; b = 0;
+                            } else if (i == 7) {
+                                r = 0; g = 5; b = 0;
+                            } else if (i == 8) {
+                                r = 0; g = 0; b = 5;
+                            } else if (i == 9) {
+                                r = 5; g = 5; b = 0;
+                            } else if (i == 10) {
+                                r = 0; g = 5; b = 5;
+                            } else if (i == 11) {
+                                r = 5; g = 0; b = 5;
+                            }
+                        }
+                    }
+                } else {
+                    /* remainder of the columns are built
+                     * based on the current row/column
+                     */
+                    r = parseInt(i/6,10)*3 + parseInt(j/6,10);
+                    g = j%6;
+                    b = i%6;
+                }
+                var bgColor = '#'+this.options.hexColors[r]+
+                                  this.options.hexColors[g]+
+                                  this.options.hexColors[b];
+
+                var td = new Element('td');
+                if (!bSkip) {
+                    td.setStyle('backgroundColor', bgColor);
+
+                    var a = new Element('a', {
+                        'class': 'colorSwatch ' + (((r > 2 && g > 2) || (r > 2 && b > 2) || (g > 2 && b > 2)) ? 'borderBlack': 'borderWhite'),
+                        'href':'javascript:void(0)',
+                        'title':bgColor,
+                        'alt':bgColor,
+                        events: {
+                            'mouseover': swatchOver,
+                            'click': swatchClick
+                        }
+                    });
+                    a.store('swatchColor', bgColor);
+                    td.adopt(a);
+                } else {
+                    var span = new Element('span', {'class':'emptyCell'});
+                    td.adopt(span);
+                }
+                tr.adopt(td);
+            }
+            tbody.adopt(tr);
+        }
+        this.domObj.adopt(table);
+        this.updateSelected();
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: swatchOver
+     * handle the mouse moving over a colour swatch by updating the preview
+     *
+     * Parameters:
+     * e - {Event} the mousemove event object
+     */
+    swatchOver: function(e) {
+        var a = e.target;
+
+        this.previewSwatch.setStyle('backgroundColor', a.retrieve('swatchColor'));
+    },
+
+    /**
+     * Method: swatchClick
+     * handle mouse click on a swatch by updating the color and hiding the
+     * panel.
+     *
+     * Parameters:
+     * e - {Event} the mouseclick event object
+     */
+    swatchClick: function(e) {
+        var a = e.target;
+
+        this.options.color = a.retrieve('swatchColor');
+        this.updateSelected();
+        this.fireEvent('click', this);
+    },
+
+    /**
+     * Method: changed
+     * handle the user entering a new colour value manually by updating the
+     * selected colour if the entered value is valid HEX.
+     */
+    changed: function() {
+        var color = this.colorInput.value;
+        if (color.substring(0,1) == '#') {
+            color = color.substring(1);
+        }
+        if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+            this.options.color = '#' +color.toUpperCase();
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * Method: alphaChanged
+     * handle the user entering a new alpha value manually by updating the
+     * selected alpha if the entered value is valid alpha (0-100).
+     */
+    alphaChanged: function() {
+        var alpha = this.alphaInput.value;
+        if (alpha.match(/^[0-9]{1,3}$/)) {
+            this.options.alpha = parseFloat(alpha/100);
+            this.updateSelected();
+        }
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the colour represented by this colour panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function( color ) {
+        this.colorInput.value = color;
+        this.changed();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this colour panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function( alpha ) {
+        this.alphaInput.value = alpha;
+        this.alphaChanged();
+    },
+
+    /**
+     * Method: updateSelected
+     * update the colour panel user interface based on the current
+     * colour and alpha values
+     */
+    updateSelected: function() {
+        var styles = {'backgroundColor':this.options.color};
+
+        this.colorInput.value = this.options.color.substring(1);
+
+        this.alphaInput.value = parseInt(this.options.alpha*100,10);
+        if (this.options.alpha < 1) {
+            styles.opacity = this.options.alpha;
+            styles.filter = 'Alpha(opacity='+(this.options.alpha*100)+')';
+            
+        } else {
+            styles.opacity = 1;
+            //not sure what the proper way to remove the filter would be since
+            // I don't have IE to test against.
+            styles.filter = '';  
+        }
+        this.selectedSwatch.setStyles(styles);
+        this.previewSwatch.setStyles(styles);
+        
+        this.fireEvent('change', this);
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the
+     * widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	
+    	if ($defined(this.alphaLabel)) {
+    		this.alphaLabel.set('html', this.getText({set:'Jx',key:'colorpalette',value:'alphaLabel'}));
+    	}
+    }
+});
+
+/*
+---
+
+name: Jx.Button.Color
+
+description:
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button.Flyout
+ - Jx.ColorPalette
+
+provides: [Jx.Button.Color]
+
+...
+ */
+// $Id: color.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Color
+ *
+ * Extends: <Jx.Button.Flyout>
+ *
+ * A <Jx.ColorPalette> wrapped up in a Jx.Button.  The button includes a
+ * preview of the currently selected color.  Clicking the button opens
+ * the color panel.
+ *
+ * A color button is essentially a <Jx.Button.Flyout> where the content
+ * of the flyout is a <Jx.ColorPalette>.  For performance, all color buttons
+ * share an instance of <Jx.ColorPalette> which means only one button can be
+ * open at a time.  This isn't a huge restriction as flyouts already close
+ * each other when opened.
+ *
+ * Example:
+ * (code)
+ * var colorButton = new Jx.Button.Color({
+ *     onChange: function(button) {
+ *         console.log('color:' + button.options.color + ' alpha: ' +
+ *                     button.options.alpha);
+ *     }
+ * });
+ * (end)
+ *
+ * Events:
+ * change - fired when the color is changed.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Color = new Class({
+    Family: 'Jx.Button.Color',
+    Extends: Jx.Button.Flyout,
+
+    /**
+     * Property: swatch
+     * the color swatch element used to portray the currently selected
+     * color
+     */
+    swatch: null,
+
+    options: {
+        /**
+         * Option: color
+         * a color to initialize the panel with, defaults to #000000
+         * (black) if not specified.
+         */
+        color: '#000000',
+        /**
+         * Option: alpha
+         * an alpha value to initialize the panel with, defaults to 1
+         *  (opaque) if not specified.
+         *
+         */
+        alpha: 100,
+        /*
+         * Option: template
+         * the HTML template for the color button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonFlyout jxDiscloser"><span class="jxButtonContent"><span class="jxButtonSwatch"><span class="jxButtonSwatchColor"></span></span><span class="jxButtonLabel"></span></span></a></span>'
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        swatch: 'jxButtonSwatchColor',
+        domLabel: 'jxButtonLabel'
+    }),
+
+    /**
+     * Method: render
+     * creates a new color button.
+     */
+    render: function() {
+        if (!Jx.Button.Color.ColorPalette) {
+            Jx.Button.Color.ColorPalette = new Jx.ColorPalette(this.options);
+        }
+
+        /* we need to have an image to replace, but if a label is
+           requested, there wouldn't normally be an image. */
+        this.options.image = Jx.aPixel.src;
+
+        /* now we can safely initialize */
+        this.parent();
+        this.updateSwatch();
+
+        this.bound.changed = this.changed.bind(this);
+        this.bound.hide = this.hide.bind(this);
+    },
+    cleanup: function() {
+      this.bound.changed = false;
+      this.bound.hide = false;
+      this.parent();
+    },
+    /**
+     * APIMethod: clicked
+     * override <Jx.Button.Flyout> to use a singleton color palette.
+     */
+    clicked: function() {
+        var cp = Jx.Button.Color.ColorPalette;
+        if (cp.currentButton) {
+            cp.currentButton.hide();
+        }
+        cp.currentButton = this;
+        cp.addEvent('change', this.bound.changed);
+        cp.addEvent('click', this.bound.hide);
+        this.content.appendChild(cp.domObj);
+        cp.domObj.setStyle('display', 'block');
+        Jx.Button.Flyout.prototype.clicked.apply(this, arguments);
+        /* setting these before causes an update problem when clicking on
+         * a second color button when another one is open - the color
+         * wasn't updating properly
+         */
+
+        cp.options.color = this.options.color;
+        cp.options.alpha = this.options.alpha/100;
+        cp.updateSelected();
+},
+
+    /**
+     * APIMethod: hide
+     * hide the color panel
+     */
+    hide: function() {
+        var cp = Jx.Button.Color.ColorPalette;
+        this.setActive(false);
+        cp.removeEvent('change', this.bound.changed);
+        cp.removeEvent('click', this.bound.hide);
+        Jx.Button.Flyout.prototype.hide.apply(this, arguments);
+        cp.currentButton = null;
+    },
+
+    /**
+     * APIMethod: setColor
+     * set the color represented by this color panel
+     *
+     * Parameters:
+     * color - {String} the new hex color value
+     */
+    setColor: function(color) {
+        this.options.color = color;
+        this.updateSwatch();
+    },
+
+    /**
+     * APIMethod: setAlpha
+     * set the alpha represented by this color panel
+     *
+     * Parameters:
+     * alpha - {Integer} the new alpha value (between 0 and 100)
+     */
+    setAlpha: function(alpha) {
+        this.options.alpha = alpha;
+        this.updateSwatch();
+    },
+
+    /**
+     * Method: changed
+     * handle the color changing in the palette by updating the preview swatch
+     * in the button and firing the change event.
+     *
+     * Parameters:
+     * panel - <Jx.ColorPalette> the palette that changed.
+     */
+    changed: function(panel) {
+        var changed = false;
+        if (this.options.color != panel.options.color) {
+            this.options.color = panel.options.color;
+            changed = true;
+        }
+        if (this.options.alpha != panel.options.alpha * 100) {
+            this.options.alpha = panel.options.alpha * 100;
+            changed = true;
+        }
+        if (changed) {
+            this.updateSwatch();
+            this.fireEvent('change',this);
+        }
+    },
+
+    /**
+     * Method: updateSwatch
+     * Update the swatch color for the current color
+     */
+    updateSwatch: function() {
+        var styles = {'backgroundColor':this.options.color};
+        if (this.options.alpha < 100) {
+            styles.filter = 'Alpha(opacity='+(this.options.alpha)+')';
+            styles.opacity = this.options.alpha / 100;
+
+        } else {
+            styles.opacity = 1;
+            styles.filter = '';
+        }
+        this.swatch.setStyles(styles);
+    }
+});
+/*
+---
+
+name: Jx.Menu
+
+description: A main menu as opposed to a sub menu that lives inside the menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.List
+
+provides: [Jx.Menu]
+
+css:
+ - menu
+
+images:
+ - flyout_chrome.png
+ - emblems.png
+...
+ */
+// $Id: menu.js 1012 2011-03-03 20:37:26Z pagameba $
+/**
+ * Class: Jx.Menu
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A main menu as opposed to a sub menu that lives inside the menu.
+ *
+ * TODO: Jx.Menu
+ * revisit this to see if Jx.Menu and Jx.SubMenu can be merged into
+ * a single implementation.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu = new Class({
+    Family: 'Jx.Menu',
+    Extends: Jx.Widget,
+    // Binds: ['onMouseEnter','onMouseLeave','hide','keypressHandler'],
+    /**
+     * Property: button
+     * {<Jx.Button>} The button that represents this menu in a toolbar and
+     * opens the menu.
+     */
+    button : null,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML element that contains the menu items
+     * within the menu.
+     */
+    subDomObj : null,
+    /**
+     * Property: list
+     * {<Jx.List>} the list of items in the menu
+     */
+    list: null,
+
+    parameters: ['buttonOptions', 'options'],
+
+    options: {
+        /**
+         * Option: exposeOnHover
+         * {Boolean} default false, if set to true the menu will show
+         * when the mouse hovers over it rather than when it is clicked.
+         */
+        exposeOnHover: false,
+        /**
+         * Option: hideDelay
+         * {Integer} default 0, if greater than 0, this is the number of
+         * milliseconds to delay before hiding a menu when the mouse leaves
+         * the menu button or list.
+         */
+        hideDelay: 0,
+        template: "<div class='jxMenuContainer'><ul class='jxMenu'></ul></div>",
+        buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><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: new Hash({
+        contentContainer: 'jxMenuContainer',
+        subDomObj: 'jxMenu'
+    }),
+    
+    init: function() {
+        this.bound.stop = function(e){e.stop();};
+        this.bound.remove = function(item) {item.setOwner(null);};
+        this.bound.show = this.show.bind(this);
+        this.bound.mouseenter = this.onMouseEnter.bind(this);
+        this.bound.mouseleave = this.onMouseLeave.bind(this);
+        this.bound.keypress = this.keypressHandler.bind(this);
+        this.bound.hide = this.hide.bind(this);
+        this.parent();
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.
+     */
+    render : function() {
+        this.parent();
+        if (!Jx.Menu.Menus) {
+            Jx.Menu.Menus = [];
+        }
+
+        this.contentClone = this.contentContainer.clone();
+        this.list = new Jx.List(this.subDomObj, {
+            onRemove: this.bound.remove
+        });
+
+        /* if options are passed, make a button inside an LI so the
+           menu can be embedded inside a toolbar */
+        if (this.options.buttonOptions) {
+            this.button = new Jx.Button($merge(this.options.buttonOptions,{
+                template: this.options.buttonTemplate,
+                onClick:this.bound.show
+            }));
+
+            this.button.domA.addEvent('mouseenter', this.bound.mouseenter);
+            this.button.domA.addEvent('mouseleave', this.bound.mouseleave);
+
+            this.domObj = this.button.domObj;
+            this.domObj.store('jxMenu', this);
+        }
+        
+        this.subDomObj.addEvent('mouseenter', this.bound.mouseenter);
+        this.subDomObj.addEvent('mouseleave', this.bound.mouseleave);
+        this.subDomObj.store('jxSubMenu', this);
+        
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    cleanup: function() {
+      if (this.hideTimer) {
+        window.clearTimeout(this.hideTimer);
+      }
+      this.list.removeEvent('remove', this.bound.remove);
+      this.list.destroy();
+      this.list = null;
+      if (this.button) {
+        this.domObj.eliminate('jxMenu');
+        this.domObj = null;
+        this.button.removeEvent('click', this.bound.show);
+        this.button.domA.removeEvents({
+          mouseenter: this.bound.mouseenter,
+          mouseleave: this.bound.mouseleave
+        });
+        
+        this.button.destroy();
+        this.button = null;
+      }
+      this.subDomObj.removeEvents({
+        mouseenter: this.bound.mouseenter,
+        mouseleave: this.bound.mouseleave
+      });
+      this.subDomObj.removeEvents();
+      this.contentContainer.removeEvent('contextmenu', this.bound.stop);
+      this.subDomObj.destroy();
+      this.contentContainer.destroy();
+      this.contentClone.destroy();
+      this.bound.remove = null;
+      this.bound.show = null;
+      this.bound.stop = null;
+      this.bound.mouseenter = null;
+      this.bound.mouseleave = null;
+      this.bound.keypress = null;
+      this.bound.hide = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     *     can be added by passing an array of menu items.
+     * position - the index to add the item at, defaults to the end of the
+     *     menu
+     */
+    add: function(item, position, owner) {
+        if (Jx.type(item) == 'array') {
+            item.each(function(i){
+                i.setOwner(owner||this);
+            }, this);
+        } else {
+            item.setOwner(owner||this);
+        }
+        this.list.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: 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;
+    },
+    /**
+     * APIMethod: empty
+     * Empty the menu of items
+     */
+    empty: function() {
+      this.list.each(function(item){
+        if (item.empty) {
+          item.empty();
+        }
+        item.setOwner(null);
+      }, this);
+      this.list.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the menu by hiding it.
+     */
+    deactivate: function() {this.hide();},
+    /**
+     * Method: onMouseOver
+     * Handle the user moving the mouse over the button for this menu
+     * by showing this menu and hiding the other menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseEnter: function(e) {
+      if (this.hideTimer) {
+        window.clearTimeout(this.hideTimer);
+        this.hideTimer = null;
+      }
+      if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] != this) {
+          this.show.delay(1,this);
+      } else if (this.options.exposeOnHover) {
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+          Jx.Menu.Menus[0] = null;
+        }
+        this.show.delay(1,this);
+      }
+    },
+    /**
+     * Method: onMouseLeave
+     * Handle the user moving the mouse off this button or menu by
+     * starting the hide process if so configured.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    onMouseLeave: function(e) {
+      if (this.options.hideDelay > 0) {
+        this.hideTimer = (function(){
+          this.deactivate();
+        }).delay(this.options.hideDelay, this);
+      }
+    },
+    
+    /**
+     * Method: eventInMenu
+     * determine if an event happened inside this menu or a sub menu
+     * of this menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     *
+     * Returns:
+     * {Boolean} true if the event happened in the menu or
+     * a sub menu of this menu, false otherwise
+     */
+    eventInMenu: function(e) {
+        var target = document.id(e.target);
+        if (!target) {
+            return false;
+        }
+        if (target.descendantOf(this.domObj) ||
+            target.descendantOf(this.subDomObj)) {
+            return true;
+        } else {
+            var ul = target.getParent('ul');
+            if (ul) {
+                var sm = ul.retrieve('jxSubMenu');
+                if (sm) {
+                    if (sm.eventInMenu(e)) {
+                      return true;
+                    }
+                    var owner = sm.owner;
+                    while (owner) {
+                        if (owner == this) {
+                            return true;
+                        }
+                        owner = owner.owner;
+                    }
+                }
+            }
+            return false;
+        }
+    },
+
+    /**
+     * APIMethod: hide
+     * Hide the menu.
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    hide: function(e) {
+        if (e) {
+            if (this.visibleItem && this.visibleItem.eventInMenu) {
+                if (this.visibleItem.eventInMenu(e)) {
+                    return;
+                }
+            } else if (this.eventInMenu(e)) {
+                return;
+            }
+        }
+        if (Jx.Menu.Menus[0] && Jx.Menu.Menus[0] == this) {
+            Jx.Menu.Menus[0] = null;
+        }
+        if (this.button && this.button.domA) {
+            this.button.domA.removeClass(this.button.options.activeClass);
+        }
+        if (this.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+        this.list.each(function(item){item.retrieve('jxMenuItem').hide(e);});
+        document.removeEvent('mousedown', this.bound.hide);
+        document.removeEvent('keydown', this.bound.keypress);
+        this.unstack(this.contentContainer);
+        this.contentContainer.dispose();
+        this.visibleItem = null;
+        this.fireEvent('hide', this);
+    },
+    /**
+     * APIMethod: show
+     * Show the menu
+     */
+    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.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+
+        this.subDomObj.dispose();
+        this.contentContainer.destroy();
+        this.contentContainer = this.contentClone.clone();
+        this.contentContainer.empty().adopt(this.subDomObj);
+        this.contentContainer.addEvent('contextmenu', this.bound.stop);
+        this.contentContainer.setStyle('display','none');
+        document.id(document.body).adopt(this.contentContainer);
+        this.contentContainer.setStyles({
+            visibility: 'hidden',
+            display: 'block'
+        });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+        this.showChrome(this.contentContainer);
+
+        this.position(this.contentContainer, this.domObj, $merge({
+            offsets: this.chromeOffsets
+        }, this.options.position));
+        this.stack(this.contentContainer);
+        this.contentContainer.setStyle('visibility','visible');
+
+        if (this.button && this.button.domA) {
+            this.button.domA.addClass(this.button.options.activeClass);
+        }
+
+        /* fix bug in IE that closes the menu as it opens 
+         * because of bubbling (I think)
+         */
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keydown', this.bound.keypress);
+        this.fireEvent('show', this);
+    },
+    /**
+     * APIMethod: setVisibleItem
+     * Set the sub menu that is currently open
+     *
+     * Parameters:
+     * obj- {<Jx.SubMenu>} the sub menu that just became visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.hideTimer) {
+          window.clearTimeout(this.hideTimer);
+        }
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    },
+
+    /* hide flyout if the user presses the ESC key */
+    keypressHandler: function(e) {
+        e = new Event(e);
+        if (e.key == 'esc') {
+            this.hide();
+        }
+    },
+    /**
+     * APIMethod: isEnabled
+     * This returns true if the menu is enabled, false otherwise
+     *
+     * Returns:
+     * {Boolean} whether the menu is enabled or not
+     */
+    isEnabled: function() {
+        return this.button ? this.button.isEnabled() : this.options.enabled ;
+    },
+
+    /**
+     * APIMethod: setEnabled
+     * enable or disable the menu.
+     *
+     * Parameters:
+     * enabled - {Boolean} the new enabled state of the menu
+     */
+    setEnabled: function(enabled) {
+        return this.button ? this.button.setEnabled(enabled) : this.options.enable;
+    },
+    /**
+     * APIMethod: isActive
+     * returns true if the menu is open.
+     *
+     * Returns:
+     * {Boolean} the active state of the menu
+     */
+    isActive: function() {
+        return this.button ? this.button.isActive() : this.options.active;
+    },
+    /**
+     * APIMethod: setActive
+     * Set the active state of the menu
+     *
+     * Parameters:
+     * active - {Boolean} the new active state of the menu
+     */
+    setActive: function(active) {
+        if (this.button) {
+          this.button.setActive(active);
+        }
+    },
+    /**
+     * APIMethod: setImage
+     * set the image of this menu to a new image URL
+     *
+     * Parameters:
+     * path - {String} the new url to use as the image for this menu
+     */
+    setImage: function(path) {
+        if (this.button) {
+          this.button.setImage(path);
+        }
+    },
+    /**
+     * APIMethod: setLabel
+     *
+     * sets the text of the menu.
+     *
+     * Parameters:
+     *
+     * label - {String} the new label for the menu
+     */
+    setLabel: function(label) {
+        if (this.button) {
+          this.button.setLabel(label);
+        }
+    },
+    /**
+     * APIMethod: getLabel
+     *
+     * returns the text of the menu.
+     */
+    getLabel: function() {
+        return this.button ? this.button.getLabel() : '';
+    },
+    /**
+     * APIMethod: setTooltip
+     * sets the tooltip displayed by the menu
+     *
+     * Parameters:
+     * tooltip - {String} the new tooltip
+     */
+    setTooltip: function(tooltip) {
+        if (this.button) {
+          this.button.setTooltip(tooltip);
+        }
+    },
+    /**
+     * APIMethod: focus
+     * capture the keyboard focus on this menu
+     */
+    focus: function() {
+        if (this.button) {
+          this.button.focus();
+        }
+    },
+    /**
+     * APIMethod: blur
+     * remove the keyboard focus from this menu
+     */
+    blur: function() {
+        if (this.button) {
+          this.button.blur();
+        }
+    }
+});
+
+/*
+---
+
+name: Jx.Menu.Item
+
+description: A menu item is a single entry in a menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Item]
+
+images:
+ - menuitem.png
+...
+ */
+// $Id: menu.item.js 967 2010-07-20 13:31:44Z pagameba $
+/**
+ * Class: Jx.Menu.Item
+ *
+ * Extends: <Jx.Button>
+ *
+ * A menu item is a single entry in a menu.  It is typically composed of
+ * a label and an optional icon.  Selecting the menu item emits an event.
+ *
+ * Jx.Menu.Item is represented by a <Jx.Button> with type MenuItem and the
+ * associated CSS changes noted in <Jx.Button>.  The container of a MenuItem
+ * is an 'li' element.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - fired when the menu item is clicked.
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Item = new Class({
+    Family: 'Jx.Menu.Item',
+    Extends: Jx.Button,
+    // Binds: ['onMouseOver'],
+    /**
+     * Property: owner
+     * {<Jx.SubMenu> or <Jx.Menu>} the menu that contains the menu item.
+     */
+    owner: null,
+    options: {
+        //image: null,
+        label: '&nbsp;',
+        toggleClass: 'jxMenuItemToggle',
+        pressedClass: 'jxMenuItemPressed',
+        activeClass: 'jxMenuItemActive',
+        /* 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: new Hash({
+        domObj:'jxMenuItemContainer',
+        domA: 'jxMenuItem',
+        domImg: 'jxMenuItemIcon',
+        domLabel: 'jxMenuItemLabel'
+    }),
+    init: function() {
+      this.bound.mouseover = this.onMouseOver.bind(this);
+      this.parent();
+    },
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Menu.Item
+     */
+    render: function() {
+        if (!this.options.image) {
+            this.options.image = Jx.aPixel.src;
+        }
+        this.parent();
+        if (this.options.image && this.options.image != Jx.aPixel.src) {
+            this.domObj.removeClass(this.options.toggleClass);
+        }
+        if (this.options.target) {
+          this.domA.set('target', this.options.target);
+        }
+        this.domObj.addEvent('mouseover', this.bound.mouseover);
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.domObj.removeEvent('mouseover', this.bound.mouseover);
+      this.bound.mouseover = null;
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: function() {this.blur.delay(1,this);},
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty,
+    /**
+     * Method: clicked
+     * Handle the user clicking on the menu item, overriding the <Jx.Button::clicked>
+     * method to facilitate menu tracking
+     *
+     * Parameters:
+     * obj - {Object} an object containing an event property that was the user
+     * event.
+     */
+    clicked: function(obj) {
+        var href = this.options.href && this.options.href.indexOf('javascript:') != 0;
+        if (this.options.enabled) {
+          if (!href) {
+            if (this.options.toggle) {
+                this.setActive.delay(1,this,!this.options.active);
+            }
+            this.fireEvent.delay(1, this, ['click', {obj: this}]);
+            this.blur();
+          }
+          if (this.owner && this.owner.deactivate) {
+              this.owner.deactivate.delay(1, this.owner, obj.event);
+          }
+        }
+        return href ? true : false;
+    },
+    /**
+     * Method: onmouseover
+     * handle the mouse moving over the menu item
+     */
+    onMouseOver: function(e) {
+        e.stop();
+        if (this.owner && this.owner.setVisibleItem) {
+            this.owner.setVisibleItem(this);
+        }
+        return false;
+    },
+    
+    /**
+     * APIMethod: changeText
+     *
+     * updates the label of the menu item on langChange Event for
+     * Internationalization
+     */
+    changeText: function(lang) {
+        this.parent();
+        if (this.owner && this.owner.deactivate) {
+            this.owner.deactivate();
+        }
+    }
+});
+
+/*
+---
+
+name: Jx.ButtonSet
+
+description: A ButtonSet manages a set of Jx.Button instances by ensuring that only one of the buttons is active.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.ButtonSet]
+
+
+...
+ */
+// $Id: set.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.ButtonSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A ButtonSet manages a set of <Jx.Button> instances by ensuring that only
+ * one of the buttons is active.  All the buttons need to have been created
+ * with the toggle option set to true for this to work.
+ *
+ * Example:
+ * (code)
+ * var toolbar = new Jx.Toolbar('bar');
+ * var buttonSet = new Jx.ButtonSet();
+ *
+ * var b1 = new Jx.Button({label: 'b1', toggle:true, contentID: 'content1'});
+ * var b2 = new Jx.Button({label: 'b2', toggle:true, contentID: 'content2'});
+ * var b3 = new Jx.Button({label: 'b3', toggle:true, contentID: 'content3'});
+ * var b4 = new Jx.Button({label: 'b4', toggle:true, contentID: 'content4'});
+ *
+ * buttonSet.add(b1,b2,b3,b4);
+ * (end)
+ *
+ * Events:
+ * change - the current button has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.ButtonSet = new Class({
+    Family: 'Jx.ButtonSet',
+    Extends: Jx.Object,
+    Binds: ['buttonChanged'],
+    /**
+     * Property: buttons
+     * {Array} array of buttons that are managed by this button set
+     */
+    buttons: [],
+    
+    cleanup: function() {
+      this.buttons.each(function(b){
+        b.removeEvent('down', this.buttonChanged);
+        b.setActive = null;
+      },this);
+      this.activeButton = null;
+      this.buttons = null;
+      this.parent();
+    },
+
+    /**
+     * APIMethod: add
+     * Add one or more <Jx.Button>s to the ButtonSet.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} an instance of <Jx.Button> to add to the button
+     * set.  More than one button can be added by passing extra parameters to
+     * this method.
+     */
+    add : function() {
+        $A(arguments).each(function(button) {
+            if (button.domObj.hasClass(button.options.toggleClass)) {
+                button.domObj.removeClass(button.options.toggleClass);
+                button.domObj.addClass(button.options.toggleClass+'Set');
+            }
+            button.addEvent('down',this.buttonChanged);
+            button.setActive = function(active) {
+                if (button.options.active && this.activeButton == button) {
+                    return;
+                } else {
+                    Jx.Button.prototype.setActive.apply(button, [active]);
+                }
+            }.bind(this);
+            if (!this.activeButton || button.options.active) {
+                button.options.active = false;
+                button.setActive(true);
+            }
+            this.buttons.push(button);
+        }, this);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * Remove a button from this Button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove.
+     */
+    remove : function(button) {
+        this.buttons.erase(button);
+        if (this.activeButton == button) {
+            if (this.buttons.length) {
+                this.buttons[0].setActive(true);
+            }
+            button.removeEvent('down',this.buttonChanged);
+            button.setActive = Jx.Button.prototype.setActive;
+        }
+    },
+    /**
+     * APIMethod: empty
+     * empty the button set and clear the active button
+     */
+    empty: function() {
+      this.buttons = [];
+      this.activeButton = null;
+    },
+    /**
+     * APIMethod: setActiveButton
+     * Set the active button to the one passed to this method
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    setActiveButton: function(button) {
+        var b = this.activeButton;
+        this.activeButton = button;
+        if (b && b != button) {
+            b.setActive(false);
+        }
+    },
+    /**
+     * Method: buttonChanged
+     * Handle selection changing on the buttons themselves and activate the
+     * appropriate button in response.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to make active.
+     */
+    buttonChanged: function(button) {
+        this.setActiveButton(button);
+        this.fireEvent('change', this);
+    }
+});/*
+---
+
+name: Jx.Button.Multi
+
+description: 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.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.Menu
+ - Jx.ButtonSet
+
+provides: [Jx.Button.Multi]
+
+images:
+ - button_multi.png
+ - button_multi_disclose.png
+
+...
+ */
+// $Id: multi.js 977 2010-09-02 18:57:42Z pagameba $
+/**
+ * Class: Jx.Button.Multi
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * that button to replace the active button in the toolbar and performs
+ * the button's regular action.
+ *
+ * Other buttons can be added to the Multi button using the add method.
+ *
+ * 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
+ * added buttons are placed.  The main button content is cloned from the
+ * last button clicked (or first button added).
+ *
+ * The Multi button does not trigger any events itself, only the contained
+ * buttons trigger events.
+ *
+ * Example:
+ * (code)
+ * var b1 = new Jx.Button({
+ *     label: 'b1',
+ *     onClick: function(button) {
+ *         console.log('b1 clicked');
+ *     }
+ * });
+ * var b2 = new Jx.Button({
+ *     label: 'b2',
+ *     onClick: function(button) {
+ *         console.log('b2 clicked');
+ *     }
+ * });
+ * var b3 = new Jx.Button({
+ *     label: 'b3',
+ *     onClick: function(button) {
+ *         console.log('b3 clicked');
+ *     }
+ * });
+ * var multiButton = new Jx.Button.Multi();
+ * multiButton.add(b1, b2, b3);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Button.Multi = new Class({
+    Family: 'Jx.Button.Multi',
+    Extends: Jx.Button,
+
+    /**
+     * Property: {<Jx.Button>} activeButton
+     * the currently selected button
+     */
+    activeButton: null,
+
+    /**
+     * Property: buttons
+     * {Array} the buttons added to this multi button
+     */
+    buttons: null,
+
+    options: {
+        /* Option: template
+         * the button template for a multi button
+         */
+        template: '<span class="jxButtonContainer"><a class="jxButton jxButtonMulti jxDiscloser"><span class="jxButtonContent"><img src="'+Jx.aPixel.src+'" class="jxButtonIcon"><span class="jxButtonLabel"></span></span></a><a class="jxButtonDisclose" href="javascript:void(0)"><img src="'+Jx.aPixel.src+'"></a></span>',
+        menuOptions: {}
+    },
+
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxButtonContainer',
+        domA: 'jxButton',
+        domImg: 'jxButtonIcon',
+        domLabel: 'jxButtonLabel',
+        domDisclose: 'jxButtonDisclose'
+    }),
+
+    /**
+     * Method: render
+     * construct a new instance of Jx.Button.Multi.
+     */
+    render: function() {
+        this.parent();
+        this.buttons = [];
+
+        this.menu = new Jx.Menu({}, this.options.menuOptions);
+        this.menu.button = this;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.bound.click = this.clicked.bind(this);
+
+        if (this.domDisclose) {
+            var button = this;
+            var hasFocus;
+
+            this.bound.disclose = {
+              click: function(e) {
+                  if (this.list.count() === 0) {
+                      return;
+                  }
+                  if (!button.options.enabled) {
+                      return;
+                  }
+                  this.contentContainer.setStyle('visibility','hidden');
+                  this.contentContainer.setStyle('display','block');
+                  document.id(document.body).adopt(this.contentContainer);
+                  /* we have to size the container for IE to render the chrome
+                   * correctly but just in the menu/sub menu case - there is
+                   * some horrible peekaboo bug in IE related to ULs that we
+                   * just couldn't figure out
+                   */
+                  this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+                  this.showChrome(this.contentContainer);
+
+                  this.position(this.contentContainer, this.button.domObj, {
+                      horizontal: ['right right'],
+                      vertical: ['bottom top', 'top bottom'],
+                      offsets: this.chromeOffsets
+                  });
+
+                  this.contentContainer.setStyle('visibility','');
+
+                  document.addEvent('mousedown', this.bound.hide);
+                  document.addEvent('keyup', this.bound.keypress);
+
+                  this.fireEvent('show', this);
+              }.bindWithEvent(this.menu),
+              mouseenter:function(){
+                  document.id(this.domObj.firstChild).addClass('jxButtonHover');
+                  if (hasFocus) {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bind(this),
+              mouseleave:function(){
+                  document.id(this.domObj.firstChild).removeClass('jxButtonHover');
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bind(this),
+              mousedown: function(e) {
+                  this.domDisclose.addClass(this.options.pressedClass);
+                  hasFocus = true;
+                  this.focus();
+              }.bindWithEvent(this),
+              mouseup: function(e) {
+                  this.domDisclose.removeClass(this.options.pressedClass);
+              }.bindWithEvent(this),
+              keydown: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.addClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              keyup: function(e) {
+                  if (e.key == 'enter') {
+                      this.domDisclose.removeClass(this.options.pressedClass);
+                  }
+              }.bindWithEvent(this),
+              blur: function() { hasFocus = false; }
+            };
+
+            this.domDisclose.addEvents({
+              click: this.bound.disclose.click,
+              mouseenter: this.bound.disclose.mouseenter,
+              mouseleave: this.bound.disclose.mouseleave,
+              mousedown: this.bound.disclose.mousedown,
+              mouseup: this.bound.disclose.mouseup,
+              keydown: this.bound.disclose.keydown,
+              keyup: this.bound.disclose.keyup,
+              blur: this.bound.disclose.blur
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domDisclose, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+        this.bound.show = function() {
+            this.domA.addClass(this.options.activeClass);
+        }.bind(this);
+        this.bound.hide = function() {
+            if (this.options.active) {
+                this.domA.addClass(this.options.activeClass);
+            }
+        }.bind(this);
+
+        this.menu.addEvents({
+            'show': this.bound.show,
+            'hide': this.bound.hide
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+    },
+    cleanup: function() {
+      var self = this,
+          bound = this.bound;
+      // clean up the discloser
+      if (self.domDisclose) {
+        self.domDisclose.removeEvents({
+          click: bound.disclose.click,
+          mouseenter: bound.disclose.mouseenter,
+          mouseleave: bound.disclose.mouseleave,
+          mousedown: bound.disclose.mousedown,
+          mouseup: bound.disclose.mouseup,
+          keydown: bound.disclose.keydown,
+          keyup: bound.disclose.keyup,
+          blur: bound.disclose.blur
+        });
+      }
+
+      // clean up the button set
+      self.buttonSet.destroy();
+      self.buttonSet = null;
+
+      // clean up the buttons array
+      self.buttons.each(function(b){
+        b.removeEvents();
+        self.menu.remove(b.multiButton);
+        b.multiButton.destroy();
+        b.multiButton = null;
+        b.destroy();
+      });
+      self.buttons.empty();
+      self.buttons = null;
+
+      // clean up the menu object
+      self.menu.removeEvents({
+        'show': bound.show,
+        'hide': bound.hide
+      });
+      // unset the menu button because it references this object
+      self.menu.button = null;
+      self.menu.destroy();
+      self.menu = null;
+
+      // clean up binds and call parent to finish
+      self.bound.show = null;
+      self.bound.hide = null;
+      self.bound.clicked = null;
+      self.bound.disclose = null;
+      self.activeButton = null;
+      self.parent();
+    },
+    /**
+     * APIMethod: add
+     * adds one or more buttons to the Multi button.  The first button
+     * added becomes the active button initialize.  This function
+     * takes a variable number of arguments, each of which is expected
+     * to be an instance of <Jx.Button>.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance, may be repeated in the parameter list
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(theButton){
+          var f,
+              opts,
+              button;
+            if (!theButton instanceof Jx.Button) {
+                return;
+            }
+            theButton.domA.addClass('jxDiscloser');
+            theButton.setLabel(theButton.options.label);
+            this.buttons.push(theButton);
+            f = this.setButton.bind(this, theButton);
+            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;
+            }
+            button = new Jx.Menu.Item(opts);
+            this.buttonSet.add(button);
+            this.menu.add(button);
+            theButton.multiButton = button;
+            theButton.domA.addClass('jxButtonMulti');
+            if (!this.activeButton) {
+                this.domA.dispose();
+                this.setActiveButton(theButton);
+            }
+        }, this);
+    },
+    /**
+     * APIMethod: remove
+     * remove a button from a multi button
+     *
+     * Parameters:
+     * button - {<Jx.Button>} the button to remove
+     */
+    remove: function(button) {
+        if (!button || !button.multiButton) {
+            return;
+        }
+        // the toolbar will only remove the li.toolItem, which is
+        // the parent node of the multiButton's domObj.
+        if (this.menu.remove(button.multiButton)) {
+            button.multiButton = null;
+            if (this.activeButton == button) {
+                // if any buttons are left that are not this button
+                // then set the first one to be the active button
+                // otherwise set the active button to nothing
+                if (!this.buttons.some(function(b) {
+                    if (b != button) {
+                        this.setActiveButton(b);
+                        return true;
+                    } else {
+                        return false;
+                    }
+                }, this)) {
+                    this.setActiveButton(null);
+                }
+            }
+            this.buttons.erase(button);
+        }
+    },
+    /**
+     * APIMethod: empty
+     * remove all buttons from the multi button
+     */
+    empty: function() {
+      this.buttons.each(function(b){this.remove(b);}, this);
+    },
+    /**
+     * APIMethod: setActiveButton
+     * update the menu item to be the requested button.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} a <Jx.Button> instance that was added to this multi button.
+     */
+    setActiveButton: function(button) {
+        if (this.activeButton) {
+            this.activeButton.domA.dispose();
+            this.activeButton.domA.removeEvent('click', this.bound.click);
+        }
+        if (button && button.domA) {
+            this.domObj.grab(button.domA, 'top');
+            this.domA = button.domA;
+            this.domA.addEvent('click', this.bound.click);
+            if (this.options.toggle) {
+                this.options.active = false;
+                this.setActive(true);
+            }
+        }
+        this.activeButton = button;
+    },
+    /**
+     * Method: setButton
+     * update the active button in the menu item, trigger the button's action
+     * and hide the flyout that contains the buttons.
+     *
+     * Parameters:
+     * button - {<Jx.Button>} The button to set as the active button
+     */
+    setButton: function(button) {
+        this.setActiveButton(button);
+        button.clicked();
+    }
+});/*
+---
+
+name: Jx.Layout
+
+description: Jx.Layout is used to provide more flexible layout options for applications
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+
+provides: [Jx.Layout]
+
+css:
+ - layout
+
+...
+ */
+// $Id: layout.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Layout
+ *
+ * Extends: <Jx.Object>
+ *
+ * Jx.Layout is used to provide more flexible layout options for applications
+ *
+ * Jx.Layout wraps an existing DOM element (typically a div) and provides
+ * extra functionality for sizing that element within its parent and sizing
+ * elements contained within it that have a 'resize' function attached to them.
+ *
+ * To create a Jx.Layout, pass the element or id plus an options object to
+ * the constructor.
+ *
+ * Example:
+ * (code)
+ * var myContainer = new Jx.Layout('myDiv', options);
+ * (end)
+ *
+ * Events:
+ * sizeChange - fired when the size of the container changes
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Layout = new Class({
+    Family: 'Jx.Layout',
+    Extends: Jx.Object,
+
+    options: {
+        /* Option: resizeWithWindow
+         * boolean, automatically resize this layout when the window size
+         * changes, even if the element is not a direct descendant of the
+         * BODY.  False by default.
+         */
+        resizeWithWindow: false,
+        /* Option: propagate
+         * boolean, controls propogation of resize to child nodes.
+         * True by default. If set to false, changes in size will not be
+         * propogated to child nodes.
+         */
+        propagate: true,
+        /* Option: position
+         * how to position the element, either 'absolute' or 'relative'.
+         * The default (if not passed) is 'absolute'.  When using
+         * 'absolute' positioning, both the width and height are
+         * controlled by Jx.Layout.  If 'relative' positioning is used
+         * then only the width is controlled, allowing the height to
+         * be controlled by its content.
+         */
+        position: 'absolute',
+        /* Option: left
+         * the distance (in pixels) to maintain the left edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the left edge can be any distance from its parent
+         * based on other parameters.
+         */
+        left: 0,
+        /* Option: right
+         * the distance (in pixels) to maintain the right edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the right edge can be any distance from its parent
+         * based on other parameters.
+         */
+        right: 0,
+        /* Option: top
+         * the distance (in pixels) to maintain the top edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the top edge can be any distance from its parent
+         * based on other parameters.
+         */
+        top: 0,
+        /* Option: bottom
+         * the distance (in pixels) to maintain the bottom edge of the element
+         * from its parent element.  The default value is 0.  If this is set
+         * to 'null', then the bottom edge can be any distance from its parent
+         * based on other parameters.
+         */
+        bottom: 0,
+        /* Option: width
+         * the width (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the width can be any value based on
+         * other parameters.
+         */
+        width: null,
+        /* Option: height
+         * the height (in pixels) of the element.  The default value is null.
+         * If this is set to 'null', then the height can be any value based on
+         * other parameters.
+         */
+        height: null,
+        /* Option: minWidth
+         * the minimum width that the element can be sized to.  The default
+         * value is 0.
+         */
+        minWidth: 0,
+        /* Option: minHeight
+         * the minimum height that the element can be sized to.  The
+         * default value is 0.
+         */
+        minHeight: 0,
+        /* Option: maxWidth
+         * the maximum width that the element can be sized to.  The default
+         * value is -1, which means no maximum.
+         */
+        maxWidth: -1,
+        /* Option: maxHeight
+         * the maximum height that the element can be sized to.  The
+         * default value is -1, which means no maximum.
+         */
+        maxHeight: -1
+    },
+
+    /**
+     * Parameters:
+     * domObj - {HTMLElement} element or id to apply the layout to
+     * options - <Jx.Layout.Options>
+     */
+    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);
+
+        if (this.options.resizeWithWindow || document.body == this.domObj.parentNode) {
+            window.addEvent('resize', this.windowResize.bindWithEvent(this));
+            window.addEvent('load', this.windowResize.bind(this));
+        }
+        //this.resize();
+    },
+
+    /**
+     * Method: windowResize
+     * when the window is resized, any Jx.Layout controlled elements that are
+     * direct children of the BODY element are resized
+     */
+     windowResize: function() {
+         this.resize();
+         if (this.resizeTimer) {
+             $clear(this.resizeTimer);
+             this.resizeTimer = null;
+         }
+         this.resizeTimer = this.resize.delay(50, this);
+    },
+
+    /**
+     * Method: resize
+     * resize the element controlled by this Jx.Layout object.
+     *
+     * Parameters:
+     * options - new options to apply, see <Jx.Layout.Options>
+     */
+    resize: function(options) {
+         /* this looks like a really big function but actually not
+          * much code gets executed in the two big if statements
+          */
+        this.resizeTimer = null;
+        var needsResize = false;
+        if (options) {
+            for (var i in options) {
+                //prevent forceResize: false from causing a resize
+                if (i == 'forceResize') {
+                    continue;
+                }
+                if (this.options[i] != options[i]) {
+                    needsResize = true;
+                    this.options[i] = options[i];
+                }
+            }
+            if (options.forceResize) {
+                needsResize = true;
+            }
+        }
+        if (!document.id(this.domObj.parentNode)) {
+            return;
+        }
+
+        var parentSize;
+        if (this.domObj.parentNode.tagName == 'BODY') {
+            parentSize = Jx.getPageDimensions();
+        } else {
+            parentSize = document.id(this.domObj.parentNode).getContentBoxSize();
+        }
+
+        if (this.lastParentSize && !needsResize) {
+            needsResize = (this.lastParentSize.width != parentSize.width ||
+                          this.lastParentSize.height != parentSize.height);
+        } else {
+            needsResize = true;
+        }
+        this.lastParentSize = parentSize;
+
+        if (!needsResize) {
+            return;
+        }
+
+        var l, t, w, h;
+
+        /* calculate left and width */
+        if (this.options.left != null) {
+            /* fixed left */
+            l = this.options.left;
+            if (this.options.right == null) {
+                /* variable right */
+                if (this.options.width == null) {
+                    /* variable right and width
+                     * set right to min, stretch width */
+                    w = parentSize.width - l;
+                    if (w < this.options.minWidth ) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* variable right, fixed width
+                     * use width
+                     */
+                    w = this.options.width;
+                }
+            } else {
+                /* fixed right */
+                if (this.options.width == null) {
+                    /* fixed right, variable width
+                     * stretch width
+                     */
+                    w = parentSize.width - l - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        w = this.options.maxWidth;
+                    }
+                } else {
+                    /* fixed right, fixed width
+                     * respect left and width, allow right to stretch
+                     */
+                    w = this.options.width;
+                }
+            }
+
+        } else {
+            if (this.options.right == null) {
+                if (this.options.width == null) {
+                    /* variable left, width and right
+                     * set left, right to min, stretch width
+                     */
+                     l = 0;
+                     w = parentSize.width;
+                     if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                         l = l + parseInt(w - this.options.maxWidth,10)/2;
+                         w = this.options.maxWidth;
+                     }
+                } else {
+                    /* variable left, fixed width, variable right
+                     * distribute space between left and right
+                     */
+                    w = this.options.width;
+                    l = parseInt((parentSize.width - w)/2,10);
+                    if (l < 0) {
+                        l = 0;
+                    }
+                }
+            } else {
+                if (this.options.width != null) {
+                    /* variable left, fixed width, fixed right
+                     * left is calculated directly
+                     */
+                    w = this.options.width;
+                    l = parentSize.width - w - this.options.right;
+                    if (l < 0) {
+                        l = 0;
+                    }
+                } else {
+                    /* variable left and width, fixed right
+                     * set left to min value and stretch width
+                     */
+                    l = 0;
+                    w = parentSize.width - this.options.right;
+                    if (w < this.options.minWidth) {
+                        w = this.options.minWidth;
+                    }
+                    if (this.options.maxWidth >= 0 && w > this.options.maxWidth) {
+                        l = w - this.options.maxWidth - this.options.right;
+                        w = this.options.maxWidth;
+                    }
+                }
+            }
+        }
+
+        /* calculate the top and height */
+        if (this.options.top != null) {
+            /* fixed top */
+            t = this.options.top;
+            if (this.options.bottom == null) {
+                /* variable bottom */
+                if (this.options.height == null) {
+                    /* variable bottom and height
+                     * set bottom to min, stretch height */
+                    h = parentSize.height - t;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* variable bottom, fixed height
+                     * stretch height
+                     */
+                    h = this.options.height;
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = h - this.options.maxHeight;
+                        h = this.options.maxHeight;
+                    }
+                }
+            } else {
+                /* fixed bottom */
+                if (this.options.height == null) {
+                    /* fixed bottom, variable height
+                     * stretch height
+                     */
+                    h = parentSize.height - t - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        h = this.options.maxHeight;
+                    }
+                } else {
+                    /* fixed bottom, fixed height
+                     * respect top and height, allow bottom to stretch
+                     */
+                    h = this.options.height;
+                }
+            }
+        } else {
+            if (this.options.bottom == null) {
+                if (this.options.height == null) {
+                    /* variable top, height and bottom
+                     * set top, bottom to min, stretch height
+                     */
+                     t = 0;
+                     h = parentSize.height;
+                     if (h < this.options.minHeight) {
+                         h = this.options.minHeight;
+                     }
+                     if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                         t = parseInt((parentSize.height - this.options.maxHeight)/2,10);
+                         h = this.options.maxHeight;
+                     }
+                } else {
+                    /* variable top, fixed height, variable bottom
+                     * distribute space between top and bottom
+                     */
+                    h = this.options.height;
+                    t = parseInt((parentSize.height - h)/2,10);
+                    if (t < 0) {
+                        t = 0;
+                    }
+                }
+            } else {
+                if (this.options.height != null) {
+                    /* variable top, fixed height, fixed bottom
+                     * top is calculated directly
+                     */
+                    h = this.options.height;
+                    t = parentSize.height - h - this.options.bottom;
+                    if (t < 0) {
+                        t = 0;
+                    }
+                } else {
+                    /* variable top and height, fixed bottom
+                     * set top to min value and stretch height
+                     */
+                    t = 0;
+                    h = parentSize.height - this.options.bottom;
+                    if (h < this.options.minHeight) {
+                        h = this.options.minHeight;
+                    }
+                    if (this.options.maxHeight >= 0 && h > this.options.maxHeight) {
+                        t = parentSize.height - this.options.maxHeight - this.options.bottom;
+                        h = this.options.maxHeight;
+                    }
+                }
+            }
+        }
+
+        //TODO: check left, top, width, height against current styles
+        // and only apply changes if they are not the same.
+
+        /* apply the new sizes */
+        var sizeOpts = {width: w};
+        if (this.options.position == 'absolute') {
+            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+m.left,
+                top: t+m.top
+            });
+            sizeOpts.height = h;
+        } else {
+            if (this.options.height) {
+                sizeOpts.height = this.options.height;
+            }
+        }
+        this.domObj.setBorderBoxSize(sizeOpts);
+
+        if (this.options.propagate) {
+            // propogate changes to children
+            var o = {forceResize: options ? options.forceResize : false};
+            $A(this.domObj.childNodes).each(function(child){
+                if (child.resize && child.getStyle('display') != 'none') {
+                    child.resize.delay(0,child,o);
+                }
+            });
+        }
+
+        this.fireEvent('sizeChange',this);
+    }
+});/*
+---
+
+name: Jx.Toolbar
+
+description: A toolbar is a container object that contains other objects such as buttons.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.List
+
+provides: [Jx.Toolbar]
+
+css:
+ - toolbar
+
+images:
+ - toolbar.png
+...
+ */
+// $Id: toolbar.js 960 2010-06-06 22:23: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: list
+     * {<Jx.List>} the list that holds the items in this toolbar
+     */
+    list : 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: {
+        /* 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: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. If scroll is set to true
+         * this option does nothing. Default: 'left', valid values: 'left',
+         * 'center', or 'right'
+         */
+        align: 'left',
+        /* Option: scroll
+         * if true, the toolbar may scroll if the contents are wider than
+         * the size of the toolbar
+         */
+        scroll: true,
+        template: '<ul class="jxToolbar"></ul>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolbar'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxToolbar', this);
+        if ($defined(this.options.id)) {
+            this.domObj.id = this.options.id;
+        }
+
+        this.list = new Jx.List(this.domObj, {
+            onAdd: function(item) {
+                this.fireEvent('add', this);
+            }.bind(this),
+            onRemove: function(item) {
+                this.fireEvent('remove', this);
+            }.bind(this)
+        });
+
+        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 = document.id(parent).retrieve('jxBarContainer');
+        if (!tbc) {
+            tbc = new Jx.Toolbar.Container({
+                parent: parent,
+                position: this.options.position,
+                autoSize: this.options.autoSize,
+                align: this.options.align,
+                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) {
+            var item = thing;
+            if (item.domObj) {
+                item = item.domObj;
+            }
+
+            if (item.tagName == 'LI') {
+                if (!item.hasClass('jxToolItem')) {
+                    item.addClass('jxToolItem');
+                }
+            } else {
+                item = new Jx.Toolbar.Item(thing);
+            }
+            this.list.add(item);
+        }, this);
+        
+        //Update the size of the toolbar container.
+        this.update();
+        
+        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');
+        this.list.remove(li);
+        this.update();
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the toolbar
+     */
+    empty: function() {
+      this.list.each(function(item){this.remove(item);},this);
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the Toolbar (when it is acting as a menu bar).
+     */
+    deactivate: function() {
+        this.list.each(function(item){
+            if (item.retrieve('jxMenu')) {
+                item.retrieve('jxMenu').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);
+    },
+    /**
+     * Method: update
+     * Updates the size of the UL so that the size is always consistently the 
+     * exact size of the size of the sum of the buttons. This will keep all of 
+     * the buttons on one line.
+     */
+    update: function () {
+        // if (['top','bottom'].contains(this.options.position)) {
+        //     (function(){
+        //         var s = 0;
+        //         var children = this.domObj.getChildren();
+        //         children.each(function(button){
+        //             var size = button.getMarginBoxSize();
+        //             s += size.width +0.5;
+        //         },this);
+        //         if (s !== 0) {
+        //             this.domObj.setStyle('width', Math.round(s));
+        //         } else {
+        //             this.domObj.setStyle('width','auto');
+        //         }
+        //     }).delay(1,this);
+        // }
+        this.fireEvent('update');
+    },
+    changeText : function(lang) {
+      this.update();
+    }
+});
+/*
+---
+
+name: Jx.Toolbar.Container
+
+description: A toolbar container contains toolbars.  This has an optional dependency on Fx.Tween that, if included, will allow toolbars that contain more elements than can be displayed to be smoothly scrolled left and right.  Without this optional dependency, the toolbar will jump in fixed increments rather than smoothly scrolling.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+ - Jx.Button
+
+optional:
+ - Core/Fx.Tween
+
+provides: [Jx.Toolbar.Container]
+
+images:
+ - emblems.png
+
+...
+ */
+// $Id: container.js 1006 2011-01-01 22:43:42Z 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,
+    Binds: ['update'],
+    pluginNamespace: 'ToolbarContainer',
+    /**
+     * 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,
+        /**
+         * Option: align
+         * Determines whether the toolbar is aligned left, center, or right.
+         * Mutually exclusive with the scroll option. This option overrides
+         * scroll if set to something other than the default. Default: 'left',
+         * valid values are 'left','center', or 'right'
+         */
+        align: 'left',
+        template: "<div class='jxBarContainer'><div class='jxBarControls'></div></div>",
+        scrollerTemplate: "<div class='jxBarScroller'><div class='jxBarWrapper'></div></div>"
+    },
+    classes: new Hash({
+        domObj: 'jxBarContainer',
+        scroller: 'jxBarScroller',
+        //used to hide the overflow of the wrapper
+        wrapper: 'jxBarWrapper',
+        controls: 'jxBarControls'
+        //used to allow multiple toolbars to float next to each other
+    }),
+
+    updating: false,
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Container
+     */
+    render: function() {
+        this.parent();
+        /* if a container was passed in, use it instead of the one from the
+         * template
+         */
+        if (document.id(this.options.parent)) {
+            this.domObj = document.id(this.options.parent);
+            this.elements = new Hash({
+                'jxBarContainer': this.domObj
+            });
+            this.domObj.addClass('jxBarContainer');
+            this.domObj.grab(this.controls);
+            this.domObj.addEvent('sizeChange', this.update);
+        }
+
+        if (!['center', 'right'].contains(this.options.align) && this.options.scroll) {
+            this.processElements(this.options.scrollerTemplate, this.classes);
+            this.domObj.grab(this.scroller, 'top');
+        }
+        
+        //add the alignment option... not sure why this keeps getting removed??
+        this.domObj.addClass('jxToolbarAlign' + 
+                this.options.align.capitalize());
+
+        /* 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', function(){
+              this.domObj.getParent().addEvent('sizeChange', this.update);
+              this.update();
+            });
+
+            this.scrollLeft = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollLeft).addClass('jxBarScrollLeft');
+            this.scrollLeft.addEvents({
+                click: this.scroll.bind(this, 'left')
+            });
+
+            this.scrollRight = new Jx.Button({
+                image: Jx.aPixel.src
+            }).addTo(this.controls, 'bottom');
+            document.id(this.scrollRight).addClass('jxBarScrollRight');
+            this.scrollRight.addEvents({
+                click: this.scroll.bind(this, 'right')
+            });
+
+        } else if (this.options.scroll && ['left', 'right'].contains(this.options.position)) {
+            //do we do scrolling up and down?
+            //for now disable scroll in this case
+            this.options.scroll = false;
+        } else {
+            this.options.scroll = false;
+        }
+
+        this.addEvent('add', this.update);
+        if (this.options.toolbars) {
+            this.add(this.options.toolbars);
+        }
+    },
+
+    /**
+     * APIMethod: update
+     * Updates the scroller enablement dependent on the total size of the
+     * toolbar(s).
+     */
+    update: function() {
+        if (this.options.scroll) {
+            if (['top', 'bottom'].contains(this.options.position)) {
+                var tbcSize = this.domObj.getContentBoxSize().width;
+
+                var s = 0;
+                //next check to see if we need the scrollers or not.
+                var children = this.wrapper.getChildren();
+                if (children.length > 0) {
+                    children.each(function(tb) {
+                        s += tb.getMarginBoxSize().width;
+                    },
+                    this);
+
+                    var scrollerSize = tbcSize;
+
+                    if (s === 0) {
+                        this.scrollLeft.setEnabled(false);
+                        this.scrollRight.setEnabled(false);
+                    } else {
+
+
+                        var leftMargin = this.wrapper.getStyle('margin-left').toInt();
+                        scrollerSize -= this.controls.getMarginBoxSize().width;
+
+
+                        if (leftMargin < 0) {
+                            //has been scrolled left so activate the right scroller
+                            this.scrollLeft.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollLeft.setEnabled(false);
+                        }
+
+                        if (s + leftMargin > scrollerSize) {
+                            //we need the right one
+                            this.scrollRight.setEnabled(true);
+                        } else {
+                            //we don't need it
+                            this.scrollRight.setEnabled(false);
+                        }
+                    }
+
+                } else {
+                    this.scrollRight.setEnabled(false);
+                    this.scrollLeft.setEnabled(false);
+                }
+                this.scroller.setStyle('width', scrollerSize);
+
+                this.findFirstVisible();
+                this.updating = false;
+            }
+        }
+    },
+    /**
+     * Method: findFirstVisible
+     * Finds the first visible button on the toolbar and saves a reference in 
+     * the scroller object
+     */
+    findFirstVisible: function() {
+        if ($defined(this.scroller.retrieve('buttonPointer'))) {
+            return;
+        };
+
+        var children = this.wrapper.getChildren();
+
+        if (children.length > 0) {
+            children.each(function(toolbar) {
+                var buttons = toolbar.getChildren();
+                if (buttons.length > 1) {
+                    buttons.each(function(button) {
+                        var pos = button.getCoordinates(this.scroller);
+                        if (pos.left >= 0 && !$defined(this.scroller.retrieve('buttonPointer'))) {
+                            //this is the first visible button
+                            this.scroller.store('buttonPointer', button);
+                        }
+                    },
+                    this);
+                }
+            },
+            this);
+        }
+    },
+
+    /**
+     * APIMethod: 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('update', this.update.bind(this));
+                thing.addEvent('show', this.scrollIntoView.bind(this));
+            }
+            if (this.wrapper) {
+                this.wrapper.adopt(thing.domObj);
+            } else {
+                this.domObj.adopt(thing.domObj);
+            }
+            this.domObj.addClass('jxBar' + this.options.position.capitalize());
+        },
+        this);
+        if (arguments.length > 0) {
+            this.fireEvent('add', this);
+        }
+        return this;
+    },
+
+    /**
+     * Method: scroll
+     * Does the work of scrolling the toolbar to a specific position.
+     *
+     * Parameters:
+     * direction - whether to scroll left or right
+     */
+    scroll: function(direction) {
+        if (this.updating) {
+            return
+        };
+        this.updating = true;
+
+        var currentButton = this.scroller.retrieve('buttonPointer');
+        if (direction === 'left') {
+            //need to tween the amount of the previous button
+            var previousButton = this.scroller.retrieve('previousPointer');
+            if (!previousButton) {
+                previousButton = this.getPreviousButton(currentButton);
+            }
+            if (previousButton) {
+                var w = previousButton.getMarginBoxSize().width;
+                var ml = this.wrapper.getStyle('margin-left').toInt();
+                ml += w;
+                if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                    //scroll it
+                    this.wrapper.get('tween', {
+                        property: 'margin-left',
+                        onComplete: this.afterTweenLeft.bind(this, previousButton)
+                    }).start(ml);
+                } else {
+                    //set it
+                    this.wrapper.setStyle('margin-left', ml);
+                    this.afterTweenLeft(previousButton);
+                }
+            } else {
+                this.update();
+            }
+        } else {
+            //must be right
+            var w = currentButton.getMarginBoxSize().width;
+
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= w;
+
+            //now, if Fx is defined tween the margin to the left to
+            //hide the current button
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+
+        }
+    },
+
+    /**
+     * Method: afterTweenRight
+     * Updates pointers to buttons after the toolbar scrolls right
+     *
+     * Parameters:
+     * currentButton - the button that was currently first before the scroll
+     */
+    afterTweenRight: function(currentButton) {
+        var np = this.getNextButton(currentButton);
+        if (!np) {
+            np = currentButton;
+        }
+        this.scroller.store('buttonPointer', np);
+        if (np !== currentButton) {
+            this.scroller.store('previousPointer', currentButton);
+        }
+        this.update();
+    },
+    /**
+     * Method: afterTweenLeft
+     * Updates pointers to buttons after the toolbar scrolls left
+     *
+     * Parameters:
+     * previousButton - the button that was to the left of the first visible
+     *      button.
+     */
+    afterTweenLeft: function(previousButton) {
+        this.scroller.store('buttonPointer', previousButton);
+        var pp = this.getPreviousButton(previousButton);
+        if ($defined(pp)) {
+            this.scroller.store('previousPointer', pp);
+        } else {
+            this.scroller.eliminate('previousPointer');
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 instanceof Jx.Widget) {
+            item.dispose();
+        } else {
+            document.id(item).dispose();
+        }
+        this.update();
+    },
+    /**
+     * APIMethod: 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 currentButton = this.scroller.retrieve('buttonPointer');
+
+        if (!$defined(currentButton)) return;
+
+        if ($defined(item.domObj)) {
+            item = item.domObj;
+            while (!item.hasClass('jxToolItem')) {
+                item = item.getParent();
+            }
+        }
+        var pos = item.getCoordinates(this.scroller);
+        var scrollerSize = this.scroller.getStyle('width').toInt();
+
+        if (pos.right > 0 && pos.right <= scrollerSize && pos.left > 0 && pos.left <= scrollerSize) {
+           //we are completely on screen 
+            return;
+        };
+
+        if (pos.right > scrollerSize) {
+            //it's right of the scroller
+            var diff = pos.right - scrollerSize;
+
+            //loop through toolbar items until we have enough width to
+            //make the item visible
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            var w = currentButton.getMarginBoxSize().width;
+            var np;
+            while (w < diff && $defined(currentButton)) {
+                np = this.getNextButton(currentButton);
+                if (np) {
+                    w += np.getMarginBoxSize().width;
+                } else {
+                    break;
+                }
+                currentButton = np;
+            }
+
+            ml -= w;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenRight.bind(this, currentButton)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenRight(currentButton);
+            }
+        } else {
+            //it's left of the scroller
+            var ml = this.wrapper.getStyle('margin-left').toInt();
+            ml -= pos.left;
+
+            if (typeof Fx != 'undefined' && typeof Fx.Tween != 'undefined') {
+                //scroll it
+                this.wrapper.get('tween', {
+                    property: 'margin-left',
+                    onComplete: this.afterTweenLeft.bind(this, item)
+                }).start(ml);
+            } else {
+                //set it
+                this.wrapper.setStyle('margin-left', ml);
+                this.afterTweenLeft(item);
+            }
+        }
+
+    },
+    /**
+     * Method: getPreviousButton
+     * Finds the button to the left of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getPreviousButton: function(currentButton) {
+        pp = currentButton.getPrevious();
+        if (!$defined(pp)) {
+            //check for a new toolbar
+            pp = currentButton.getParent().getPrevious();
+            if (pp) {
+                pp = pp.getLast();
+            }
+        }
+        return pp;
+    },
+    /**
+     * Method: getNextButton
+     * Finds the button to the right of the first visible button
+     *
+     * Parameters:
+     * currentButton - the first visible button
+     */
+    getNextButton: function(currentButton) {
+        np = currentButton.getNext();
+        if (!np) {
+            np = currentButton.getParent().getNext();
+            if (np) {
+                np = np.getFirst();
+            }
+        }
+        return np;
+    }
+
+});
+/*
+---
+
+name: Jx.Toolbar.Item
+
+description: A helper class to provide a container for something to go into a Jx.Toolbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+
+provides: [Jx.Toolbar.Item]
+
+...
+ */
+// $Id: toolbar.item.js 960 2010-06-06 22:23: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.Widget,
+    options: {
+        /* Option: active
+         * is this item active or not?  Default is true.
+         */
+        active: true,
+        template: '<li class="jxToolItem"></li>'
+    },
+    classes: new Hash({
+        domObj: 'jxToolItem'
+    }),
+
+    parameters: ['jxThing', 'options'],
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.Toolbar.Item.
+     */
+    render: function() {
+        this.parent();
+        var el = document.id(this.options.jxThing);
+        if (el) {
+            this.domObj.adopt(el);
+        }
+    }
+});/*
+---
+
+name: Jx.Panel
+
+description: A panel is a fundamental container object that has a content area and optional toolbars around the content area.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.Menu.Item
+ - Jx.Layout
+ - Jx.Toolbar.Container
+ - Jx.Toolbar.Item
+
+provides: [Jx.Panel]
+
+css:
+ - panel
+
+images:
+ - panel_controls.png
+ - panelbar.png
+
+...
+ */
+// $Id: panel.js 980 2010-09-09 14:02:45Z pagameba $
+/**
+ * Class: Jx.Panel
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * some user controls as determined by the options passed to the
+ * constructor.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * close - fired when the panel is closed
+ * collapse - fired when the panel is collapsed
+ * expand - fired when the panel is opened
+ * 
+ * MooTools.lang Keys:
+ * - panel.collapseTooltip
+ * - panel.collapseLabel
+ * - panel.expandlabel
+ * - panel.maximizeTooltip
+ * - panel.maximizeLabel
+ * - panel.restoreTooltip
+ * - panel.restoreLabel
+ * - panel.closeTooltip
+ * - panel.closeLabel
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel = new Class({
+    Family: 'Jx.Panel',
+    Extends: Jx.Widget,
+
+    toolbarContainers: {
+        top: null,
+        right: null,
+        bottom: null,
+        left: null
+    },
+
+     options: {
+        position: null,
+        collapsedClass: 'jxPanelMin',
+        collapseClass: 'jxPanelCollapse',
+        menuClass: 'jxPanelMenu',
+        maximizeClass: 'jxPanelMaximize',
+        closeClass: 'jxPanelClose',
+
+        /* Option: label
+         * String, the title of the Jx Panel
+         */
+        label: '&nbsp;',
+        /* Option: height
+         * integer, fixed height to give the panel - no fixed height by
+         * default.
+         */
+        height: null,
+        /* Option: collapse
+         * boolean, determine if the panel can be collapsed and expanded
+         * by the user.  This puts a control into the title bar for the user
+         * to control the state of the panel.
+         */
+        collapse: true,
+        /* Option: close
+         * boolean, determine if the panel can be closed (hidden) by the user.
+         * The application needs to provide a way to re-open the panel after
+         * it is closed.  The closeable property extends to dialogs created by
+         * floating panels.  This option puts a control in the title bar of
+         * the panel.
+         */
+        close: false,
+        /* Option: closed
+         * boolean, initial state of the panel (true to start the panel
+         *  closed), default is false
+         */
+        closed: false,
+        /* Option: hideTitle
+         * Boolean, hide the title bar if true.  False by default.
+         */
+        hideTitle: false,
+        /* Option: toolbars
+         * array of Jx.Toolbar objects to put in the panel.  The position
+         * of each toolbar is used to position the toolbar within the panel.
+         */
+        toolbars: [],
+        type: 'panel',
+        template: '<div class="jxPanel"><div class="jxPanelTitle"><img class="jxPanelIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxPanelLabel"></span><div class="jxPanelControls"></div></div><div class="jxPanelContentContainer"><div class="jxPanelContent"></div></div></div>',
+        controlButtonTemplate: '<a class="jxButtonContainer jxButton"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>'
+    },
+    classes: new Hash({
+        domObj: 'jxPanel',
+        title: 'jxPanelTitle',
+        domImg: 'jxPanelIcon',
+        domLabel: 'jxPanelLabel',
+        domControls: 'jxPanelControls',
+        contentContainer: 'jxPanelContentContainer',
+        content: 'jxPanelContent'
+    }),
+
+    /**
+     * APIMethod: render
+     * Initialize a new Jx.Panel instance
+     */
+    render : function(){
+        this.parent();
+
+        this.toolbars = this.options ? this.options.toolbars || [] : [];
+
+        this.options.position = ($defined(this.options.height) && !$defined(this.options.position)) ? 'relative' : 'absolute';
+
+        if (this.options.image && this.domImg) {
+            this.domImg.setStyle('backgroundImage', 'url('+this.options.image+')');
+        }
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        var tbDiv = new Element('div');
+        this.domControls.adopt(tbDiv);
+        this.toolbar = new Jx.Toolbar({parent:tbDiv, scroll: false});
+
+        var that = this;
+        if (this.options.menu) {
+            this.menu = new Jx.Menu({
+                image: Jx.aPixel.src
+            }, {
+              buttonTemplate: this.options.controlButtonTemplate
+            });
+            this.menu.domObj.addClass(this.options.menuClass);
+            this.menu.domObj.addClass('jxButtonContentLeft');
+            this.toolbar.add(this.menu);
+        }
+
+        //var b, item;
+        if (this.options.collapse) {
+            if (this.title) {
+              this.title.addEvent('dblclick', function() {
+                that.toggleCollapse();
+              });
+            }
+            this.colB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'collapseTooltip'},
+                onClick: function() {
+                    that.toggleCollapse();
+                }
+            });
+            this.colB.domObj.addClass(this.options.collapseClass);
+            this.addEvents({
+                collapse: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'expandTooltip'});
+                }.bind(this),
+                expand: function() {
+                    this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.colB);
+            if (this.menu) {
+                this.colM = new Jx.Menu.Item({
+                    label: this.options.collapseLabel,
+                    onClick: function() { that.toggleCollapse(); }
+                });
+                var item = this.colM
+                this.addEvents({
+                    collapse: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+                    }.bind(this),
+                    expand: function() {
+                        this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(item);
+            }
+        }
+
+        if (this.options.maximize) {
+            this.maxB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'maximizeTooltip'},
+                onClick: function() {
+                    that.maximize();
+                }
+            });
+            this.maxB.domObj.addClass(this.options.maximizeClass);
+            this.addEvents({
+                maximize: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'restoreTooltip'});
+                }.bind(this),
+                restore: function() {
+                    this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+                }.bind(this)
+            });
+            this.toolbar.add(this.maxB);
+            if (this.menu) {
+                this.maxM = new Jx.Menu.Item({
+                    label: this.options.maximizeLabel,
+                    onClick: function() { that.maximize(); }
+                });
+                
+                this.addEvents({
+                    maximize: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'maximizeLabel'});
+                    }.bind(this),
+                    restore: function() {
+                        this.maxM.setLabel({set:'Jx',key:'panel',value:'restoreLabel'});
+                    }.bind(this)
+                });
+                this.menu.add(this.maxM);
+            }
+        }
+
+        if (this.options.close) {
+            this.closeB = new Jx.Button({
+                template: this.options.controlButtonTemplate,
+                image: Jx.aPixel.src,
+                tooltip: {set:'Jx',key:'panel',value:'closeTooltip'},
+                onClick: function() {
+                    that.close();
+                }
+            });
+            this.closeB.domObj.addClass(this.options.closeClass);
+            this.toolbar.add(this.closeB);
+            if (this.menu) {
+                this.closeM = new Jx.Menu.Item({
+                    label: {set:'Jx',key:'panel',value:'closeLabel'},
+                    onClick: function() {
+                        that.close();
+                    }
+                });
+                this.menu.add(item);
+            }
+
+        }
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        var jxl = new Jx.Layout(this.domObj, $merge(this.options, {propagate:false}));
+        var layoutHandler = this.layoutContent.bind(this);
+        jxl.addEvent('sizeChange', layoutHandler);
+
+        if (this.options.hideTitle) {
+            this.title.dispose();
+        }
+
+        if (Jx.type(this.options.toolbars) == 'array') {
+            this.options.toolbars.each(function(tb){
+                var position = tb.options.position;
+                var tbc = this.toolbarContainers[position];
+                if (!tbc) {
+                    tbc = new Element('div');
+                    new Jx.Layout(tbc);
+                    this.contentContainer.adopt(tbc);
+                    this.toolbarContainers[position] = tbc;
+                }
+                tb.addTo(tbc);
+            }, this);
+        }
+
+        new Jx.Layout(this.contentContainer);
+        new Jx.Layout(this.content);
+
+        if(this.shouldLoadContent()) {
+          this.loadContent(this.content);
+        }
+
+        this.toggleCollapse(this.options.closed);
+
+        this.addEvent('addTo', function() {
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: layoutContent
+     * the sizeChange event of the <Jx.Layout> that manages the outer container
+     * is intercepted and passed through this method to handle resizing of the
+     * panel contents because we need to do some calculations if the panel
+     * is collapsed and if there are toolbars to put around the content area.
+     */
+    layoutContent: function() {
+        var titleHeight = 0;
+        var top = 0;
+        var bottom = 0;
+        var left = 0;
+        var right = 0;
+        var tbc;
+        var tb;
+        var position;
+        if (!this.options.hideTitle && this.title.parentNode == this.domObj) {
+            titleHeight = this.title.getMarginBoxSize().height;
+        }
+        var domSize = this.domObj.getContentBoxSize();
+        if (domSize.height > titleHeight) {
+            this.contentContainer.setStyle('display','block');
+            this.options.closed = false;
+            this.contentContainer.resize({
+                top: titleHeight,
+                height: null,
+                bottom: 0
+            });
+            ['left','right'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.width = 'auto';
+                }
+            }, this);
+            ['top','bottom'].each(function(position){
+                if (this.toolbarContainers[position]) {
+                    this.toolbarContainers[position].style.height = '';
+                }
+            }, this);
+            if (Jx.type(this.options.toolbars) == 'array') {
+                this.options.toolbars.each(function(tb){
+                    tb.update();
+                    position = tb.options.position;
+                    tbc = this.toolbarContainers[position];
+                    // IE 6 doesn't seem to want to measure the width of
+                    // things correctly
+                    if (Browser.Engine.trident4) {
+                        var oldParent = document.id(tbc.parentNode);
+                        tbc.style.visibility = 'hidden';
+                        document.id(document.body).adopt(tbc);
+                    }
+                    var size = tbc.getBorderBoxSize();
+                    // put it back into its real parent now we are done
+                    // measuring
+                    if (Browser.Engine.trident4) {
+                        oldParent.adopt(tbc);
+                        tbc.style.visibility = '';
+                    }
+                    switch(position) {
+                        case 'bottom':
+                            bottom = size.height;
+                            break;
+                        case 'left':
+                            left = size.width;
+                            break;
+                        case 'right':
+                            right = size.width;
+                            break;
+                        case 'top':
+                        default:
+                            top = size.height;
+                            break;
+                    }
+                },this);
+            }
+            tbc = this.toolbarContainers['top'];
+            if (tbc) {
+                tbc.resize({top: 0, left: left, right: right, bottom: null, height: top, width: null});
+            }
+            tbc = this.toolbarContainers['bottom'];
+            if (tbc) {
+                tbc.resize({top: null, left: left, right: right, bottom: 0, height: bottom, width: null});
+            }
+            tbc = this.toolbarContainers['left'];
+            if (tbc) {
+                tbc.resize({top: top, left: 0, right: null, bottom: bottom, height: null, width: left});
+            }
+            tbc = this.toolbarContainers['right'];
+            if (tbc) {
+                tbc.resize({top: top, left: null, right: 0, bottom: bottom, height: null, width: right});
+            }
+            this.content.resize({top: top, bottom: bottom, left: left, right: right});
+        } else {
+            this.contentContainer.setStyle('display','none');
+            this.options.closed = true;
+        }
+        this.fireEvent('sizeChange', this);
+    },
+
+    /**
+     * Method: setLabel
+     * Set the label in the title bar of this panel
+     *
+     * Parameters:
+     * s - {String} the new label
+     */
+    setLabel: function(s) {
+        this.domLabel.set('html',this.getText(s));
+    },
+    /**
+     * Method: getLabel
+     * Get the label of the title bar of this panel
+     *
+     * Returns:
+     * {String} the label
+     */
+    getLabel: function() {
+        return this.domLabel.get('html');
+    },
+    /**
+     * Method: finalize
+     * Clean up the panel
+     */
+    finalize: function() {
+        this.domObj = null;
+        this.deregisterIds();
+    },
+    /**
+     * Method: maximize
+     * Maximize this panel
+     */
+    maximize: function() {
+        if (this.manager) {
+            this.manager.maximizePanel(this);
+        }
+    },
+    /**
+     * Method: setContent
+     * set the content of this panel to some HTML
+     *
+     * Parameters:
+     * html - {String} the new HTML to go in the panel
+     */
+    setContent : function (html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+    },
+    /**
+     * Method: setContentURL
+     * Set the content of this panel to come from some URL.
+     *
+     * Parameters:
+     * url - {String} URL to some HTML content for this panel
+     */
+    setContentURL : function (url) {
+        this.bContentReady = false;
+        this.setBusy(true);
+        if (arguments[1]) {
+            this.onContentReady = arguments[1];
+        }
+        if (url.indexOf('?') == -1) {
+            url = url + '?';
+        }
+        var a = new Request({
+            url: url,
+            method: 'get',
+            evalScripts:true,
+            onSuccess:this.panelContentLoaded.bind(this),
+            requestHeaders: ['If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT']
+        }).send();
+    },
+    /**
+     * Method: panelContentLoaded
+     * When the content of the panel is loaded from a remote URL, this
+     * method is called when the ajax request returns.
+     *
+     * Parameters:
+     * html - {String} the html return from xhr.onSuccess
+     */
+    panelContentLoaded: function(html) {
+        this.content.innerHTML = html;
+        this.bContentReady = true;
+        this.setBusy(false);
+        if (this.onContentReady) {
+            window.setTimeout(this.onContentReady.bind(this),1);
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','none');
+                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;
+                }
+                this.domObj.resize({height: height});
+                this.fireEvent('collapse', this);
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+                this.contentContainer.setStyle('display','block');
+                this.domObj.resize({height: this.options.height});
+                this.fireEvent('expand', this);
+            }
+        }
+    },
+
+    /**
+     * Method: close
+     * Closes the panel (completely hiding it).
+     */
+    close: function() {
+        this.domObj.dispose();
+        this.fireEvent('close', this);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();	//TODO: change this class so that we can access these properties without too much voodoo...
+    	if($defined(this.closeB)) {
+    		this.closeB.setTooltip({set:'Jx',key:'panel',value:'closeTooltip'});
+    	}
+    	if ($defined(this.closeM)) {
+    		this.closeM.setLabel({set:'Jx',key:'panel',value:'closeLabel'});
+    	}
+    	if ($defined(this.maxB)) {
+    		this.maxB.setTooltip({set:'Jx',key:'panel',value:'maximizeTooltip'});
+    	}
+    	if ($defined(this.colB)) {
+    		this.colB.setTooltip({set:'Jx',key:'panel',value:'collapseTooltip'});
+    	}
+    	if ($defined(this.colM)) {
+	    	if (this.options.closed == true) {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'expandLabel'});
+	    	} else {
+	    		this.colM.setLabel({set:'Jx',key:'panel',value:'collapseLabel'});
+	    	}
+    	}
+      if (this.options.label && this.domLabel) {
+          this.setLabel(this.options.label);
+      }
+      // TODO: is this the right method to call?
+      // if toolbars left/right are used and localized, they may change their size..
+      this.layoutContent();
+    },
+
+    /**
+     * Method to be able to allow loadingOnDemand in subclasses but not here
+     */
+    shouldLoadContent: function() {
+      return true;
+    }
+});/*
+---
+
+name: Jx.Dialog
+
+description: A Jx.Panel that implements a floating dialog.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - more/Keyboard
+
+optional:
+ - More/Drag
+
+provides: [Jx.Dialog]
+
+css:
+ - dialog
+
+images:
+ - dialog_chrome.png
+ - dialog_resize.png
+
+...
+ */
+// $Id: dialog.js 1006 2011-01-01 22:43:42Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog
+ *
+ * Extends: <Jx.Panel>
+ *
+ * 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
+ * operating system dialog boxes, including:
+ *
+ * - dialogs may be modal (user must dismiss the dialog to continue) or
+ * non-modal
+ *
+ * - dialogs are movable (user can drag the title bar to move the dialog
+ * around)
+ *
+ * - dialogs may be a fixed size or allow user resizing.
+ *
+ * Jx.Dialog uses <Jx.ContentLoader> to load content into the content area
+ * of the dialog.  Refer to the <Jx.ContentLoader> documentation for details
+ * on content options.
+ *
+ * Example:
+ * (code)
+ * var dialog = new Jx.Dialog();
+ * (end)
+ *
+ * Events:
+ * open - triggered when the dialog is opened
+ * close - triggered when the dialog is closed
+ * change - triggered when the value of an input in the dialog is changed
+ * resize - triggered when the dialog is resized
+ *
+ * Extends:
+ * Jx.Dialog extends <Jx.Panel>, please go there for more details.
+ * 
+ * MooTools.lang Keys:
+ * - dialog.resizeToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog = new Class({
+    Family: 'Jx.Dialog',
+    Extends: Jx.Panel,
+
+    options: {
+        /* Option: modal
+         * (optional) {Boolean} controls whether the dialog will be modal
+         * or not.  The default is to create modal dialogs.
+         */
+        modal: true,
+        /** 
+         * Option: maskOptions
+         */
+        maskOptions: {
+          'class':'jxModalMask',
+          maskMargins: true,
+          useIframeShim: true,
+          iframeShimOptions: {
+            className: 'jxIframeShim'
+          }
+        },
+        eventMaskOptions: {
+          'class':'jxEventMask',
+          maskMargins: false,
+          useIframeShim: false,
+          destroyOnHide: true
+        },
+        /* just overrides default position of panel, don't document this */
+        position: 'absolute',
+        /* Option: width
+         * (optional) {Integer} the initial width in pixels of the dialog.
+         * The default value is 250 if not specified.
+         */
+        width: 250,
+        /* Option: height
+         * (optional) {Integer} the initial height in pixels of the
+         * dialog. The default value is 250 if not specified.
+         */
+        height: 250,
+        /* Option: horizontal
+         * (optional) {String} the horizontal rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        horizontal: 'center center',
+        /* Option: vertical
+         * (optional) {String} the vertical rule for positioning the
+         * dialog.  The default is 'center center' meaning the dialog will be
+         * centered on the page.  See {<Jx.AutoPosition>} for details.
+         */
+        vertical: 'center center',
+        /* Option: label
+         * (optional) {String} the title of the dialog box.
+         */
+        label: '',
+        /* Option: parent
+         * (optional) {HTMLElement} a reference to an HTML element that
+         * the dialog is to be contained by.  The default value is for the dialog
+         * to be contained by the body element.
+         */
+        //parent: null,
+        /* Option: resize
+         * (optional) {Boolean} determines whether the dialog is
+         * resizeable by the user or not.  Default is false.
+         */
+        resize: false,
+
+        /* Option: move
+         * (optional) {Boolean} determines whether the dialog is
+         * moveable by the user or not.  Default is true.
+         */
+        move: true,
+        /*
+         * Option: limit
+         * (optional) {Object} || false
+         * passed to the Drag instance of this dialog to limit the movement
+         * {Object} must have x&y coordinates with a range, like {x:[0,500],y:[0,500]}.
+         * Set an id or a reference of a DOM Element (ie 'document', 'myContainerWithId', 
+         * $('myContainer'), $('domID').getParent()) to use these dimensions
+         * as boundaries. Default is false.
+         */
+        limit : false,
+        /* Option: close
+         * (optional) {Boolean} determines whether the dialog is
+         * closeable by the user or not.  Default is true.
+         */
+        close: true,
+        /**
+         * Option: useKeyboard
+         * (optional) {Boolean} determines whether the Dialog listens to keyboard events globally
+         * Default is false
+         */
+        useKeyboard : false,
+        /**
+         * Option: keys
+         * (optional) {Object} refers with the syntax for MooTools Keyboard Class
+         * to functions. Set key to false to disable it manually 
+         */
+        keys: {
+          'esc' : 'close'
+        },
+        /**
+         * Option: keyboardMethods
+         *
+         * can be used to overwrite existing keyboard methods that are used inside
+         * this.options.keys - also possible to add new ones.
+         * Functions are bound to the dialog when using 'this'
+         *
+         * example:
+         *  keys : {
+         *    'alt+enter' : 'maximizeDialog'
+         *  },
+         *  keyboardMethods: {
+         *    'maximizeDialog' : function(ev){
+         *      ev.preventDefault();
+         *      this.maximize();
+         *    }
+         *  }
+         */
+        keyboardMethods : {},
+        collapsedClass: 'jxDialogMin',
+        collapseClass: 'jxDialogCollapse',
+        menuClass: 'jxDialogMenu',
+        maximizeClass: 'jxDialogMaximize',
+        closeClass: 'jxDialogClose',
+        type: 'dialog',
+        template: '<div class="jxDialog"><div class="jxDialogTitle"><img class="jxDialogIcon" src="'+Jx.aPixel.src+'" alt="" title=""/><span class="jxDialogLabel"></span><div class="jxDialogControls"></div></div><div class="jxDialogContentContainer"><div class="jxDialogContent"></div></div></div>'
+    },
+    classes: new Hash({
+        domObj: 'jxDialog',
+        title: 'jxDialogTitle',
+        domImg: 'jxDialogIcon',
+        domLabel: 'jxDialogLabel',
+        domControls: 'jxDialogControls',
+        contentContainer: 'jxDialogContentContainer',
+        content: 'jxDialogContent'
+    }),
+    /**
+     * MooTools Keyboard class for Events (mostly used in Dialog.Confirm, Prompt or Message)
+     * But also optional here with esc to close
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * renders Jx.Dialog
+     */
+    render: function() {
+        this.isOpening = false;
+        this.firstShow = true;
+
+        this.options = $merge(
+            {parent:document.body}, // these are defaults that can be overridden
+            this.options,
+            {position: 'absolute'} // these override anything passed to the options
+        );
+
+        /* initialize the panel overriding the type and position */
+        this.parent();
+        this.openOnLoaded = this.open.bind(this);
+        this.options.parent = document.id(this.options.parent);
+
+        this.domObj.setStyle('display','none');
+        this.options.parent.adopt(this.domObj);
+
+        /* the dialog is moveable by its title bar */
+        if (this.options.move && typeof Drag != 'undefined') {
+            this.title.addClass('jxDialogMoveable');
+
+            this.options.limit = this.setDragLimit(this.options.limit);
+            // local reference to use Drag instance variables inside onDrag()
+            var self = this;
+            // COMMENT: any reason why the drag instance isn't referenced to the dialog?
+            new Drag(this.domObj, {
+                handle: this.title,
+                limit: this.options.limit,
+                onBeforeStart: (function(){
+                    this.stack();
+                }).bind(this),
+                onStart: function() {
+                    if (!self.options.modal && self.options.parent.mask) {
+                      self.options.parent.mask(self.options.eventMaskOptions);
+                    }
+                    self.contentContainer.setStyle('visibility','hidden');
+                    self.chrome.addClass('jxChromeDrag');
+                    if(self.options.limit) {
+                      var coords = self.options.limitOrig.getCoordinates();
+                      for(var i in coords) {
+                        window.console ? console.log(i, coords[i]) : false;
+                      }
+                      this.options.limit = self.setDragLimit(self.options.limitOrig);
+                    }
+                }, // COMMENT: removed bind(this) for setting the limit to the drag instance
+                onDrag: function() {
+                  if(this.options.limit) {
+                    // find out if the right border of the dragged element is out of range
+                    if(this.value.now.x+self.options.width >= this.options.limit.x[1]) {
+                      this.value.now.x = this.options.limit.x[1] - self.options.width;
+                      this.element.setStyle('left',this.value.now.x);
+                    }
+                    // find out if the bottom border of the dragged element is out of range
+                    if(this.value.now.y+self.options.height >= this.options.limit.y[1]) {
+                      this.value.now.y = this.options.limit.y[1] - self.options.height;
+                      this.element.setStyle('top',this.value.now.y);
+                    }
+                  }
+                },
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    this.contentContainer.setStyle('visibility','');
+                    var left = Math.max(this.chromeOffsets.left, parseInt(this.domObj.style.left,10));
+                    var top = Math.max(this.chromeOffsets.top, parseInt(this.domObj.style.top,10));
+                    this.options.horizontal = left + ' left';
+                    this.options.vertical = top + ' top';
+                    this.position(this.domObj, this.options.parent, this.options);
+                    this.options.left = parseInt(this.domObj.style.left,10);
+                    this.options.top = parseInt(this.domObj.style.top,10);
+                    if (!this.options.closed) {
+                        this.domObj.resize(this.options);
+                    }
+                }).bind(this)
+            });
+        }
+
+        /* the dialog is resizeable */
+        if (this.options.resize && typeof Drag != 'undefined') {
+            this.resizeHandle = new Element('div', {
+                'class':'jxDialogResize',
+                title: this.getText({set:'Jx',key:'panel',value:'resizeTooltip'}),
+                styles: {
+                    'display':this.options.closed?'none':'block'
+                }
+            });
+            this.domObj.appendChild(this.resizeHandle);
+
+            this.resizeHandleSize = this.resizeHandle.getSize();
+            this.resizeHandle.setStyles({
+                bottom: this.resizeHandleSize.height,
+                right: this.resizeHandleSize.width
+            });
+            this.domObj.makeResizable({
+                handle:this.resizeHandle,
+                onStart: (function() {
+                    if (!this.options.modal && this.options.parent.mask) {
+                      this.options.parent.mask(this.options.eventMaskOptions);
+                    }
+                    this.contentContainer.setStyle('visibility','hidden');
+                    this.chrome.addClass('jxChromeDrag');
+                }).bind(this),
+                onDrag: (function() {
+                    this.resizeChrome(this.domObj);
+                }).bind(this),
+                onComplete: (function() {
+                    if (!this.options.modal && this.options.parent.unmask) {
+                      this.options.parent.unmask();
+                    }
+                    this.chrome.removeClass('jxChromeDrag');
+                    var size = this.domObj.getMarginBoxSize();
+                    this.options.width = size.width;
+                    this.options.height = size.height;
+                    this.layoutContent();
+                    this.domObj.resize(this.options);
+                    this.contentContainer.setStyle('visibility','');
+                    this.fireEvent('resize');
+                    this.resizeChrome(this.domObj);
+
+                }).bind(this)
+            });
+        }
+        /* this adjusts the zIndex of the dialogs when activated */
+        this.domObj.addEvent('mousedown', (function(){
+            this.stack();
+        }).bind(this));
+
+        // initialize keyboard class
+        this.initializeKeyboard();
+    },
+
+    /**
+     * Method: resize
+     * resize the dialog.  This can be called when the dialog is closed
+     * or open.
+     *
+     * Parameters:
+     * width - the new width
+     * height - the new height
+     * autoPosition - boolean, false by default, if resizing an open dialog
+     * setting this to true will reposition it according to its position
+     * rules.
+     */
+    resize: function(width, height, autoPosition) {
+        this.options.width = width;
+        this.options.height = height;
+        if (this.domObj.getStyle('display') != 'none') {
+            this.layoutContent();
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            if (autoPosition) {
+                this.position(this.domObj, this.options.parent, this.options);
+            }
+        } else {
+            this.firstShow = false;
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * overload panel's sizeChanged method
+     */
+    sizeChanged: function() {
+        if (!this.options.closed) {
+            this.layoutContent();
+        }
+    },
+
+    /**
+     * Method: toggleCollapse
+     * sets or toggles the collapsed state of the panel.  If a
+     * new state is passed, it is used, otherwise the current
+     * state is toggled.
+     *
+     * Parameters:
+     * state - optional, if passed then the state is used,
+     * otherwise the state is toggled.
+     */
+    toggleCollapse: function(state) {
+        if ($defined(state)) {
+            this.options.closed = state;
+        } else {
+            this.options.closed = !this.options.closed;
+        }
+        if (this.options.closed) {
+            if (!this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.addClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','none');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','none');
+            }
+        } else {
+            if (this.domObj.hasClass(this.options.collapsedClass)) {
+                this.domObj.removeClass(this.options.collapsedClass);
+            }
+            this.contentContainer.setStyle('display','block');
+            if (this.resizeHandle) {
+                this.resizeHandle.setStyle('display','block');
+            }
+        }
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+            this.fireEvent('collapse');
+        } else {
+            this.domObj.resize(this.options);
+            this.fireEvent('expand');
+        }
+        this.showChrome(this.domObj);
+    },
+    
+    /**
+     * Method: maximize
+     * Called when the maximize button of a dialog is clicked. It will maximize
+     * the dialog to match the size of its parent.
+     */
+    maximize: function () {
+        
+        if (!this.maximized) {
+            //get size of parent
+            var p = this.options.parent;
+            var size;
+            
+            if (p === document.body) {
+                size = Jx.getPageDimensions();
+            } else {
+                size = p.getBorderBoxSize();
+            }
+            this.previousSettings = {
+                width: this.options.width,
+                height: this.options.height,
+                horizontal: this.options.horizontal,
+                vertical: this.options.vertical,
+                left: this.options.left,
+                right: this.options.right,
+                top: this.options.top,
+                bottom: this.options.bottom
+            };
+            this.options.width = size.width;
+            this.options.height = size.height;
+            this.options.vertical = '0 top';
+            this.options.horizontal = '0 left';
+            this.options.right = 0;
+            this.options.left = 0;
+            this.options.top = 0;
+            this.options.bottom = 0;
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = true;
+            this.domObj.addClass('jxDialogMaximized');
+            this.fireEvent('maximize');
+        } else {
+            this.options = $merge(this.options, this.previousSettings);
+            this.domObj.resize(this.options);
+            this.fireEvent('resize');
+            this.resizeChrome(this.domObj);
+            this.maximized = false;
+            if (this.domObj.hasClass('jxDialogMaximized')) {
+                this.domObj.removeClass('jxDialogMaximized');
+            }
+            this.fireEvent('restore');
+        }
+    },
+
+    /**
+     * Method: show
+     * show the dialog, external code should use the <Jx.Dialog::open> method
+     * to make the dialog visible.
+     */
+    show : function( ) {
+        /* prepare the dialog for display */
+        this.domObj.setStyles({
+            'display': 'block',
+            'visibility': 'hidden'
+        });
+        this.toolbar.update();
+        
+        /* do the modal thing */
+        if (this.options.modal && this.options.parent.mask) {
+          var opts = $merge(this.options.maskOptions || {}, {
+            style: {
+              'z-index': Jx.getNumber(this.domObj.getStyle('z-index')) - 1
+            }
+          });
+          this.options.parent.mask(opts);
+          Jx.Stack.stack(this.options.parent.get('mask').element);
+        }
+        /* stack the dialog */
+        this.stack();
+
+        if (this.options.closed) {
+            var m = this.domObj.measure(function(){
+                return this.getSizes(['margin'],['top','bottom']).margin;
+            });
+            var size = this.title.getMarginBoxSize();
+            this.domObj.resize({height: m.top + size.height + m.bottom});
+        } else {
+            this.domObj.resize(this.options);
+        }
+        
+        if (this.firstShow) {
+            this.contentContainer.resize({forceResize: true});
+            this.layoutContent();
+            this.firstShow = false;
+            /* if the chrome got built before the first dialog show, it might
+             * not have been properly created and we should clear it so it
+             * does get built properly
+             */
+            if (this.chrome) {
+                this.chrome.dispose();
+                this.chrome = null;
+            }
+        }
+        /* update or create the chrome */
+        this.showChrome(this.domObj);
+        /* put it in the right place using auto-positioning */
+        this.position(this.domObj, this.options.parent, this.options);
+        this.domObj.setStyle('visibility', 'visible');
+    },
+    /**
+     * Method: hide
+     * hide the dialog, external code should use the <Jx.Dialog::close>
+     * method to hide the dialog.
+     */
+    hide : function() {
+        this.domObj.setStyle('display','none');
+        this.unstack();
+        if (this.options.modal && this.options.parent.unmask) {
+          Jx.Stack.unstack(this.options.parent.get('mask').element);
+          this.options.parent.unmask();
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.deactivate();
+        }
+    },
+    /**
+     * Method: openURL
+     * open the dialog and load content from the provided url.  If you don't
+     * provide a URL then the dialog opens normally.
+     *
+     * Parameters:
+     * url - <String> the url to load when opening.
+     */
+    openURL: function(url) {
+        if (url) {
+            this.options.contentURL = url;
+            this.options.content = null;  //force Url loading
+            this.setBusy();
+            this.loadContent(this.content);
+            this.addEvent('contentLoaded', this.openOnLoaded);
+        } else {
+            this.open();
+        }
+    },
+
+    /**
+     * Method: open
+     * open the dialog.  This may be delayed depending on the
+     * asynchronous loading of dialog content.  The onOpen
+     * callback function is called when the dialog actually
+     * opens
+     */
+    open: function() {
+        if (!this.isOpening) {
+            this.isOpening = true;
+        }
+        // COMMENT: this works only for onDemand -> NOT for cacheContent = false..
+        // for this loading an URL everytime, use this.openURL(url) 
+        if(!this.contentIsLoaded && this.options.loadOnDemand) {
+          this.loadContent(this.content);
+        }
+        if (this.contentIsLoaded) {
+            this.removeEvent('contentLoaded', this.openOnLoaded);
+            this.show();
+            this.fireEvent('open', this);
+            this.isOpening = false;
+        } else {
+            this.addEvent('contentLoaded', this.openOnLoaded);
+        }
+        if(this.options.useKeyboard && this.keyboard != null) {
+          this.keyboard.activate();
+        }
+    },
+    /**
+     * Method: close
+     * close the dialog and trigger the onClose callback function
+     * if necessary
+     */
+    close: function() {
+        this.isOpening = false;
+        this.hide();
+        this.fireEvent('close');
+    },
+
+    cleanup: function() { },
+    
+    /**
+     * APIMethod: isOpen
+     * returns true if the dialog is currently open, false otherwise
+     */
+    isOpen: function () {
+        //check to see if we're visible
+        return !((this.domObj.getStyle('display') === 'none') || (this.domObj.getStyle('visibility') === 'hidden'));
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.maxM)) {
+			if (this.maximize) {
+				this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'restoreLabel'}));
+	    	} else {
+	    		this.maxM.setLabel(this.getText({set:'Jx',key:'panel',value:'maximizeLabel'}));
+	    	}
+    	}
+    	if ($defined(this.resizeHandle)) {
+    		this.resizeHandle.set('title', this.getText({set:'Jx',key:'dialog',value:'resizeTooltip'}));
+    	}
+      this.toggleCollapse(false);
+    },
+
+    initializeKeyboard: function() {
+      if(this.options.useKeyboard) {
+        var self = this;
+        this.keyboardEvents = {};
+        this.keyboardMethods = {
+          close : function(ev) {ev.preventDefault();self.close()}
+        }
+        this.keyboard = new Keyboard({
+          events: this.getKeyboardEvents()
+        });
+      }
+    },
+
+    /**
+     * Method: getKeyboardMethods
+     * used by this and all child classes to have methods listen to keyboard events,
+     * returned object will be parsed to the events object of a MooTools Keyboard instance
+     *
+     * @return Object
+     */
+    getKeyboardEvents : function() {
+      var self = this;
+      for(var i in this.options.keys) {
+        // only add a reference once, otherwise keyboard events will be fired twice in subclasses
+        if(!$defined(this.keyboardEvents[i])) {
+          if($defined(this.keyboardMethods[this.options.keys[i]])) {
+            this.keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+          }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+            this.keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+          }else if(Jx.type(this.options.keys[i]) == 'function') {
+            this.keyboardEvents[i] = this.options.keys[i].bind(self);
+          }else{
+            // allow disabling of special keys by setting them to false or null with having a warning
+            if(this.options.keyboardMethods[this.options.keys[i]] != false) {
+              $defined(console) ? console.warn("keyboard method %o not defined for %o", this.options.keys[i], this) : false;
+            }
+          }
+        }
+      }
+      return this.keyboardEvents;
+    },
+
+    /**
+     * Method: setDragLimit
+     * calculates the drag-dimensions of an given element to drag
+     *
+     * Parameters:
+     * - reference {Object} (optional) the element|elementId|object to set the limits
+     */
+    setDragLimit : function(reference) {
+      if($defined(reference)) this.options.limit = reference;
+      
+      // check drag limit if it is an container or string for an element and use dimensions
+      var limitType = this.options.limit != null ? Jx.type(this.options.limit) : false;
+      if(this.options.limit && limitType != 'object') {
+        var coords = false;
+        switch(limitType) {
+          case 'string':
+            if(document.id(this.options.limit)) {
+              coords = document.id(this.options.limit).getCoordinates();
+            }
+            break;
+          case 'element':
+          case 'document':
+          case 'window':
+            coords = this.options.limit.getCoordinates();
+            break;
+        }
+        if(coords) {
+          this.options.limitOrig = this.options.limit;
+          this.options.limit = {
+            x : [coords.left, coords.right],
+            y : [coords.top, coords.bottom]
+          }
+        }else{
+          this.options.limit = false;
+        }
+      }
+      return this.options.limit;
+    },
+
+    /**
+     * gets called by parent class Jx.Panel and decides whether to load content or not
+     */
+    shouldLoadContent: function() {
+      return !this.options.loadOnDemand;
+    }
+});
+
+/*
+---
+
+name: Jx.Splitter
+
+description: A Jx.Splitter creates two or more containers within a parent container and provides user control over the size of the containers.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Layout
+
+optional:
+ - More/Drag
+
+provides: [Jx.Splitter]
+
+css:
+ - splitter
+
+...
+ */
+// $Id: splitter.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Splitter
+ *
+ * Extends: <Jx.Object>
+ *
+ * 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.
+ *
+ * A horizontal split creates containers that divide the space horizontally
+ * with vertical bars between the containers.  A vertical split divides
+ * the space vertically and creates horizontal bars between the containers.
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - splitter.barToolTip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+
+Jx.Splitter = new Class({
+    Family: 'Jx.Splitter',
+    Extends: Jx.Object,
+    /**
+     * Property: domObj
+     * {HTMLElement} the element being split
+     */
+    domObj: null,
+    /**
+     * Property: elements
+     * {Array} an array of elements that are displayed in each of the split
+     * areas
+     */
+    elements: null,
+    /**
+     * Property: bars
+     * {Array} an array of the bars between each of the elements used to
+     * resize the split areas.
+     */
+    bars: null,
+    /**
+     * Property: firstUpdate
+     * {Boolean} track the first resize event so that unexposed Jx things
+     * can be forced to calculate their size the first time they are exposed.
+     */
+    firstUpdate: true,
+    options: {
+        /* Option: useChildren
+         * {Boolean} if set to true, then the children of the
+         * element to be split are used as the elements.  The default value is
+         * false.  If this is set, then the elements and splitInto options
+         * are ignored.
+         */
+        useChildren: false,
+        /* Option: splitInto
+         * {Integer} the number of elements to split the domObj into.
+         * If not set, then the length of the elements option is used, or 2 if
+         * elements is not specified.  If splitInto is specified and elements
+         * is specified, then splitInto is used.  If there are more elements than
+         * splitInto specifies, then the extras are ignored.  If there are less
+         * elements than splitInto specifies, then extras are created.
+         */
+        splitInto: 2,
+        /* Option: elements
+         * {Array} an array of elements to put into the split areas.
+         * If splitInto is not set, then it is calculated from the length of
+         * this array.
+         */
+        elements: null,
+        /* Option: containerOptions
+         * {Array} an array of objects that provide options
+         *  for the <Jx.Layout> constraints on each element.
+         */
+        containerOptions: [],
+        /* Option: barOptions
+         * {Array} an array of object that provide options for the bars,
+         * this array should be one less than the number of elements in the
+         * splitter.  The barOptions objects can contain a snap property indicating
+         * that a default snap object should be created in the bar and the value
+         * of 'before' or 'after' indicates which element it snaps open/shut.
+         */
+        barOptions: [],
+        /* Option: layout
+         * {String} either 'horizontal' or 'vertical', indicating the
+         * direction in which the domObj is to be split.
+         */
+        layout: 'horizontal',
+        /* Option: snaps
+         * {Array} an array of objects which can be used to snap
+         * elements open or closed.
+         */
+        snaps: [],
+        /* Option: onStart
+         * an optional function to call when a bar starts dragging
+         */
+        onStart: null,
+        /* Option: onFinish
+         * an optional function to call when a bar finishes dragging
+         */
+        onFinish: null
+    },
+
+    parameters: ['domObj','options'],
+
+    /**
+     * APIMethod: init
+     * Create a new instance of Jx.Splitter
+     */
+    init: function() {
+        this.domObj = document.id(this.options.domObj);
+        this.domObj.addClass('jxSplitContainer');
+        var jxLayout = this.domObj.retrieve('jxLayout');
+        if (jxLayout) {
+            jxLayout.addEvent('sizeChange', this.sizeChanged.bind(this));
+        }
+
+        this.elements = [];
+        this.bars = [];
+        var i;
+        var nSplits = 2;
+        if (this.options.useChildren) {
+            this.elements = this.domObj.getChildren();
+            nSplits = this.elements.length;
+        } else {
+            nSplits = this.options.elements ?
+                            this.options.elements.length :
+                            this.options.splitInto;
+            for (i=0; i<nSplits; i++) {
+                var el;
+                if (this.options.elements && this.options.elements[i]) {
+                    if (this.options.elements[i].domObj) {
+                        el = this.options.elements[i].domObj;
+                    } else {
+                        el = document.id(this.options.elements[i]);
+                    }
+                    if (!el) {
+                        el = this.prepareElement();
+                        el.id = this.options.elements[i];
+                    }
+                } else {
+                    el = this.prepareElement();
+                }
+                this.elements[i] = el;
+                this.domObj.adopt(this.elements[i]);
+            }
+        }
+        this.elements.each(function(el) { el.addClass('jxSplitArea'); });
+        for (i=0; i<nSplits; i++) {
+            var jxl = this.elements[i].retrieve('jxLayout');
+            if (!jxl) {
+                new Jx.Layout(this.elements[i], this.options.containerOptions[i]);
+            } else {
+                if (this.options.containerOptions[i]) {
+                    jxl.resize($merge(this.options.containerOptions[i],
+                        {position:'absolute'}));
+                } else {
+                    jxl.resize({position: 'absolute'});
+                }
+            }
+        }
+
+        for (i=1; i<nSplits; i++) {
+            var bar;
+            if (this.options.prepareBar) {
+                bar = this.options.prepareBar(i-1);
+            } else {
+                bar = this.prepareBar();
+            }
+            bar.store('splitterObj', this);
+            bar.store('leftSide',this.elements[i-1]);
+            bar.store('rightSide', this.elements[i]);
+            this.elements[i-1].store('rightBar', bar);
+            this.elements[i].store('leftBar', bar);
+            this.domObj.adopt(bar);
+            this.bars[i-1] = bar;
+        }
+
+        //making dragging dependent on mootools Drag class
+        if ($defined(Drag)) {
+            this.establishConstraints();
+        }
+
+        for (i=0; i<this.options.barOptions.length; i++) {
+            if (!this.bars[i]) {
+                continue;
+            }
+            var opt = this.options.barOptions[i];
+            if (opt && opt.snap && (opt.snap == 'before' || opt.snap == 'after')) {
+                var element;
+                if (opt.snap == 'before') {
+                    element = this.bars[i].retrieve('leftSide');
+                } else if (opt.snap == 'after') {
+                    element = this.bars[i].retrieve('rightSide');
+                }
+                var snap;
+                var snapEvents;
+                if (opt.snapElement) {
+                    snap = opt.snapElement;
+                    snapEvents = opt.snapEvents || ['click', 'dblclick'];
+                } else {
+                    snap = this.bars[i];
+                    snapEvents = opt.snapEvents || ['dblclick'];
+                }
+                if (!snap.parentNode) {
+                    this.bars[i].adopt(snap);
+                }
+                new Jx.Splitter.Snap(snap, element, this, snapEvents);
+            }
+        }
+
+        for (i=0; i<this.options.snaps.length; i++) {
+            if (this.options.snaps[i]) {
+                new Jx.Splitter.Snap(this.options.snaps[i], this.elements[i], this);
+            }
+        }
+
+        this.sizeChanged();
+    },
+    /**
+     * Method: prepareElement
+     * Prepare a new, empty element to go into a split area.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that goes into a split area.
+     */
+    prepareElement: function(){
+        var o = new Element('div', {styles:{position:'absolute'}});
+        return o;
+    },
+
+    /**
+     * Method: prepareBar
+     * Prepare a new, empty bar to go into between split areas.
+     *
+     * Returns:
+     * {HTMLElement} an HTMLElement that becomes a bar.
+     */
+    prepareBar: function() {
+        var o = new Element('div', {
+            'class': 'jxSplitBar'+this.options.layout.capitalize(),
+            'title': this.getText({set:'Jx',key:'splitter',value:'barToolTip'})
+        });
+        return o;
+    },
+
+    /**
+     * Method: establishConstraints
+     * Setup the initial set of constraints that set the behaviour of the
+     * bars between the elements in the split area.
+     */
+    establishConstraints: function() {
+        var modifiers = {x:null,y:null};
+        var fn;
+        if (this.options.layout == 'horizontal') {
+            modifiers.x = "left";
+            fn = this.dragHorizontal;
+        } else {
+            modifiers.y = "top";
+            fn = this.dragVertical;
+        }
+        if (typeof Drag != 'undefined') {
+            this.bars.each(function(bar){
+                var mask;
+                new Drag(bar, {
+                    //limit: limit,
+                    modifiers: modifiers,
+                    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');
+                        if (obj.retrieve('splitterObj') != this) {
+                            return;
+                        }
+                        fn.apply(this,[obj]);
+                        this.fireEvent('complete',[obj]);
+                        this.fireEvent('finish',[obj]);
+                    }).bind(this),
+                    onBeforeStart: (function(obj) {
+                        this.fireEvent('beforeStart',[obj]);
+                        mask = new Element('div',{'class':'jxSplitterMask'}).inject(obj, 'after');
+                    }).bind(this),
+                    onStart: (function(obj, event) {
+                        this.fireEvent('start',[obj, event]);
+                    }).bind(this)
+                });
+            }, this);
+        }
+    },
+
+    /**
+     * Method: dragHorizontal
+     * In a horizontally split container, handle a bar being dragged left or
+     * right by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragHorizontal: function(obj) {
+        var leftEdge = parseInt(obj.style.left,10);
+        var leftSide = obj.retrieve('leftSide');
+        var rightSide = obj.retrieve('rightSide');
+        var leftJxl = leftSide.retrieve('jxLayout');
+        var rightJxl = rightSide.retrieve('jxLayout');
+
+        var paddingLeft = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        /* process right side first */
+        var rsLeft, rsWidth, rsRight;
+
+        var size = obj.retrieve('size');
+        if (!size) {
+            size = obj.getBorderBoxSize();
+            obj.store('size',size);
+        }
+        rsLeft = leftEdge + size.width - paddingLeft;
+
+        var parentSize = this.domObj.getContentBoxSize();
+
+        if (rightJxl.options.width != null) {
+            rsWidth = rightJxl.options.width + rightJxl.options.left - rsLeft;
+            rsRight = parentSize.width - rsLeft - rsWidth;
+        } else {
+            rsWidth = parentSize.width - rightJxl.options.right - rsLeft;
+            rsRight = rightJxl.options.right;
+        }
+
+        /* enforce constraints on right side */
+        if (rsWidth < 0) {
+            rsWidth = 0;
+        }
+
+        if (rsWidth < rightJxl.options.minWidth) {
+            rsWidth = rightJxl.options.minWidth;
+        }
+        if (rightJxl.options.maxWidth >= 0 && rsWidth > rightJxl.options.maxWidth) {
+            rsWidth = rightJxl.options.maxWidth;
+        }
+
+        rsLeft = parentSize.width - rsRight - rsWidth;
+        leftEdge = rsLeft - size.width;
+
+        /* process left side */
+        var lsLeft, lsWidth;
+        lsLeft = leftJxl.options.left;
+        lsWidth = leftEdge - lsLeft;
+
+        /* enforce constraints on left */
+        if (lsWidth < 0) {
+            lsWidth = 0;
+        }
+        if (lsWidth < leftJxl.options.minWidth) {
+            lsWidth = leftJxl.options.minWidth;
+        }
+        if (leftJxl.options.maxWidth >= 0 &&
+            lsWidth > leftJxl.options.maxWidth) {
+            lsWidth = leftJxl.options.maxWidth;
+        }
+
+        /* update the leftEdge to accomodate constraints */
+        if (lsLeft + lsWidth != leftEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            leftEdge = lsLeft + lsWidth;
+            var delta = leftEdge + size.width - rsLeft;
+            rsLeft += delta;
+            rsWidth -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.left = paddingLeft + leftEdge + 'px';
+
+        /* update leftSide positions */
+        if (leftJxl.options.width == null) {
+            parentSize = this.domObj.getContentBoxSize();
+            leftSide.resize({right: parentSize.width - lsLeft-lsWidth});
+        } else {
+            leftSide.resize({width: lsWidth});
+        }
+
+        /* update rightSide position */
+        if (rightJxl.options.width == null) {
+            rightSide.resize({left:rsLeft});
+        } else {
+            rightSide.resize({left: rsLeft, width: rsWidth});
+        }
+    },
+
+    /**
+     * Method: dragVertical
+     * In a vertically split container, handle a bar being dragged up or
+     * down by resizing the elements on either side of the bar.
+     *
+     * Parameters:
+     * obj - {HTMLElement} the bar that was dragged
+     */
+    dragVertical: function(obj) {
+        /* top edge of the bar */
+        var topEdge = parseInt(obj.style.top,10);
+
+        /* the containers on either side of the bar */
+        var topSide = obj.retrieve('leftSide');
+        var bottomSide = obj.retrieve('rightSide');
+        var topJxl = topSide.retrieve('jxLayout');
+        var bottomJxl = bottomSide.retrieve('jxLayout');
+
+        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) {
+            size = obj.getBorderBoxSize();
+            obj.store('size', size);
+        }
+        var parentSize = this.domObj.getContentBoxSize();
+
+        /* process top side first */
+        var bsTop, bsHeight, bsBottom;
+
+        /* top edge of bottom side is the top edge of bar plus the height of the bar */
+        bsTop = topEdge + size.height - paddingTop;
+
+        if (bottomJxl.options.height != null) {
+            /* bottom side height is fixed */
+            bsHeight = bottomJxl.options.height + bottomJxl.options.top - bsTop;
+            bsBottom = parentSize.height - bsTop - bsHeight;
+        } else {
+            /* bottom side height is not fixed. */
+            bsHeight = parentSize.height - bottomJxl.options.bottom - bsTop;
+            bsBottom = bottomJxl.options.bottom;
+        }
+
+        /* enforce constraints on bottom side */
+        if (bsHeight < 0) {
+            bsHeight = 0;
+        }
+
+        if (bsHeight < bottomJxl.options.minHeight) {
+            bsHeight = bottomJxl.options.minHeight;
+        }
+
+        if (bottomJxl.options.maxHeight >= 0 && bsHeight > bottomJxl.options.maxHeight) {
+            bsHeight = bottomJxl.options.maxHeight;
+        }
+
+        /* recalculate the top of the bottom side in case it changed
+           due to a constraint.  The bar may have moved also.
+         */
+        bsTop = parentSize.height - bsBottom - bsHeight;
+        topEdge = bsTop - size.height;
+
+        /* process left side */
+        var tsTop, tsHeight;
+        tsTop = topJxl.options.top;
+        tsHeight = topEdge - tsTop;
+
+        /* enforce constraints on left */
+        if (tsHeight < 0) {
+            tsHeight = 0;
+        }
+        if (tsHeight < topJxl.options.minHeight) {
+            tsHeight = topJxl.options.minHeight;
+        }
+        if (topJxl.options.maxHeight >= 0 &&
+            tsHeight > topJxl.options.maxHeight) {
+            tsHeight = topJxl.options.maxHeight;
+        }
+
+        /* update the topEdge to accomodate constraints */
+        if (tsTop + tsHeight != topEdge) {
+            /* need to update right side, ignoring constraints because left side
+               constraints take precedence (arbitrary decision)
+             */
+            topEdge = tsTop + tsHeight;
+            var delta = topEdge + size.height - bsTop;
+            bsTop += delta;
+            bsHeight -= delta;
+        }
+
+        /* put bar in its final location based on constraints */
+        obj.style.top = paddingTop + topEdge + 'px';
+
+        /* update topSide positions */
+        if (topJxl.options.height == null) {
+            topSide.resize({bottom: parentSize.height - tsTop-tsHeight});
+        } else {
+            topSide.resize({height: tsHeight});
+        }
+
+        /* update bottomSide position */
+        if (bottomJxl.options.height == null) {
+            bottomSide.resize({top:bsTop});
+        } else {
+            bottomSide.resize({top: bsTop, height: bsHeight});
+        }
+    },
+
+    /**
+     * Method: sizeChanged
+     * handle the size of the container being changed.
+     */
+    sizeChanged: function() {
+        if (this.options.layout == 'horizontal') {
+            this.horizontalResize();
+        } else {
+            this.verticalResize();
+        }
+    },
+
+    /**
+     * Method: horizontalResize
+     * Resize a horizontally layed-out container
+     */
+    horizontalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().width;
+        var overallWidth = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.width == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size',size);
+            }
+            availableSpace -= size.width;
+        }
+
+        var nVariable = 0, w = 0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.width != null) {
+                availableSpace -= parseInt(jxo.width,10);
+            } else {
+                w = 0;
+                if (jxo.right != 0 ||
+                    jxo.left != 0) {
+                    w = e.getBorderBoxSize().width;
+                }
+
+                availableSpace -= w;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.width;
+            jxo.width = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var leftPadding = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['left']);
+            return m.padding.left;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.width != null) {
+                 jxl.resize({left: currentPosition});
+                 currentPosition += jxo.width;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 if (jxo.right != 0 || jxo.left != 0) {
+                     w = e.getBorderBoxSize().width + a;
+                 } else {
+                     w = a;
+                 }
+
+                 if (w < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + w/nVariable;
+                     }
+                     w = 0;
+                 }
+                 if (w < jxo.minWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.minWidth)/nVariable;
+                     }
+                     w = jxo.minWidth;
+                 }
+                 if (jxo.maxWidth >= 0 && w > jxo.maxWidth) {
+                     if (nVariable > 0) {
+                         amount = amount + (w - jxo.maxWidth)/nVariable;
+                     }
+                     w = e.options.maxWidth;
+                 }
+
+                 var r = overallWidth - currentPosition - w;
+                 jxl.resize({left: currentPosition, right: r});
+                 currentPosition += w;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.setStyle('left', leftPadding + currentPosition);
+                 currentPosition += rightBar.retrieve('size').width;
+             }
+         }
+    },
+
+    /**
+     * Method: verticalResize
+     * Resize a vertically layed out container.
+     */
+    verticalResize: function() {
+        var availableSpace = this.domObj.getContentBoxSize().height;
+        var overallHeight = availableSpace;
+        var i,e,jxo;
+        for (i=0; i<this.bars.length; i++) {
+            var bar = this.bars[i];
+            var size = bar.retrieve('size');
+            if (!size || size.height == 0) {
+                size = bar.getBorderBoxSize();
+                bar.store('size', size);
+            }
+            availableSpace -= size.height;
+        }
+
+        var nVariable = 0, h=0;
+        for (i=0; i<this.elements.length; i++) {
+            e = this.elements[i];
+            jxo = e.retrieve('jxLayout').options;
+            if (jxo.height != null) {
+                availableSpace -= parseInt(jxo.height,10);
+            } else {
+                if (jxo.bottom != 0 || jxo.top != 0) {
+                    h = e.getBorderBoxSize().height;
+                }
+
+                availableSpace -= h;
+                nVariable++;
+            }
+        }
+
+        if (nVariable == 0) { /* all fixed */
+            /* stick all available space in the last one */
+            availableSpace += jxo.height;
+            jxo.height = null;
+            nVariable = 1;
+        }
+
+        var amount = parseInt(availableSpace / nVariable,10);
+        /* account for rounding errors */
+        var remainder = availableSpace % nVariable;
+
+        var paddingTop = this.domObj.measure(function(){
+            var m = this.getSizes(['padding'], ['top']);
+            return m.padding.top;
+        });
+
+        var currentPosition = 0;
+
+        for (i=0; i<this.elements.length; i++) {
+             e = this.elements[i];
+             var jxl = e.retrieve('jxLayout');
+             jxo = jxl.options;
+             if (jxo.height != null) {
+                 jxl.resize({top: currentPosition});
+                 currentPosition += jxo.height;
+             } else {
+                 var a = amount;
+                 if (nVariable == 1) {
+                     a += remainder;
+                 }
+                 nVariable--;
+
+                 h = 0;
+                 if (jxo.bottom != 0 || jxo.top != 0) {
+                     h = e.getBorderBoxSize().height + a;
+                 } else {
+                     h = a;
+                 }
+
+                 if (h < 0) {
+                     if (nVariable > 0) {
+                         amount = amount + h/nVariable;
+                     }
+                     h = 0;
+                 }
+                 if (h < jxo.minHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.minHeight)/nVariable;
+                     }
+                     h = jxo.minHeight;
+                 }
+                 if (jxo.maxHeight >= 0 && h > jxo.maxHeight) {
+                     if (nVariable > 0) {
+                         amount = amount + (h - jxo.maxHeight)/nVariable;
+                     }
+                     h = jxo.maxHeight;
+                 }
+
+                 var r = overallHeight - currentPosition - h;
+                 jxl.resize({top: currentPosition, bottom: r});
+                 currentPosition += h;
+             }
+             var rightBar = e.retrieve('rightBar');
+             if (rightBar) {
+                 rightBar.style.top = paddingTop + currentPosition + 'px';
+                 currentPosition += rightBar.retrieve('size').height;
+             }
+         }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	this.bars.each(function(bar){
+    		document.id(bar).set('title', this.getText({set:'Jx',key:'splitter',value:'barToolTip'}));
+    	},this);	
+    }
+});/*
+---
+
+name: Jx.PanelSet
+
+description: A panel set manages a set of panels within a DOM element.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Splitter
+ - Jx.Panel
+
+provides: [Jx.PanelSet]
+
+...
+ */
+// $Id: panelset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.PanelSet
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A panel set manages a set of panels within a DOM element.  The PanelSet
+ * fills its container by resizing the panels in the set to fill the width and
+ * then distributing the height of the container across all the panels. 
+ * Panels can be resized by dragging their respective title bars to make them
+ * taller or shorter.  The maximize button on the panel title will cause all
+ * other panels to be closed and the target panel to be expanded to fill the
+ * remaining space.  In this respect, PanelSet works like a traditional
+ * Accordion control.
+ *
+ * When creating panels for use within a panel set, it is important to use the
+ * proper options.  You must override the collapse option and set it to false
+ * and add a maximize option set to true.  You must also not include options
+ * for menu and close.
+ *
+ * Example:
+ * (code)
+ * var p1 = new Jx.Panel({collapse: false, maximize: true, content: 'c1'});
+ * var p2 = new Jx.Panel({collapse: false, maximize: true, content: 'c2'});
+ * var p3 = new Jx.Panel({collapse: false, maximize: true, content: 'c3'});
+ * var panelSet = new Jx.PanelSet('panels', [p1,p2,p3]);
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - panelset.barTooltip
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.PanelSet = new Class({
+    Family: 'Jx.PanelSet',
+    Extends: Jx.Widget,
+
+    options: {
+        /* Option: parent
+         * the object to add the panel set to
+         */
+        parent: null,
+        /* Option: panels
+         * an array of <Jx.Panel> objects that will be managed by the set.
+         */
+        panels: []
+    },
+
+    /**
+     * Property: panels
+     * {Array} the panels being managed by the set
+     */
+    panels: null,
+    /**
+     * Property: height
+     * {Integer} the height of the container, cached for speed
+     */
+    height: null,
+    /**
+     * Property: firstLayout
+     * {Boolean} true until the panel set has first been resized
+     */
+    firstLayout: true,
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.PanelSet.
+     */
+    render: function() {
+        if (this.options.panels) {
+            this.panels = this.options.panels;
+            this.options.panels = null;
+        }
+        this.domObj = new Element('div');
+        new Jx.Layout(this.domObj);
+
+        //make a fake panel so we get the right number of splitters
+        var d = new Element('div', {styles:{position:'absolute'}});
+        new Jx.Layout(d, {minHeight:0,maxHeight:0,height:0});
+        var elements = [d];
+        this.panels.each(function(panel){
+            elements.push(panel.domObj);
+            panel.options.hideTitle = true;
+            panel.contentContainer.resize({top:0});
+            panel.toggleCollapse = this.maximizePanel.bind(this,panel);
+            panel.domObj.store('Jx.Panel', panel);
+            panel.manager = this;
+        }, this);
+
+        this.splitter = new Jx.Splitter(this.domObj, {
+            splitInto: this.panels.length+1,
+            layout: 'vertical',
+            elements: elements,
+            prepareBar: (function(i) {
+                var bar = new Element('div', {
+                    'class': 'jxPanelBar',
+                    'title': this.getText({set:'Jx',key:'panelset',value:'barToolTip'})
+                });
+
+                var panel = this.panels[i];
+                panel.title.setStyle('visibility', 'hidden');
+                document.id(document.body).adopt(panel.title);
+                var size = panel.title.getBorderBoxSize();
+                bar.adopt(panel.title);
+                panel.title.setStyle('visibility','');
+
+                bar.setStyle('height', size.height);
+                bar.store('size', size);
+
+                return bar;
+            }).bind(this)
+        });
+        this.addEvent('addTo', function() {
+            document.id(this.domObj.parentNode).setStyle('overflow', 'hidden');
+            this.domObj.resize();
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+
+    /**
+     * Method: maximizePanel
+     * Maximize a panel, taking up all available space (taking into
+     * consideration any minimum or maximum values)
+     */
+    maximizePanel: function(panel) {
+        var domHeight = this.domObj.getContentBoxSize().height;
+        var space = domHeight;
+        var panelSize = panel.domObj.retrieve('jxLayout').options.maxHeight;
+        var panelIndex,i,p,thePanel,o,panelHeight;
+        /* calculate how much space might be left after setting all the panels to
+         * their minimum height (except the one we are resizing of course)
+         */
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            space -= p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                space -= o.minHeight;
+            } else {
+                panelIndex = i;
+            }
+        }
+
+        // calculate how much space the panel will take and what will be left over
+        if (panelSize == -1 || panelSize >= space) {
+            panelSize = space;
+            space = 0;
+        } else {
+            space = space - panelSize;
+        }
+        var top = 0;
+        for (i=1; i<this.splitter.elements.length; i++) {
+            p = this.splitter.elements[i];
+            top += p.retrieve('leftBar').getBorderBoxSize().height;
+            if (p !== panel.domObj) {
+                thePanel = p.retrieve('Jx.Panel');
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // this panel can stay open at its current height
+                        space -= panelHeight;
+                        p.resize({top: top, height: panelHeight});
+                        top += panelHeight;
+                    } else {
+                        // this panel needs to shrink some
+                        if (space > o.minHeight) {
+                            // it can use all the space
+                            p.resize({top: top, height: space});
+                            top += space;
+                            space = 0;
+                        } else {
+                            p.resize({top: top, height: o.minHeight});
+                            top += o.minHeight;
+                        }
+                    }
+                } else {
+                    // no more space, just shrink away
+                    p.resize({top:top, height: o.minHeight});
+                    top += o.minHeight;
+                }
+                p.retrieve('rightBar').style.top = top + 'px';
+            } else {
+                break;
+            }
+        }
+
+        /* now work from the bottom up */
+        var bottom = domHeight;
+        for (i=this.splitter.elements.length - 1; i > 0; i--) {
+            p = this.splitter.elements[i];
+            if (p !== panel.domObj) {
+                o = p.retrieve('jxLayout').options;
+                panelHeight = $chk(o.height) ? o.height : p.getBorderBoxSize().height;
+                if (space > 0) {
+                    if (space >= panelHeight) {
+                        // panel can stay open
+                        bottom -= panelHeight;
+                        space -= panelHeight;
+                        p.resize({top: bottom, height: panelHeight});
+                    } else {
+                        if (space > o.minHeight) {
+                            bottom -= space;
+                            p.resize({top: bottom, height: space});
+                            space = 0;
+                        } else {
+                            bottom -= o.minHeight;
+                            p.resize({top: bottom, height: o.minHeight});
+                        }
+                    }
+                } else {
+                    bottom -= o.minHeight;
+                    p.resize({top: bottom, height: o.minHeight, bottom: null});
+                }
+                bottom -= p.retrieve('leftBar').getBorderBoxSize().height;
+                p.retrieve('leftBar').style.top = bottom + 'px';
+
+            } else {
+                break;
+            }
+        }
+        panel.domObj.resize({top: top, height:panelSize, bottom: null});
+        this.fireEvent('panelMaximize',panel);
+    },
+    
+    createText: function (lang) {
+      this.parent();
+      //barTooltip is handled by the splitter's createText() function
+    }
+});/*
+---
+
+name: Jx.Dialog.Message
+
+description: A subclass of jx.Dialog for displaying messages w/a single OK button.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+
+provides: [Jx.Dialog.Message]
+
+css:
+ - message
+
+...
+ */
+// $Id: message.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog.Message
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Message is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user. It only presents an OK button.
+ * 
+ * MooTools.lang Keys:
+ * - message.okButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Message = new Class({
+    Family: 'Jx.Dialog.Message',
+    Extends: Jx.Dialog,
+    Binds: ['onOk'],
+    options: {
+        /**
+         * Option: message
+         * The message to display to the user
+         */
+        message: '',
+        /**
+         * Option: width
+         * default width of message dialogs is 300px
+         */
+        width: 300,
+        /**
+         * Option: height
+         * default height of message dialogs is 150px
+         */
+        height: 150,
+        /**
+         * Option: close
+         * by default, message dialogs are closable
+         */
+        close: true,
+        /**
+         * Option: resize
+         * by default, message dialogs are resizable
+         */
+        resize: true,
+        /**
+         * Option: collapse
+         * by default, message dialogs are not collapsible
+         */
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * Method: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'message',value:'okButton'}),
+            onClick: this.onOk
+        });
+        this.buttons.add(this.ok);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.message);
+        if (type === 'string' || type == 'object' || type == 'element') {
+            this.question = new Element('div', {
+                'class': 'jxMessage'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.message));
+              break;
+              case 'element':
+                this.options.message.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxMessage');
+        }
+        this.options.content = this.question;
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok = function(ev) { ev.preventDefault(); self.close(); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onOk
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onOk: function () {
+        this.close();
+    },
+    
+    /**
+     * APIMethod: setMessage
+     * set the message of the dialog, useful for responding to language
+     * changes on the fly.
+     *
+     * Parameters
+     * message - {String} the new message
+     */
+    setMessage: function(message) {
+      this.options.message = message;
+      if ($defined(this.question)) {
+        this.question.set('html',this.getText(message));
+      }
+    },
+    
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.ok)) {
+        this.ok.setLabel({set:'Jx',key:'message',value:'okButton'});
+      }
+      if(Jx.type(this.options.message) === 'object') {
+        this.question.set('html', this.getText(this.options.message))
+      }
+    }
+});
+/*
+---
+
+name: Jx.Dialog.Confirm
+
+description: A subclass of Jx.dialog for asking a yes/no type question of the user.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+
+provides: [Jx.Dialog.Confirm]
+
+css:
+ - confirm
+
+...
+ */
+// $Id: confirm.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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.
+ * 
+ * MooTools.lang Keys:
+ * - confirm.affirmitiveLabel
+ * - confirm.negativeLabel
+ * 
+ * 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: '',
+        /**
+         * Jx.Dialog option defaults
+         */
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        },
+        width: 300,
+        height: 150,
+        close: false,
+        resize: true,
+        collapse: false
+    },
+    /**
+     * Reference to MooTools keyboards Class for handling keypress events like Enter or ESC
+     */
+    keyboard : null,
+    /**
+     * APIMethod: render
+     * creates the dialog
+     */
+    render: function () {
+        //create content to be added
+        //turn scrolling off as confirm only has 2 buttons.
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll: false});
+
+        // COMMENT: returning boolean would be more what people expect instead of a localized label of a button?
+        this.ok = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'affirmativeLabel'}),
+            onClick: this.onClick.bind(this, true)
+        }),
+        this.cancel = new Jx.Button({
+            label: this.getText({set:'Jx',key:'confirm',value:'negativeLabel'}),
+            onClick: this.onClick.bind(this, false)
+        })
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+        var type = Jx.type(this.options.question);
+        if (type === 'string' || type === 'object' || type == 'element'){
+            this.question = new Element('div', {
+                'class': 'jxConfirmQuestion'
+            });
+            switch(type) {
+              case 'string':
+              case 'object':
+                this.question.set('html', this.getText(this.options.question));
+              break;
+              case 'element':
+                this.options.question.inject(this.question);
+                break;
+            }
+        } else {
+            this.question = this.options.question;
+            document.id(this.question).addClass('jxConfirmQuestion');
+        }
+        this.options.content = this.question;
+
+        // add default key functions
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        // add new ones
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * 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]);
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'confirm',value:'affirmativeLabel'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'confirm',value:'negativeLabel'});
+    	}
+      if(Jx.type(this.options.question) === 'object') {
+        this.question.set('html', this.getText(this.options.question))
+      }
+    }
+
+});/*
+---
+
+name: Jx.Tooltip
+
+description: 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.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Tooltip]
+
+css:
+ - tooltip
+
+...
+ */
+// $Id: tooltip.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends : Jx.Widget,
+    Binds: ['enter', 'leave', 'move'],
+    options : {
+        /**
+         * Option: offsets
+         * An object with x and y components for where to put the tip related
+         * to the mouse cursor.
+         */
+        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'],
+
+    /**
+     * Method: 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' || Jx.type(this.options.tip) == 'object') {
+            this.domObj.set('html', this.getText(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);
+        this.options.target.addEvent('mouseleave', this.leave);
+        this.options.target.addEvent('mousemove', this.move);
+    },
+
+    /**
+     * Method: enter
+     * Method run when the cursor passes over an element with a tip
+     *
+     * Parameters:
+     * event - the event object
+     */
+    enter : function (event) {
+        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
+     */
+    leave : function (event) {
+        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);
+    },
+    /**
+     * APIMethod: detach
+     * Called to manually remove a tooltip.
+     */
+    detach : function () {
+        this.target.eliminate('Tip');
+        this.destroy();
+    }
+});
+/*
+---
+
+name: Jx.Fieldset
+
+description: Used to create fieldsets in Forms
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Fieldset]
+
+...
+ */
+// $Id: fieldset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Fieldset',
+    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
+    },
+
+    classes: new Hash({
+        domObj: 'jxFieldset',
+        legend: 'jxFieldsetLegend'
+    }),
+
+    /**
+     * 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;
+        }
+
+        //FIELDSET
+        if (this.domObj) {
+            if ($defined(this.options.id)) {
+                this.domObj.set('id', this.options.id);
+            }
+            if ($defined(this.options.fieldsetClass)) {
+                this.domObj.addClass(this.options.fieldsetClass);
+            }
+        }
+
+        if (this.legend) {
+            if ($defined(this.options.legend)) {
+                this.legend.set('html', this.getText(this.options.legend));
+                if ($defined(this.options.legendClass)) {
+                    this.legend.addClass(this.options.legendClass);
+                }
+            } else {
+                this.legend.destroy();
+            }
+        }
+    },
+    /**
+     * APIMethod: add
+     * Adds fields to this fieldset
+     *
+     * Parameters:
+     * pass as many fields to this method as you like. They should 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 ($defined(field.jxFamily) && !$defined(field.form) && $defined(this.form)) {
+                field.form = this.form;
+                this.form.addField(field);
+            }
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: addTo
+     *
+     */
+    addTo: function(what) {
+        if (what instanceof Jx.Form) {
+            this.form = what;
+        } else if (what instanceof Jx.Fieldset) {
+            this.form = what.form;
+        }
+        return this.parent(what);
+    }
+    
+});
+/*
+---
+
+name: Jx.Form
+
+description: Represents a HTML Form
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - More/String.QueryString
+ - More/Form.Validator
+
+provides: [Jx.Form]
+
+css:
+ - form
+
+...
+ */
+// $Id: form.js 1010 2011-01-04 00:09:39Z jonlb at comcast.net $
+/**
+ * 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
+ * through the use of a plugin (Jx.Plugin.Form.Validator).
+ *
+ * Jx.Form has the ability to submit itself via normal HTTP submit as well as
+ * via AJAX. To submit normally you simply call the submit() function. To submit by
+ * AJAX, call ajaxSubmit().  If the form contains Jx.Field.File instances it will
+ * either submit all of the files individually and then the data, or it will submit
+ * data with the last File instance it finds. This behavior is dependant on the
+ * uploadFilesFirst option (which defaults to false).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Form = new Class({
+    Family: 'Jx.Form',
+    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: formClass
+         */
+        formClass: null,
+        /**
+         * Option: name
+         * the name property for the form
+         */
+        name: '',
+        /**
+         * Option: acceptCharset
+         * the character encoding to be used. Defaults to utf-8.
+         */
+        acceptCharset: 'utf-8',
+        /**
+         * Option: uploadFilesFirst
+         * Whether to upload all of the files in the form before
+         * uploading the rest of the form. If set to false the form will
+         * upload the data with the last file that it finds,
+         */
+        uploadFilesFirst: false,
+
+        template: '<form class="jxForm"></form>'
+    },
+    
+    /**
+     * Property: defaultAction
+     * the default field to activate if the user hits the enter key in this
+     * form.  Set by specifying default: true as an option to a field.  Will
+     * only work if the default is a Jx button field or an input of a type
+     * that is a button
+     */
+    defaultAction: null,
+
+    /**
+     * Property: fields
+     * An array of all of the single fields (not contained in a fieldset) for
+     * this form
+     */
+    fields : null,
+    /**
+     * Property: pluginNamespace
+     * required variable for plugins
+     */
+    pluginNamespace: 'Form',
+
+    classes: $H({
+        domObj: 'jxForm'
+    }),
+    
+    init: function() {
+      this.parent();
+      this.fields = new Hash();
+      this.data = {};
+    },
+    /**
+     * 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 () {
+        this.parent();
+        //create the form first
+        this.domObj.set({
+            'method' : this.options.method,
+            'action' : this.options.action,
+            'name' : this.options.name,
+            'accept-charset': this.options.acceptCharset,
+            events: {
+                keypress: function(e) {
+                    if (e.key == 'enter' && 
+                        e.target.tagName != "TEXTAREA" && 
+                        this.defaultAction &&
+                        this.defaultAction.click) {
+                        document.id(this.defaultAction).focus();
+                        this.defaultAction.click();
+                        e.stop();
+                    }
+                }.bind(this)
+            }
+        });
+
+        if (this.options.fileUpload) {
+            this.domObj.set('enctype', 'multipart/form-data');
+        }
+        
+        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);
+        if (field.options.defaultAction) {
+            this.defaultAction = 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) && 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) {
+        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);
+            } else if (field instanceof Jx.Fieldset && !$defined(field.form)) {
+                field.form = this;
+            }
+            
+            this.domObj.grab(field);
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: reset
+     * Resets all fields back to their original value
+     */
+    reset : function () {
+        this.fields.each(function (field, name) {
+            field.reset();
+        }, this);
+        this.fireEvent('reset',this);
+    },
+    /**
+     * APIMethod: getFieldsByName
+     * Allows retrieving a field from a form by the name of the field (NOT the
+     * ID).
+     *
+     * Parameters:
+     * name - {string} the name of the field to find
+     */
+    getFieldsByName: function (name) {
+        var fields = [];
+        this.fields.each(function(val, id){
+            if (val.name === name) {
+                fields.push(val);
+            }
+        },this);
+        return fields;
+    },
+    /**
+     * APIMethod: getField
+     * Returns a Jx.Field object by its ID.
+     *
+     * Parameters:
+     * id - {string} the id of the field to find.
+     */
+    getField: function (id) {
+        if (this.fields.has(id)) {
+            return this.fields.get(id);
+        } 
+        return null;
+    },
+    /**
+     * APIMethod: setBusy
+     * Sets the busy state of the Form and all of it's fields.
+     *
+     * Parameters:
+     * state - {boolean} indicated whether the form is busy or not.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.parent(state);
+      this.fields.each(function(field) {
+        field.setBusy(state, true);
+      });
+    },
+
+    submit: function() {
+        //are there any files in this form?
+        var opts = this.options;
+        if (opts.fileUpload) {
+            //grab all of the files and pull them into the main domObj
+            var files = this.findFiles();
+            files.each(function(file){
+                var inputs = file.getFileInputs();
+                if (inputs.length > 1) {
+                    //we need to make these an array...
+                    inputs.each(function(input){
+                        input.set('name',input.get('name') + '[]');
+                    },this);
+                }
+                file.destroy();
+                this.domObj.adopt(inputs);
+            },this);
+        }
+        this.domObj.submit();
+    },
+
+    ajaxSubmit: function() {
+        var opts = this.options;
+        if (opts.fileUpload) {
+            var files = this.findFiles();
+            this.files = files.length;
+            this.completed = 0;
+            files.each(function(file, index){
+                file.addEvent('onFileUploadComplete',this.fileUploadComplete.bind(this));
+                if (index==(this.files - 1) && !opts.uploadFilesFirst) {
+                    file.upload(this);
+                } else {
+                    file.upload();
+                }
+            },this);
+        } else {
+            this.submitForm();
+        }
+    },
+
+    submitForm: function() {
+        //otherwise if no file field(s) present, just get the values and
+        //submit to the action via the method
+        var data = this.getValues();
+        var req = new Request.JSON({
+            url: this.action,
+            method: this.method,
+            data: data,
+            urlEncoded: true,
+            onSuccess: function(responseJSON, responseText) {
+                this.fileUploadComplete(responseJSON, true);
+            }.bind(this)
+        });
+        req.send();
+    },
+
+    findFiles: function() {
+        var files = [];
+        this.fields.each(function(field){
+            if (field instanceof Jx.Field.File) {
+                files.push(field);
+            }
+        },this);
+        return files;
+    },
+
+    fileUploadComplete: function(data){
+        this.completed++;
+        $each(data,function(value,key){
+            this.data[key] = value;
+        },this);
+        if (this.completed == this.files && this.options.uploadFilesFirst) {
+            this.submitForm();
+        } else {
+            this.fireEvent('formSubmitComplete',[this.data]);
+        }
+    }
+
+});
+/*
+---
+
+name: Jx.Field
+
+description: Base class for all inputs
+
+license: MIT-style license.
+
+requires:
+ - Jx.Fieldset
+ - Jx.Form
+
+provides: [Jx.Field]
+
+
+...
+ */
+// $Id: field.js 969 2010-08-20 12:14:54Z pagameba $
+/**
+ * Class: Jx.Field
+ *
+ * Extends: <Jx.Widget>
+ *
+ * This class is the base class for all form fields.
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ * 
+ * MooTools.lang Keys:
+ * - field.requiredText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field = new Class({
+    Family: 'Jx.Field',
+    Extends : Jx.Widget,
+    pluginNamespace: 'Field',
+    Binds: ['changeText'],
+    
+    options : {
+        /**
+         * Option: id
+         * The ID assigned to the container of the Jx.Field element, this is
+         * not the id of the input element (which is internally computed to be
+         * unique)
+         */
+        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 provided
+         * that the plugin Jx.Plugin.Field.Validator has been added to this
+         * field.
+         */
+        required : false,
+        /**
+         * 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,
+        /**
+         * Option: defaultAction
+         * {Boolean} defaults to false, if true and this field is a button
+         * of some kind (Jx.Button, a button or an input of type submit) then
+         * if the user hits the enter key on any field in the form except a
+         * textarea, this field will be activated as if clicked
+         */
+        defaultAction: 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
+     * A computed, unique id attached to the input element 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 : new Hash({
+        domObj: 'jxInputContainer',
+        label: 'jxInputLabel',
+        tag: 'jxInputTag'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render : function () {
+        this.classes.set('field', 'jxInput'+this.type);
+        var name = $defined(this.options.name) ? this.options.name : '';
+        this.options.template = this.options.template.substitute({name:name});
+        this.parent();
+
+        this.id = this.generateId();
+        this.name = this.options.name;
+
+        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';
+            }
+        }
+
+
+        // FIELD
+        if (this.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 events
+            this.field.addEvents({
+              'focus': this.onFocus.bind(this),
+              'blur': this.onBlur.bind(this),
+              'change': this.onChange.bind(this)
+            });
+
+            this.field.store('field', this);
+
+            // add click event to label to set the focus to the field
+            // COMMENT: tried it without a function using addEvent('click', this.field.focus.bind(this)) but crashed in IE
+            if(this.label) {
+              this.label.addEvent('click', function() {
+                this.field.focus();
+              }.bind(this));
+            }
+        }
+        // LABEL
+        if (this.label) {
+            if ($defined(this.options.labelClass)) {
+                this.label.addClass(this.options.labelClass);
+            }
+            if ($defined(this.options.label)) {
+                this.label.set('html', this.getText(this.options.label)
+                        + this.options.labelSeparator);
+            }
+
+            this.label.set('for', this.id);
+
+            if (this.options.required) {
+                this.requiredText = new Element('em', {
+                    'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+                    'class' : 'required'
+                });
+                this.requiredText.inject(this.label);
+            }
+
+        }
+
+        // TAG
+        if (this.tag) {
+            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) {
+        if (!this.options.readonly) {
+            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.setValue(this.options.value);
+        this.fireEvent('reset', this);
+    },
+    /**
+     * APIMethod: disable
+     * Disabled the field
+     */
+    disable : function () {
+        this.options.disabled = true;
+        this.field.set("disabled", "disabled");
+        this.field.addClass('jxFieldDisabled');
+    },
+    /**
+     * APIMethod: enable
+     * Enables the field
+     */
+    enable : function () {
+        this.options.disabled = false;
+        this.field.erase("disabled");
+        this.field.removeClass('jxFieldDisabled');
+    },
+    
+    /**
+     * APIMethod: addTo
+     * Overrides default Jx.Widget AddTo() so that we can call .add() if
+     * adding to a Jx.Form or Jx.Fieldset object.
+     *
+     * Parameters:
+     * what - the element or object to add this field to.
+     * where - where in the object to place it. Not valid if adding to Jx.Form
+     *      or Jx.Fieldset.
+     */
+    addTo: function(what, where) {
+        if (what instanceof Jx.Fieldset || what instanceof Jx.Form) {
+            what.add(this);
+        } else {
+            this.parent(what, where);
+        }
+        return this;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        this.parent();
+        if ($defined(this.options.label) && this.label) {
+          this.label.set('html', this.getText(this.options.label) + this.options.labelSeparator);
+        }
+        if(this.options.required) {
+          this.requiredText = new Element('em', {
+              'html' : this.getText({set:'Jx',key:'field',value:'requiredText'}),
+              'class' : 'required'
+          });
+          this.requiredText.inject(this.label);
+        }
+        if ($defined(this.requiredText)) {
+          this.requiredText.set('html',this.getText({set:'Jx',key:'field',value:'requiredText'}));
+        }
+    }, 
+    
+    onFocus: function() {
+      this.fireEvent('focus', this);
+    },
+    
+    onBlur: function () {
+      this.fireEvent('blur',this);
+    },
+    
+    onChange: function () {
+      this.fireEvent('change', this);
+    },
+    
+    setBusy: function(state, withoutMask) {
+      if (!withoutMask) {
+        this.parent(state);
+      }
+      this.field.set('readonly', state || this.options.readonly);
+    }
+
+});
+/*
+---
+
+name: Jx.Field.Text
+
+description: Represents a text input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+optional:
+ - More/OverText
+
+provides: [Jx.Field.Text]
+
+...
+ */
+// $Id: text.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><input class="jxInputText" type="text" name="{name}"/><span class="jxInputTag"></span></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();
+        }
+
+    }
+
+});/*
+---
+
+name: Jx.Dialog.Prompt
+
+description: A subclass of Jx.dialog for prompting the user for text input.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Dialog
+ - Jx.Button
+ - Jx.Toolbar.Item
+ - Jx.Field.Text
+
+provides: [Jx.Dialog.Prompt]
+
+...
+ */
+// $Id: prompt.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Dialog.Prompt
+ *
+ * Extends: <Jx.Dialog>
+ *
+ * Jx.Dialog.Prompt is an extension of Jx.Dialog that allows the developer
+ * to display a message to the user and ask for a text response. 
+ * 
+ * MooTools.lang Keys:
+ * - prompt.okButton
+ * - prompt.cancelButton
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Dialog.Prompt = new Class({
+
+    Extends: Jx.Dialog,
+
+    options: {
+        /**
+         * Option: prompt
+         * The message to display to the user
+         */
+        prompt: '',
+        /**
+         * Option: startingValue
+         * The startingvalue to place in the input field
+         */
+        startingValue: '',
+        /**
+         * Option: fieldOptions,
+         * Object with various
+         */
+        fieldOptions: {
+          type : 'Text',
+          options: {},
+          validate : true,
+          validatorOptions: {
+            validators: ['required'],
+            validateOnBlur: true,
+            validateOnChange : false
+          },
+          showErrorMsg : true
+        },
+        /**
+         * Jx.Dialog option defaults
+         */
+        width: 400,
+        height: 200,
+        close: true,
+        resize: true,
+        collapse: false,
+        useKeyboard : true,
+        keys : {
+          'esc'   : 'cancel',
+          'enter' : 'ok'
+        }
+    },
+    /**
+     * APIMethod: render
+     * constructs the dialog.
+     */
+    render: function () {
+        //create content to be added
+        this.buttons = new Jx.Toolbar({position: 'bottom',scroll:false});
+        this.ok = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'okButton'}),
+                onClick: this.onClick.bind(this, true)
+            });
+        this.cancel = new Jx.Button({
+                label: this.getText({set:'Jx',key:'prompt',value:'cancelButton'}),
+                onClick: this.onClick.bind(this, false)
+            });
+        this.buttons.add(this.ok, this.cancel);
+        this.options.toolbars = [this.buttons];
+
+        var fOpts = this.options.fieldOptions;
+            fOpts.options.label = this.getText(this.options.prompt);
+            fOpts.options.value = this.options.startingValue;
+            fOpts.options.containerClass = 'jxPrompt';
+
+        if(Jx.type(fOpts.type) === 'string' && $defined(Jx.Field[fOpts.type.capitalize()])) {
+          this.field = new Jx.Field[fOpts.type.capitalize()](fOpts.options);
+        }else if(Jx.type(fOpts.type) === 'Jx.Object'){
+          this.field = fOpts.type;
+        }else{
+          // warning and fallback?
+          window.console ? console.warn("Field type does not exist %o, using Jx.Field.Text", fOpts.type) : false;
+          this.field = new Jx.Field.Text(fOpts.options);
+        }
+
+        if(this.options.fieldOptions.validate) {
+          this.validator = new Jx.Plugin.Field.Validator(this.options.fieldOptions.validatorOptions);
+          this.validator.attach(this.field);
+        }
+
+        this.options.content = document.id(this.field);
+        
+        if(this.options.useKeyboard) {
+          var self = this;
+          this.options.keyboardMethods.ok     = function(ev) { ev.preventDefault(); self.onClick(true); }
+          this.options.keyboardMethods.cancel = function(ev) { ev.preventDefault(); self.onClick(false); }
+        }
+        this.parent();
+        if(this.options.useKeyboard) {
+          this.keyboard.addEvents(this.getKeyboardEvents());
+        }
+    },
+    /**
+     * Method: onClick
+     * Called when the OK button is clicked. Closes the dialog.
+     */
+    onClick: function (value) {
+        if(value && $defined(this.validator)) {
+          if(this.validator.isValid()) {
+            this.isOpening = false;
+            this.hide();
+            this.fireEvent('close', [this, value, this.field.getValue()]);
+          }else{
+            //this.options.content.adopt(this.validator.getError());
+            this.field.field.focus.delay(50, this.field.field);
+            //todo: show error messages ?
+          }
+        }else{
+          this.isOpening = false;
+          this.hide();
+          this.fireEvent('close', [this, value, this.field.getValue()]);
+        }
+    },
+    
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.ok)) {
+    		this.ok.setLabel({set:'Jx',key:'prompt',value:'okButton'});
+    	}
+    	if ($defined(this.cancel)) {
+    		this.cancel.setLabel({set:'Jx',key:'prompt',value:'cancelButton'});
+    	}
+      this.field.label.set('html', this.getText(this.options.prompt));
+    }
+
+
+});
+/*
+---
+
+name: Jx.Panel.DataView
+
+description: A panel used for displaying records from a store in a list-style interface rather than a grid.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - Jx.Store
+ - Jx.List
+
+provides: [Jx.Panel.DataView]
+
+...
+ */
+// $Id: dataview.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Panel.DataView
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This panel extension takes a standard Jx.Store (or subclass) and displays
+ * each record as an item using a provided template. It sorts the store as requested
+ * before doing so. The class only creates the HTML and has no default CSS display. All
+ * styling must be done by the developer using the control.
+ *
+ *
+ * Events:
+ * renderDone - fires when the panel completes creating all of the items.
+ *
+ * License:
+ * Copyright (c) 2009, Jonathan Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.DataView = new Class({
+
+    Extends: Jx.Panel,
+
+    options: {
+        /**
+         * Option: data
+         * The store containing the data
+         */
+        data: null,
+        /**
+         * Option: sortColumns
+         * An array of columns to sort the store by.
+         */
+        sortColumns: null,
+        /**
+         * Option: itemTemplate
+         * The template to use in rendering records
+         */
+        itemTemplate: null,
+        /**
+         * Option: emptyTemplate
+         * the template that is displayed when there are no records in the
+         * store.
+         */
+        emptyTemplate: null,
+        /**
+         * Option: containerClass
+         * The class added to the container. It can be used to target the items
+         * in the panel.
+         */
+        containerClass: null,
+        /**
+         * Option: itemClass
+         * The class to add to each item. Used for styling purposes
+         */
+        itemClass: null,
+        /**
+         * Option: itemOptions
+         * Options to pass to the list object
+         */
+        listOptions: {
+            select: true,
+            hover: true
+        }
+    },
+
+    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('storeDataLoaded', this.bound.update);
+        this.options.data.addEvent('storeSortFinished', this.bound.update);
+        this.options.data.addEvent('storeDataLoadFailed', 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));
+    }
+});
+/*
+---
+
+name: Jx.Panel.DataView.Group
+
+description: A subclass of Dataview that can display records in groups.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel.DataView
+ - Jx.Selection
+
+provides: [Jx.Panel.DataView.Group]
+
+...
+ */
+// $Id: group.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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);
+    }
+
+});
+/*
+---
+
+name: Jx.ListItem
+
+description: Represents a single item in a listview.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.ListItem]
+
+...
+ */
+// $Id: listitem.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.ListItem',
+    Extends: Jx.Widget,
+
+    options: {
+        enabled: true,
+        template: '<li class="jxListItemContainer jxListItem"></li>'
+    },
+
+    classes: new Hash({
+        domObj: 'jxListItemContainer',
+        domContent: 'jxListItem'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    render: function () {
+        this.parent();
+        this.domContent.store('jxListItem', this);
+        this.domObj.store('jxListTarget', this.domContent);
+        this.loadContent(this.domContent);
+    },
+
+    enable: function(state) {
+
+    }
+});/*
+---
+
+name: Jx.ListView
+
+description: A widget that displays items in a list format.
+
+license: MIT-style license.
+
+requires:
+ - Jx.List
+ - Jx.ListItem
+
+provides: [Jx.ListView]
+
+css:
+ - list
+
+images:
+ - listitem.png
+...
+ */
+// $Id: listview.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Widget',
+    Extends: Jx.Widget,
+
+    pluginNamespace: 'ListView',
+
+    options: {
+        template: '<ul class="jxListView jxList"></ul>',
+        /**
+         * Option: listOptions
+         * control the behaviour of the list, see <Jx.List>
+         */
+        listOptions: {
+            hover: true,
+            press: true,
+            select: true
+        }
+    },
+
+    classes: new Hash({
+        domObj: 'jxListView',
+        listObj: 'jxList'
+    }),
+
+    /**
+     * APIMethod: render
+     */
+    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;
+        }
+
+        this.list = new Jx.List(this.listObj, this.options.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;
+    },
+
+    empty: function () {
+        this.list.empty();
+        return this;
+    }
+});/*
+---
+
+name: Jx.Field.Hidden
+
+description: Represents a hidden input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Hidden]
+
+...
+ */
+// $Id: hidden.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputHidden" type="hidden" name="{name}"/></span>'
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Hidden'
+
+});
+
+
+
+
+/*
+---
+
+name: Jx.Field.File
+
+description: Represents a file input w/upload and progress tracking capabilities (requires APC for progress)
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field.Text
+ - Jx.Button
+ - Core/Request.JSON
+ - Jx.Field.Hidden
+ - Jx.Form
+
+provides: [Jx.Field.File]
+
+css:
+ - file
+
+
+...
+ */
+/**
+ * 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.
+ *
+ * MooTools.lang Keys:
+ * - file.browseLabel
+ * 
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxFileInputs"><input class="jxInputFile" type="file" name="{name}" /></div><span class="jxInputTag"></span></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,
+        /**
+         * Option: mode
+         * determines whether this file field acts in single upload mode or
+         * multiple file upload mode. The multiple upload mode was done to work with
+         * Jx.Panel.FileUpload. When in multiple mode, this field will remove the actual
+         * file control after a file is selected, fires an event to signify the selection but will
+         * hold on to the files until told to upload them. Obviously 'multiple' mode isn't designed
+         * to be used when the control is outside of the Upload Panel (unless the user designs
+         * their own upload panel around it).
+         */
+        mode: 'single'
+
+    },
+    /**
+     * Property: type
+     * The Field type used in rendering
+     */
+    type: 'File',
+    /**
+     * Property: forms
+     * holds all form references when we're in multiple mode
+     */
+    forms: null,
+
+    init: function () {
+        this.parent();
+
+        this.forms = new Hash();
+        //create the iframe
+        //we use the same iFrame for each so we don't have to recreate it each time
+        this.isIFrameSetup = true;
+        this.iframe = new Element('iframe', {
+          name: this.generateId(),
+          styles: {
+            'display':'none',
+            'visibility':'hidden'
+          }
+        });
+        // this.iframe = new IFrame(null, {
+        //     styles: {
+        //         'display':'none',
+        //         'visibility':'hidden'
+        //     },
+        //     name : this.generateId()
+        // });
+        this.iframe.inject(document.body);
+        this.iframe.addEvent('load', this.processIFrameUpload.bind(this));
+
+    },
+
+    /**
+     * 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 : '<span class="jxInputContainer"><input class="jxInputText" type="text" /></span>'
+        });
+        this.browseButton = new Jx.Button({
+            label: this.getText({set:'Jx',key:'file',value:'browseLabel'})
+        });
+
+        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.options.mode=='single' && this.field.value !== '' && (this.text.field.value !== this.field.value)) {
+            this.text.field.value = this.field.value;
+            this.fireEvent('fileSelected', this);
+            this.forms.set(this.field.value, this.prepForm());
+            if (this.options.autoUpload) {
+                this.uploadSingle();
+            }
+        } else if (this.options.mode=='multiple') {
+            var filename = this.field.value;
+            var form = this.prepForm();
+            this.forms.set(filename, form);
+            this.text.setValue('');
+            //fire the selected event.
+            this.fireEvent('fileSelected', filename);
+        }
+    },
+    /**
+     * 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');
+    },
+
+    prepForm: function () {
+        //load in the form
+        var form = new Jx.Form({
+            action : this.options.handlerUrl,
+            name : 'jxUploadForm',
+            fileUpload: true
+        });
+
+        //move the form input into it (cloneNode)
+        var parent = document.id(this.field.getParent());
+        var sibling = document.id(this.field).getPrevious();
+        var clone = this.field.clone().cloneEvents(this.field);
+        document.id(form).grab(this.field);
+        // tried clone.replaces(this.field) but it didn't seem to work
+        if (sibling) {
+          clone.inject(sibling, 'after');
+        } else if (parent) {
+            clone.inject(parent, 'top');
+        }
+        this.field = clone;
+
+        this.mouseLeave();
+
+        return form;
+    },
+
+    upload: function (externalForm) {
+        //do we have files to upload?
+        if (this.forms.getLength() > 0) {
+            var keys = this.forms.getKeys();
+            this.currentKey = keys[0];
+            var form = this.forms.get(this.currentKey);
+            this.forms.erase(this.currentKey);
+            if ($defined(externalForm) && this.forms.getLength() == 0) {
+                var fields = externalForm.fields;
+                fields.each(function(field){
+                    if (!(field instanceof Jx.Field.File)) {
+                        document.id(field).clone().inject(form);
+                    }
+                },this);
+            }
+            this.uploadSingle(form);
+        } else {
+            //fire finished event...
+            this.fireEvent('allUploadsComplete', this);
+        }
+    },
+    /**
+     * APIMethod: uploadSingle
+     * Call this to upload the file to the server
+     */
+    uploadSingle: function (form) {
+        this.form = $defined(form) ? form : this.prepForm();
+
+        this.fireEvent('fileUploadBegin', [this.currentKey, this]);
+
+        this.text.setValue('');
+
+        document.id(this.form).set('target', this.iframe.get('name')).setStyles({
+            visibility: 'hidden',
+            display: 'none'
+        }).inject(document.body);
+
+        //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');
+        }
+
+        //submit the form
+        document.id(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('fileUploadProgress', [data, this.currentKey, this]);
+            if (data.current < data.total) {
+                this.polling = true;
+                this.pollUpload();
+            } else {
+                this.polling = false;
+            }
+        }
+    },
+    /**
+     * Method: uploadFailure
+     * called if there is a problem getting progress on the upload
+     */
+    uploadFailure: function (xhr) {
+        this.fireEvent('fileUploadProgressError', [this, xhr]);
+    },
+    /**
+     * 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
+        if (this.isIFrameSetup) {
+            if (this.iframe.contentDocument  && this.iframe.contentDocument.defaultView) {
+              var iframeBody = this.iframe.contentDocument.defaultView.document.body.innerHTML;
+            } else {
+              // seems to be needed for ie7
+              var iframeBody = this.iframe.contentWindow.document.body.innerHTML;
+            }
+
+            var data = JSON.decode(iframeBody);
+            if ($defined(data) && $defined(data.success) && data.success) {
+                this.done = true;
+                this.doneData = data;
+                this.uploadCleanUp();
+                this.fireEvent('fileUploadComplete', [data, this.currentKey, this]);
+            } else {
+                this.fireEvent('fileUploadError', [data , this.currentKey, this]);
+            }
+
+            if (this.options.mode == 'multiple') {
+                this.upload();
+            } else {
+                this.fireEvent('allUploadsComplete', 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();
+        }
+    },
+    /**
+     * APIMethod: remove
+     * Removes a file from the hash of forms to upload.
+     *
+     * Parameters:
+     * filename - the filename indicating which file to remove.
+     */
+    remove: function (filename) {
+        if (this.forms.has(filename)) {
+            this.forms.erase(filename);
+        }
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if ($defined(this.browseButton)) {
+    		this.browseButton.setLabel( this.getText({set:'Jx',key:'file',value:'browseLabel'}) );
+    	}
+    },
+    
+    /**
+     * APIMethod: getFileInputs
+     * Used to get an array of all of the basic file inputs. This is mainly 
+     * here for use by Jx.Form to be able to suck in the file inputs
+     * before a standard submit.
+     * 
+     */
+    getFileInputs: function () {
+        var inputs = [];
+        this.forms.each(function(form){
+            var input = document.id(form).getElement('input[type=file]');
+            inputs.push(input);
+        },this);
+        return inputs;
+    }
+});/*
+---
+
+name: Jx.Progressbar
+
+description: A css-based progress bar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Core/Fx.Tween
+
+provides: [Jx.Progressbar]
+
+css:
+ - progressbar
+
+images:
+ - progressbar.png
+
+...
+ */
+/**
+ * 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
+ * 
+ * MooTools.lang keys:
+ * - progressbar.messageText
+ * - progressbar.progressText
+ *
+ * Copyright (c) 2010 by Jonathan Bomgardner
+ * Licensed under an mit-style license
+ */
+Jx.Progressbar = new Class({
+    Family: 'Jx.Progressbar',
+    Extends: Jx.Widget,
+    
+    options: {
+        onUpdate: $empty,
+        onComplete: $empty,
+        /**
+         * Option: parent
+         * The element to put this progressbar into
+         */
+        parent: null,
+        /**
+         * Option: progressText
+         * Text to show while processing, uses 
+         * {progress} von {total}
+         */
+        progressText : null,
+        /**
+         * Option: template
+         * The template used to create the progressbar
+         */
+        template: '<div class="jxProgressBar-container"><div class="jxProgressBar-message"></div><div class="jxProgressBar"><div class="jxProgressBar-outline"></div><div class="jxProgressBar-fill"></div><div class="jxProgressBar-text"></div></div></div>'
+    },
+    /**
+     * Property: classes
+     * The classes used in the template
+     */
+    classes: new Hash({
+        domObj: 'jxProgressBar-container',
+        message: 'jxProgressBar-message', 
+        container: 'jxProgressBar',
+        outline: 'jxProgressBar-outline',
+        fill: 'jxProgressBar-fill',
+        text: '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.parent();
+        
+        if ($defined(this.options.parent)) {
+            this.domObj.inject(document.id(this.options.parent));
+        }
+        
+        this.domObj.addClass('jxProgressStarting');
+
+        //we need to know the width of the bar
+        this.width = document.id(this.domObj).getContentBoxSize().width;
+        
+        //Message
+        if (this.message) {
+            if ($defined(MooTools.lang.get('Jx','progressbar').messageText)) {
+                this.message.set('html', this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+            } else {
+                this.message.destroy();
+            }
+        }
+
+        //Fill
+        if (this.fill) {
+            this.fill.setStyles({
+                'width': 0
+            });
+        }
+        
+        //TODO: check for {progress} and {total} in progressText
+        var obj = {};
+        var progressText = this.options.progressText == null ? 
+                              this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                              this.getText(this.options.progressText);
+        if (progressText.contains('{progress}')) {
+            obj.progress = 0;
+        }
+        if (progressText.contains('{total}')) {
+            obj.total = 0;
+        }
+        
+        //Progress text
+        if (this.text) {
+            this.text.set('html', 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) {
+    	
+    	//check for starting class
+    	if (this.domObj.hasClass('jxProgressStarting')) {
+    		this.domObj.removeClass('jxProgressStarting').addClass('jxProgressWorking');
+    	}
+    	
+        var newWidth = (progress * this.width) / total;
+        
+        //update bar width
+        this.text.get('tween', {property:'width', onComplete: function() {
+            var obj = {};
+            var progressText = this.options.progressText == null ?
+                                  this.getText({set:'Jx',key:'progressbar',value:'progressText'}) :
+                                  this.getText(this.options.progressText);
+            if (progressText.contains('{progress}')) {
+                obj.progress = progress;
+            }
+            if (progressText.contains('{total}')) {
+                obj.total = total;
+            }
+            var t = progressText.substitute(obj);
+            this.text.set('text', t);
+        }.bind(this)}).start(newWidth);
+        
+        this.fill.get('tween', {property: 'width', onComplete: (function () {
+            
+            if (total === progress) {
+                this.complete = true;
+                this.domObj.removeClass('jxProgressWorking').addClass('jxProgressFinished');
+                this.fireEvent('complete');
+            } else {
+                this.fireEvent('update');
+            }
+        }).bind(this)}).start(newWidth);
+        
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.message) {
+    		this.message.set('html',this.getText({set:'Jx',key:'progressbar',value:'messageText'}));
+    	}
+        //progress text will update on next update.
+    }
+    
+});/*
+---
+
+name: Jx.Panel.FileUpload
+
+description: A panel subclass that is designed to be a multiple file upload panel with a queue listing.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Panel
+ - Jx.ListView
+ - Jx.Field.File
+ - Jx.Progressbar
+ - Jx.Button
+ - Jx.Toolbar.Item
+ - Jx.Tooltip
+
+provides: [Jx.Panel.FileUpload]
+
+css:
+ - upload
+
+images:
+ - icons.png
+...
+ */
+// $Id: upload.js 1000 2010-12-06 01:58:47Z jonlb at comcast.net $
+/**
+ * Class: Jx.Panel.FileUpload
+ *
+ * Extends: <Jx.Panel>
+ *
+ * This class extends Jx.Panel to provide a consistent interface for uploading
+ * files in an application.
+ * 
+ * MooTools.lang Keys:
+ * - upload.buttonText
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Panel.FileUpload = new Class({
+
+    Family: 'Jx.Panel.FileUpload',
+    Extends: Jx.Panel,
+    Binds: ['moveToQueue','fileUploadBegin', 'fileUploadComplete','allUploadsComplete', 'fileUploadProgressError,', 'fileUploadError', 'fileUploadProgress'],
+
+    options: {
+        /**
+         * Option: file
+         * An object containing the options for Jx.Field.File
+         */
+        file: {
+            autoUpload: false,
+            progress: false,
+            progressIDUrl: '',
+            handlerUrl: '',
+            progressUrl: ''
+        },
+
+        progressOptions: {
+            template: "<li class='jxListItemContainer jxProgressBar-container' id='{id}'><div class='jxProgressBar'><div class='jxProgressBar-outline'></div><div class='jxProgressBar-fill'></div><div class='jxProgressBar-text'></div></div></li>",
+            containerClass: 'progress-container',
+            messageText: null,
+            messageClass: 'progress-message',
+            progressText: 'Uploading {filename}',
+            progressClass: 'progress-bar'
+        },
+        /**
+         * Option: onFileComplete
+         * 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: 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: [],
+
+    listTemplate: "<li class='jxListItemContainer' id='{id}'><a class='jxListItem' href='javascript:void(0);'><span class='itemLabel jxUploadFileName'>{name}</span><span class='jxUploadFileDelete' title='Remove this file from the queue.'></span></a></li>",
+    /**
+     * Method: 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="jxInputContainer jxFileInputs"><input class="jxInputFile" type="file" name={name} /></div>';
+
+        this.file = new Jx.Field.File(this.fileOpt);
+        this.file.addEvent('fileSelected', this.moveToQueue);
+        this.file.addTo(this.domObjA);
+
+        this.listView = new Jx.ListView({
+            template: '<ul class="jxListView jxList jxUploadQueue"></ul>'
+            
+        }).addTo(this.domObjA);
+
+        if (!this.options.file.autoUpload) {
+            //this is the upload button at the bottom of the panel.
+            this.uploadBtn = new Jx.Button({
+                label : this.getText({set:'Jx',key:'upload',value:'buttonText'}),
+                onClick: this.upload.bind(this)
+            });
+            var tlb = new Jx.Toolbar({position: 'bottom', scroll: false}).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 (filename) {
+        var theTemplate = new String(this.listTemplate).substitute({
+            name: filename,
+            id: filename
+        });
+        var item = new Jx.ListItem({template:theTemplate, enabled: true});
+
+        $(item).getElement('.jxUploadFileDelete').addEvent('click', function(){
+            this.listView.remove(item);
+            this.file.remove(filename);
+            if (this.listView.list.count() == 0) {
+                this.uploadBtn.setEnabled(false);
+            }
+        }.bind(this));
+        this.listView.add(item);
+
+        if (!this.uploadBtn.isEnabled()) {
+            this.uploadBtn.setEnabled(true);
+        }
+
+    },
+    /**
+     * Method: upload
+     * Called when the user clicks the upload button. Runs the upload process.
+     */
+    upload: function () {
+
+        this.file.addEvents({
+            'fileUploadBegin': this.fileUploadBegin ,
+            'fileUploadComplete': this.fileUploadComplete,
+            'allUploadsComplete': this.allUploadsComplete,
+            'fileUploadError': this.fileUploadError,
+            'fileUploadProgress': this.fileUploadProgress,
+            'fileUploadProgressError': this.fileUploadError
+        });
+
+
+        this.file.upload();
+    },
+
+    fileUploadBegin: function (filename) {
+        if (this.options.file.progress) {
+            //progressbar
+            //setup options
+            // TODO: should (at least some of) these options be available to
+            // the developer?
+            var options = $merge({},this.options.progressOptions);
+            options.progressText = options.progressText.substitute({filename: filename});
+            options.template = options.template.substitute({id: filename});
+            this.pb = new Jx.Progressbar(options);
+            var item = document.id(filename);
+            this.oldContents = item;
+            this.listView.replace(item,$(this.pb));
+        } else {
+            var icon = document.id(filename).getElement('.jxUploadFileDelete')
+            icon.addClass('jxUploadFileProgress').set('title','File Uploading...');
+        }
+    },
+
+    /**
+     * Method: fileUploadComplete
+     * Called when a single file is uploaded completely .
+     *
+     * Parameters:
+     * data - the data returned from the event
+     * filename - the filename of 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, filename) {
+
+        if (this.options.file.progress) {
+            //show this old contents...
+            this.listView.replace(document.id(filename),this.oldContents);
+        }
+        var icon = document.id(filename).getElement('.jxUploadFileDelete');
+        icon.erase('title');
+        if (icon.hasClass('jxUploadFileProgress')) {
+            icon.removeClass('jxUploadFileProgress').addClass('jxUploadFileError');
+        } else {
+            icon.addClass('jxUploadFileError');
+        }
+        if ($defined(data.error) && $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 (filename) {
+
+        if (this.options.removeOnComplete) {
+           this.listView.remove(document.id(filename));
+        } else {
+            if (this.options.file.progress) {
+                this.listView.replace(document.id(filename),this.oldContents);
+            }
+            var l = document.id(filename).getElement('.jxUploadFileDelete');
+            if (l.hasClass('jxUploadFileDelete')) {
+                l.addClass('jxUploadFileComplete');
+            } else if (l.hasClass('jxUploadFileProgress')) {
+                l.removeClass('jxUploadFileProgress').addClass('jxUploadFileComplete');
+            }
+        }
+
+        this.fireEvent('fileUploadComplete', filename);
+    },
+    /**
+     * 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) {
+        if (this.options.progress) {
+            this.pb.update(data.total, data.current);
+        }
+    },
+    /**
+     * Method: allUploadCompleted
+     * Called when the Jx.Field.File completes uploading
+     * all files. Sets upload button to disabled and fires the allUploadCompleted
+     * event.
+     */
+    allUploadsComplete: function () {
+        this.uploadBtn.setEnabled(false);
+        this.fireEvent('allUploadsCompleted',this);
+    },
+    /**
+     * Method: createText
+     * handle change in language
+     */
+    changeText: function (lang) {
+      this.parent();
+      if ($defined(this.uploadBtn)) {
+        this.uploadBtn.setLabel({set:'Jx',key:'upload',value:'buttonText'});
+      }
+    }
+});
+/*
+---
+
+name: Jx.Column
+
+description: A representation of a single grid column
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+provides: [Jx.Column]
+
+...
+ */
+// $Id: column.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * 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({
+
+    Family: 'Jx.Column',
+    Extends: Jx.Widget,
+
+    options: {
+        /**
+         * Option: renderMode
+         * The mode to use in rendering this column to determine its width.
+         * Valid options include
+         *
+         * fit - auto calculates the width for the best fit to the text. This
+         *      is the default.
+         * fixed - uses the value passed in the width option, doesn't
+         *      auto calculate.
+         * expand - uses the value in the width option as a minimum width and
+         *      allows this column to expand and take up any leftover space.
+         *      NOTE: there can be only 1 expand column in a grid. The
+         *      Jx.Columns object will only take the first column with this
+         *      option as the expanding column. All remaining columns marked
+         *      "expand" will be treated as "fixed".
+         */
+        renderMode: 'fixed',
+        /**
+         * Option: width
+         * Determines the width of the column when using 'fixed' rendering mode
+         * or acts as a minimum width when using 'expand' mode.
+         */
+        width: 100,
+
+        /**
+         * 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: name
+         * The name given to this column
+         */
+        name: '',
+
+        /**
+         * Option: template
+         */
+        template: null,
+        /**
+         * Option: renderer
+         * an instance of a Jx.Grid.Renderer to use in rendering the content
+         * of this column or a config object for creating one like so:
+         *
+         * (code)
+         * {
+         *     name: 'Text',
+         *     options: { ... renderer options ... }
+         * }
+         */
+        renderer: null
+    },
+
+    classes: $H({
+      domObj: 'jxGridCellContent'
+    }),
+
+    /**
+     * Property: grid
+     * holds a reference to the grid (an instance of <Jx.Grid>)
+     */
+    grid: null,
+
+    parameters: ['options','grid'],
+
+    /**
+     * Constructor: Jx.Column
+     * initializes the column object
+     */
+    init : function () {
+
+        this.name = this.options.name;
+
+        //adjust header for column
+        if (!$defined(this.options.template)) {
+            this.options.template = '<span class="jxGridCellContent">' + this.name.capitalize() + '</span>';
+        }
+
+        this.parent();
+        if ($defined(this.options.grid) && this.options.grid instanceof Jx.Grid) {
+            this.grid = this.options.grid;
+        }
+
+        //check renderer
+        if (!$defined(this.options.renderer)) {
+            //set a default renderer
+            this.options.renderer = new Jx.Grid.Renderer.Text({
+                textTemplate: '{' + this.name + '}'
+            });
+        } else {
+            if (!(this.options.renderer instanceof Jx.Grid.Renderer)) {
+                var t = Jx.type(this.options.renderer);
+                if (t === 'object') {
+                    if(!$defined(this.options.renderer.options.textTemplate)) {
+                      this.options.renderer.options.textTemplate = '{' + this.name + '}';
+                    }
+                    if(!$defined(this.options.renderer.name)) {
+                      this.options.renderer.name = 'Text';
+                    }
+                    this.options.renderer = new Jx.Grid.Renderer[this.options.renderer.name.capitalize()](
+                            this.options.renderer.options);
+                }
+            }
+        }
+
+        this.options.renderer.setColumn(this);
+    },
+
+    getTemplate: function(idx) {
+      return "<span class='jxGridCellContent' title='{col"+idx+"}'>{col"+idx+"}</span>";
+    },
+
+    /**
+     * APIMethod: getHeaderHTML
+     */
+    getHeaderHTML : function () {
+      if (this.isSortable() && !this.sortImage) {
+        this.sortImage = new Element('img', {
+            src: Jx.aPixel.src
+        });
+        this.sortImage.inject(this.domObj);
+      } else {
+        if (!this.isSortable() && this.sortImage) {
+          this.sortImage.dispose();
+          this.sortImage = null;
+        }
+      }
+      return this.domObj;
+    },
+
+    setWidth: function(newWidth, asCellWidth) {
+        asCellWidth = $defined(asCellWidth) ? asCellWidth : false;
+
+        var delta = this.cellWidth - this.width;
+        if (!asCellWidth) {
+          this.width = parseInt(newWidth,10);
+          this.cellWidth = this.width + delta;
+          this.options.width = newWidth;
+        } else {
+            this.width = parseInt(newWidth,10) - delta;
+            this.cellWidth = newWidth;
+            this.options.width = this.width;
+        }
+      if (this.rule && parseInt(this.width,10) >= 0) {
+          this.rule.style.width = parseInt(this.width,10) + "px";
+      }
+      if (this.cellRule && parseInt(this.cellWidth,10) >= 0) {
+          this.cellRule.style.width = parseInt(this.cellWidth,10) + "px";
+      }
+    },
+
+    /**
+     * APIMethod: getWidth
+     * return the width of the column
+     */
+    getWidth: function () {
+      return this.width;
+    },
+
+    /**
+     * APIMethod: getCellWidth
+     * return the cellWidth of the column
+     */
+    getCellWidth: function() {
+      return this.cellWidth;
+    },
+
+    /**
+     * APIMethod: calculateWidth
+     * returns the width of the column.
+     *
+     * Parameters:
+     * rowHeader - flag to tell us if this calculation is for the row header
+     */
+    calculateWidth : function (rowHeader) {
+        //if this gets called then we assume that we want to calculate the width
+      rowHeader = $defined(rowHeader) ? rowHeader : false;
+      var maxWidth,
+          maxCellWidth,
+          store = this.grid.getStore(),
+          t,
+          s,
+          oldPos,
+          text,
+          klass;
+      store.first();
+      if ((this.options.renderMode == 'fixed' ||
+           this.options.renderMode == 'expand') &&
+          store.valid()) {
+        t = new Element('span', {
+          'class': 'jxGridCellContent',
+          html: 'a',
+          styles: {
+            width: this.options.width
+          }
+        });
+        s = this.measure(t,'jxGridCell');
+        maxWidth = s.content.width;
+        maxCellWidth = s.cell.width;
+      } else {
+          //calculate the width
+          oldPos = store.getPosition();
+          maxWidth = maxCellWidth = 0;
+          while (store.valid()) {
+              //check size by placing text into a TD and measuring it.
+              this.options.renderer.render();
+              text = document.id(this.options.renderer);
+              klass = 'jxGridCell';
+              if (this.grid.row.useHeaders()
+                      && this.options.name === this.grid.row
+                      .getRowHeaderColumn()) {
+                  klass = 'jxGridRowHead';
+              }
+              s = this.measure(text, klass, rowHeader, store.getPosition());
+              if (s.content.width > maxWidth) {
+                  maxWidth = s.content.width;
+              }
+              if (s.cell.width > maxCellWidth) {
+                maxCellWidth = s.cell.width;
+              }
+              if (store.hasNext()) {
+                  store.next();
+              } else {
+                  break;
+              }
+          }
+
+          //check the column header as well (unless this is the row header)
+          if (!(this.grid.row.useHeaders() &&
+              this.options.name === this.grid.row.getRowHeaderColumn())) {
+              klass = 'jxGridColHead';
+              if (this.isEditable()) {
+                  klass += ' jxColEditable';
+              }
+              if (this.isResizable()) {
+                  klass += ' jxColResizable';
+              }
+              if (this.isSortable()) {
+                  klass += ' jxColSortable';
+              }
+              s = this.measure(this.domObj.clone(), klass);
+              if (s.content.width > maxWidth) {
+                  maxWidth = s.content.width;
+              }
+              if (s.cell.width > maxCellWidth) {
+                maxCellWidth = s.cell.width;
+              }
+          }
+      }
+
+      this.width = maxWidth;
+      this.cellWidth = maxCellWidth;
+      store.moveTo(oldPos);
+      return this.width;
+    },
+    /**
+     * 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.
+     * rowHeader -
+     * row -
+     */
+    measure : function (text, klass, rowHeader, row) {
+        var d = new Element('span', {
+            'class' : klass
+        }),
+        s;
+        text.inject(d);
+        //d.setStyle('height', this.grid.row.getHeight(row));
+        d.setStyles({
+            'visibility' : 'hidden',
+            'width' : 'auto'
+        });
+
+        d.inject(document.body, 'bottom');
+        s = d.measure(function () {
+            var el = this;
+            //if not rowHeader, get size of innner span
+            if (!rowHeader) {
+                el = el.getFirst();
+            }
+            return {
+              content: el.getMarginBoxSize(),
+              cell: el.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: isAttached
+     * returns whether this column is attached to a store.
+     */
+    isAttached: function () {
+        return this.options.renderer.attached;
+    },
+
+    /**
+     * APIMethod: getHTML
+     * calls render method of the renderer object passed in.
+     */
+    getHTML : function () {
+        this.options.renderer.render();
+        return document.id(this.options.renderer);
+    }
+
+});/*
+---
+
+name: Jx.Columns
+
+description: A container for defining and holding individual columns
+
+license: MIT-style license.
+
+requires:
+ - Jx.Column
+ - Jx.Object
+
+provides: [Jx.Columns]
+
+...
+ */
+// $Id: columns.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * 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({
+
+  Family: 'Jx.Columns',
+    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 : [],
+    
+    /**
+     * Property: rowTemplate
+     * a string holding a template for a single row of cells to be populated
+     * when rendering the store into a grid.  The template is constructed from
+     * the individual column templates once the store has been loaded.
+     */
+    rowTemplate: null,
+
+    parameters: ['options','grid'],
+    /**
+     * Property: hasExpandable
+     * boolean indicates whether any of the columns are expandable or not,
+     * which affects some calculations for column widths
+     */
+    hasExpandable: null,
+
+    /**
+     * 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.hasExpandable = false;
+
+        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") {
+                this.columns.push(new Jx.Column(col,this.grid));
+            }
+            var c = this.columns[this.columns.length - 1 ];
+        }, this);
+        
+        this.buildTemplates();
+    },
+    
+    /**
+     * APIMethod: addColumns
+     * add new columns to the columns object after construction.  Causes
+     * the template to change.
+     * 
+     * Parameters:
+     * columns - {Array} an array of columns to add
+     */
+    addColumns: function(columns) {
+      this.columns.extend(columns);
+      this.buildTemplates();
+    },
+    
+    /**
+     * Method: buildTemplates
+     * create the row template based on the current columns
+     */
+    buildTemplates: function() {
+      if (!this.grid) {
+        return;
+      }
+      var rowTemplate = '',
+          hasExpandable = false,
+          grid = this.grid,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null,
+          colTemplate;
+      
+      this.columns.each(function(col, idx) {
+        var colTemplate = '';
+        if (!col.isHidden() && col != rhc) {
+          hasExpandable |= col.options.renderMode == 'expand';
+          if (!col.options.renderer || !col.options.renderer.domInsert) {
+            colTemplate = col.getTemplate(idx);
+          }
+          rowTemplate += "<td class='jxGridCell jxGridCol"+idx+" jxGridCol"+col.options.name+"'>" + colTemplate + "</td>";
+        }
+      });
+      if (!hasExpandable) {
+        rowTemplate += "<td><span class='jxGridCellUnattached'></span></td>";
+      }
+      this.rowTemplate = rowTemplate;
+      this.hasExpandable = hasExpandable;
+    },
+    /**
+     * 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren();
+        var cell = headers[index];
+          var hClasses = cell.get('class').split(' ').filter(function (cls) {
+            if(this.options.useHeaders)
+              return cls.test('jxColHead-');
+            else
+              return cls.test('jxCol-');
+          }.bind(this));
+        var parts = hClasses[0].split('-');
+        return this.getByName(parts[1]);
+    },
+
+    /**
+     * APIMethod: getHeaders
+     * Returns a row with the headers in it.
+     *
+     * Parameters:
+     * row - the row to add the headers to.
+     */
+    getHeaders : function (tr) {
+      var grid = this.grid,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? this.getByName(row.options.headerColumn) : null;
+      if (this.useHeaders()) {
+        this.columns.each(function(col, idx) {
+          if (!col.isHidden() && col != rhc) {
+            var classes = ['jxGridColHead', 'jxGridCol'+idx, 'jxCol-'+col.options.name, 'jxColHead-'+col.options.name],
+                th;
+            if (col.isEditable()) { classes.push('jxColEditable'); }
+            if (col.isResizable()) { classes.push('jxColResizable'); }
+            if (col.isSortable()) { classes.push('jxColSortable'); }
+            th = new Element('th', {
+              'class': classes.join(' ')
+            });
+            th.store('jxCellData', {
+              column: col,
+              colHeader: true,
+              index: idx
+            });
+            th.adopt(col.getHeaderHTML());
+            th.inject(tr);
+          }
+        });
+        if (!this.hasExpandable) {
+          new Element('th', {
+            'class': 'jxGridColHead jxGridCellUnattached'
+          }).inject(tr);
+        }
+      }
+    },
+    
+    /**
+     * Method: getRow
+     * create a single row in the grid for a single record and populate
+     * the DOM elements for it.
+     *
+     * Parameters:
+     * tr - {DOMElement} the TR element to insert the row into
+     * record - {<Jx.Record>} the record to create the row for
+     */
+    getRow: function(tr, record) {
+      var data = {},
+          grid = this.grid,
+          store = grid.store,
+          row = grid.row,
+          rhc = grid.row.useHeaders() ? 
+                     this.getByName(row.options.headerColumn) : null,
+          domInserts = [],
+          i = 0;
+      this.columns.each(function(column, index) {
+        if (!column.isHidden() && column != rhc) {
+          if (column.options.renderer && column.options.renderer.domInsert) {
+            domInserts.push({column: column, index: i});
+          } else {
+            var renderer = column.options.renderer,
+                formatter = renderer.options.formatter,
+                text = '';
+            if (renderer.options.textTemplate) {
+              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
+            } else {
+              text = record.data.get(column.name);
+            }
+            if (formatter) {
+              text = formatter.format(text);
+            }
+            data['col'+index] = text;
+          }
+          i++;
+        }
+      });
+      tr.set('html', this.rowTemplate.substitute(data));
+      domInserts.each(function(obj) {
+        tr.childNodes[obj.index].adopt(obj.column.getHTML());
+      });
+    },
+
+    /**
+     * APIMethod: calculateWidths
+     * force calculation of column widths.  For columns with 'fit' this will
+     * cause the column to test every value in the store to compute the
+     * optimal width of the column.  Columns marked as 'expand' will get
+     * any extra space left over between the column widths and the width
+     * of the grid container (if any).
+     */
+    calculateWidths: function () {
+      //to calculate widths we loop through each column
+      var expand = null,
+          totalWidth = 0,
+          rowHeaderWidth = 0,
+          gridSize = this.grid.contentContainer.getContentBoxSize(),
+          leftOverSpace = 0;
+      this.columns.each(function(col,idx){
+        //are we checking the rowheader?
+        var rowHeader = false;
+        // if (col.name == this.grid.row.options.headerColumn) {
+        //   rowHeader = true;
+        // }
+        //if it's fixed, set the width to the passed in width
+        if (col.options.renderMode == 'fixed') {
+          col.calculateWidth(); //col.setWidth(col.options.width);
+          
+        } else if (col.options.renderMode == 'fit') {
+          col.calculateWidth(rowHeader);
+        } else if (col.options.renderMode == 'expand' && !$defined(expand)) {
+          expand = col;
+        } else {
+          //treat it as fixed if has width, otherwise as fit
+          if ($defined(col.options.width)) {
+            col.setWidth(col.options.width);
+          } else {
+            col.calculateWidth(rowHeader);
+          }
+        }
+        if (!col.isHidden() /* && !(col.name == this.grid.row.options.headerColumn) */) {
+            totalWidth += Jx.getNumber(col.getCellWidth());
+            if (rowHeader) {
+                rowHeaderWidth = col.getWidth();
+            }
+        }
+      },this);
+      
+      // width of the container
+      if (gridSize.width > totalWidth) {
+        //now figure the expand column
+        if ($defined(expand)) {
+          // var leftOverSpace = gridSize.width - totalWidth + rowHeaderWidth;
+          leftOverSpace = gridSize.width - totalWidth;
+          //account for right borders in firefox...
+          if (Browser.Engine.gecko) {
+            leftOverSpace -= this.getColumnCount(true);
+          } else {
+            // -2 is for the right hand border on the cell and the table for all other browsers
+            leftOverSpace -= 2;
+          }
+          if (leftOverSpace >= expand.options.width) {
+            //in order for this to be set properly the cellWidth must be the
+            //leftover space. we need to figure out the delta value and
+            //subtract it from the leftover width
+            expand.options.width = leftOverSpace;
+            expand.calculateWidth();
+            expand.setWidth(leftOverSpace, true);
+            totalWidth += leftOverSpace;
+          } else {
+            expand.setWidth(expand.options.width);
+          }
+        }
+      }
+      this.grid.gridObj.setContentBoxSize({'width': totalWidth});
+      this.grid.colObj.setContentBoxSize({'width': totalWidth});
+    },
+
+    /**
+     * Method: createRules
+     * create CSS rules for the current grid object
+     */
+    createRules: function(styleSheet, scope) {
+      var autoRowHeight = this.grid.row.options.rowHeight == 'auto';
+      this.columns.each(function(col, idx) {
+        var selector = scope+' .jxGridCol'+idx,
+            dec = '';
+        if (autoRowHeight) {
+          //set the white-space to 'normal !important'
+          dec = 'white-space: normal !important';
+        }
+        col.cellRule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+        col.cellRule.style.width = col.getCellWidth() + "px";
+
+        selector = scope+" .jxGridCol" + idx + " .jxGridCellContent";
+        col.rule = Jx.Styles.insertCssRule(selector, dec, styleSheet);
+        col.rule.style.width = col.getWidth() + "px";
+      }, this);
+    },
+
+    updateRule: function(column) {
+        var col = this.getByName(column);
+        if (col.options.renderMode === 'fit') {
+          col.calculateWidth();
+        }
+        col.rule.style.width = col.getWidth() + "px";
+        col.cellRule.style.width = col.getCellWidth() + "px";
+    },
+    
+    /**
+     * APIMethod: getColumnCount
+     * returns the number of columns in this model (including hidden).
+     */
+    getColumnCount : function (noHidden) {
+        noHidden = $defined(noHidden) ? false : true;
+        var total = this.columns.length;
+        if (noHidden) {
+            this.columns.each(function(col){
+                if (col.isHidden()) {
+                    total -= 1;
+                }
+            },this);
+        }
+        return total;
+    },
+    /**
+     * 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.options.useHeaders ? 
+                        this.grid.colTableBody.getFirst().getChildren() :
+                        this.grid.gridTableBody.getFirst().getChildren(),
+            c,
+            i = -1,
+            self = this;
+        headers.each(function (h) {
+            i++;
+            var hClasses = h.get('class').split(' ').filter(function (cls) {
+                if(self.options.useHeaders)
+                  return cls.test('jxColHead-');
+                else
+                  return cls.test('jxCol-');
+            });
+            hClasses.each(function (cls) {
+                if (cls.test(name)) {
+                    c = i;
+                }
+            });
+        }, this);
+        return c;
+    }
+
+});
+/*
+---
+
+name: Jx.Row
+
+description: Holds information related to display of rows in the grid.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Row]
+
+...
+ */
+// $Id: row.js 986 2010-09-15 19:01:47Z pagameba $
+/**
+ * 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({
+
+  Family: 'Jx.Row',
+    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 : 40,
+        /**
+         * Option: headerColumn
+         * The name of the column in the model to use as the header
+         */
+        headerColumn : null
+    },
+    /**
+     * Property: grid
+     * A reference to the grid that this row model belongs to
+     */
+    grid : null,
+    /**
+     * Property: heights
+     * This will hold the calculated height of each row in the grid.
+     */
+    heights: [],
+    /**
+     * Property: rules
+     * A hash that will hold all of the CSS rules for the rows.
+     */
+    rules: $H(),
+
+    parameters: ['options','grid'],
+
+    /**
+     * 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 (row, text) {
+        var o = this.options,
+            rc = o.rowClasses,
+            c = o.alternateRowColors ?(row % 2 ? rc.even : rc.odd) : rc.all,
+            tr = new Element('tr', {
+              'class' : 'jxGridRow'+row+' '+ c,
+              html: text || ''
+            });
+        return tr;
+    },
+    /**
+     * Method: getRowHeaderCell
+     * creates the TH for the row's header
+     */
+    getRowHeaderCell : function (text) {
+      text = text ? '<span class="jxGridCellContent">'+text + '</span>' : '';
+      return new Element('th', {
+        'class' : 'jxGridRowHead',
+        html: text
+      });
+    },
+    /**
+     * APIMethod: getRowHeaderWidth
+     * determines the row header's width.
+     */
+    getRowHeaderWidth : function () {
+      var col, width;
+      if (this.options.headerColumn) {
+        col = this.grid.columns.getByName(this.options.headerColumn);
+        if (!$defined(col.getWidth())) {
+          col.calculateWidth(true);
+        }
+        width = col.getWidth();
+      } else {
+        width = this.options.headerWidth;
+      }
+      return width;
+    },
+
+    /**
+     * APIMethod: getHeight
+     * determines and returns the height of a row
+     */
+    getHeight : function (row) {
+      var h = this.options.rowHeight,
+          rowEl;
+      //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.
+      if ($defined(this.heights[row])) {
+        h = this.heights[row];
+      } else if ($defined(this.options.rowHeight)) {
+        if (this.options.rowHeight == 'auto') {
+          // this.calculateHeight(row);
+          h = 20; // TODO calculate?
+          rowEl = this.grid.gridTableBody.rows[row]
+          if (rowEl) {
+            h = rowEl.getContentBoxSize().height; 
+          }
+        } else if (Jx.type(this.options.rowHeight) !== 'number') {
+          h = 20; // TODO calculate?
+        }
+      }
+      return h;
+    },
+    /**
+     * Method: calculateHeights
+     */
+    calculateHeights : function () {
+      if (this.options.rowHeight === 'auto' ||
+          !$defined(this.options.rowHeight)) {
+        //grab all rows in the grid body
+        document.id(this.grid.gridTableBody).getChildren().each(function(row){
+          row = document.id(row);
+          var data = row.retrieve('jxRowData');
+          var s = row.getContentBoxSize();
+          this.heights[data.row] = s.height;
+        },this);
+        document.id(this.grid.rowTableHead).getChildren().each(function(row){
+          row = document.id(row);
+          var data = row.retrieve('jxRowData');
+          if (data) {
+            var s = row.getContentBoxSize();
+            this.heights[data.row] = Math.max(this.heights[data.row],s.height);
+            if (Browser.Engine.webkit) {
+                //for some reason webkit (Safari and Chrome)
+                this.heights[data.row] -= 1;
+            }
+          }
+        },this);
+      } else {
+        document.id(this.grid.rowTableHead).getChildren().each(function(row,idx){
+          this.heights[idx] = this.options.rowHeight;
+        }, this);
+      }
+    },
+
+    /**
+     * 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
+     *
+     * Parameters:
+     * list - Jx.List instance to add the header to
+     */
+    getRowHeader : function (list) {
+        var th = this.getRowHeaderCell();
+        //if (this.grid.model.getPosition() === 0) {
+        //    var rowWidth = this.getRowHeaderWidth();
+        //    th.setStyle("width", rowWidth);
+        //}
+        th.store('jxCellData', {
+            rowHeader: true,
+            row: this.grid.model.getPosition()
+        });
+        list.add(th);
+    },
+    /**
+     * APIMethod: getRowHeaderColumn
+     * returns the name of the column that is used for the row header
+     */
+    getRowHeaderColumn : function () {
+        return this.options.headerColumn;
+    }
+});
+/*
+---
+
+name: Jx.Plugin
+
+description: Base class for all plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Plugin]
+
+...
+ */
+// $Id: plugin.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.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({
+
+    Family: "Jx.Plugin",
+
+    Extends: Jx.Object,
+
+    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);
+    },
+
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     *    translations changed.
+     */
+    changeText: function (lang) {
+        //if the mask is being used then recreate it. The code will pull
+        //the new text automatically
+        if (this.busy) {
+            this.setBusy(false);
+            this.setBusy(true);
+        }
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid
+
+description: Namespace for grid plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Grid]
+
+...
+ */
+// $Id: plugin.grid.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Grid
+
+description: A tabular control that has fixed scrolling headers on the rows and columns like a spreadsheet.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.Styles
+ - Jx.Layout
+ - Jx.Columns
+ - Jx.Row
+ - Jx.Plugin.Grid
+ - Jx.Store
+ - Jx.List
+
+provides: [Jx.Grid]
+
+css:
+ - grid
+
+images:
+ - table_col.png
+ - table_row.png
+
+...
+ */
+// $Id: grid.js 995 2010-10-25 14:47:15Z pagameba $
+/**
+ * Class: Jx.Grid
+ *
+ * Extends: <Jx.Widget>
+ *
+ * A tabular control that has fixed, optional, 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 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. 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
+ * Editor - allows editing of cells if the column permits editing
+ *
+ * Jx.Grid renders data that comes from an external source.  This external
+ * source, called the store, must be a Jx.Store or extended from it.
+ *
+ * Events:
+ * gridCellEnter(cell, list) - called when the mouse enters a cell
+ * gridCellLeave(cell, list) - called when the mouse leaves a cell
+ * gridCellClick(cell) - called when a cell is clicked
+ * gridRowEnter(cell, list) - called when the mouse enters a row header
+ * gridRowLeave(cell, list) - called when the mouse leaves a row header
+ * gridRowClick(cell) - called when a row header is clicked
+ * gridColumnEnter(cell, list) - called when the mouse enters a column header
+ * gridColumnLeave(cell, list) - called when the mouse leaves a column header
+ * gridColumnClick(cell) - called when a column header is clicked
+ * gridMouseLeave() - called when the mouse leaves the grid at any point.
+ *
+ * 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',
+  Extends: Jx.Widget,
+  Binds: ['storeLoaded', 'clickColumnHeader', 'moveColumnHeader', 'clickRowHeader', 'moveRowHeader', 'clickCell', 'dblclickCell', 'moveCell', 'leaveGrid', 'resize', 'drawStore', 'scroll', 'addRow', 'removeRow', 'removeRows', 'updateRow', 'storeChangesCompleted'],
+
+  /**
+   * Property: pluginNamespace
+   * the required variable for plugins
+   */
+  pluginNamespace: 'Grid',
+  
+  options: {
+    /**
+     * Option: parent
+     * the HTML element to create the grid inside. The grid will resize
+     * to fill the domObj.
+     */
+    parent: null,
+    
+    template: "<div class='jxWidget'><div class='jxGridContainer jxGridRowCol'></div><div class='jxGridContainer jxGridColumnsContainer'><table class='jxGridTable jxGridHeader jxGridColumns'><thead class='jxGridColumnHead'></thead></table></div><div class='jxGridContainer jxGridHeader jxGridRowContainer'><table class='jxGridTable jxGridRows'><thead class='jxGridRowBody'></thead></table></div><div class='jxGridContainer jxGridContentContainer'><table class='jxGridTable jxGridContent'><tbody class='jxGridTableBody'></tbody></table></div></div>",
+    
+    /**
+     * 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.
+     */
+    columns: null,
+    
+    /**
+     * Option: row
+     * Either a Jx.Grid.Row object or a json object defining options for
+     * the class
+     */
+    row : null,
+
+    /**
+     * Option: store
+     * An instance of Jx.Store
+     */
+    store: null
+  },
+   
+  classes: $H({
+    domObj: 'jxWidget',
+    columnContainer: 'jxGridColumnsContainer',
+    colObj: 'jxGridColumns',
+    colTableBody: 'jxGridColumnHead',
+    rowContainer: 'jxGridRowContainer',
+    rowObj: 'jxGridRows',
+    rowColContainer: 'jxGridRowCol',
+    rowTableBody: 'jxGridRowBody',
+    contentContainer: 'jxGridContentContainer',
+    gridObj: 'jxGridContent',
+    gridTableBody: 'jxGridTableBody'
+  }),
+  
+  /**
+   * Property: columns
+   * holds a reference to the columns object
+   */
+  columns: null,
+  
+  /**
+   * Property: row
+   * Holds a reference to the row object
+   */
+  row: null,
+  
+  parameters: ['store', 'options'],
+  
+  /**
+   * Property: store
+   * holds a reference to the <Jx.Store> that is the store for this
+   * grid
+   */
+  store: null,
+  
+  /**
+   * Property: styleSheet
+   * the name of the dynamic style sheet to use for manipulating styles
+   */
+  styleSheet: 'JxGridStyles',
+  
+  /**
+   * Property: hooks
+   * a {Hash} of event names for tracking which events have actually been attached
+   * to the grid.
+   */
+  hooks: null,
+  
+  /**
+   * Property: uniqueId
+   * an auto-generated id that is assigned as a class name to the grid's
+   * container for scoping generated CSS rules to just this grid
+   */
+  uniqueId: null,
+  
+  /**
+   * Constructor: Jx.Grid
+   */
+  init: function() {
+    this.uniqueId = this.generateId('jxGrid_');
+    this.store = this.options.store;
+    var options = this.options,
+        opts;
+
+    if ($defined(options.row)) {
+      if (options.row instanceof Jx.Row) {
+        this.row = options.row;
+        this.row.grid = this;
+      } else if (Jx.type(options.row) == 'object') {
+        this.row = new Jx.Row($extend({grid: this}, options.row));
+      }
+    } else {
+      this.row = new Jx.Row({grid: this});
+    }
+
+    if ($defined(options.columns)) {
+        if (options.columns instanceof Jx.Columns) {
+            this.columns = options.columns;
+            this.columns.grid = this;
+        } else if (Jx.type(options.columns) === 'object') {
+            this.columns = new Jx.Columns($extend({grid:this}, options.columns));
+        }
+    } else {
+      this.columns = new Jx.Columns({grid: this});
+    }
+    
+    this.hooks = $H({
+      'gridScroll': false,
+      'gridColumnEnter': false,
+      'gridColumnLeave': false,
+      'gridColumnClick': false,
+      'gridRowEnter': false,
+      'gridRowLeave': false,
+      'gridRowClick': false,
+      'gridCellClick': false,
+      'gridCellDblClick': false,
+      'gridCellEnter': false,
+      'gridCellLeave': false,
+      'gridMouseLeave': false
+    });
+    
+    this.storeEvents = {
+      'storeDataLoaded': this.storeLoaded,
+      // 'storeSortFinished': this.drawStore,
+      'storeRecordAdded': this.addRow,
+      'storeColumnChanged': this.updateRow,
+      'storeRecordRemoved': this.removeRow,
+      'storeMultipleRecordsRemoved': this.removeRows,
+      'storeChangesCompleted': this.storeChangesCompleted
+    };
+    
+    
+    this.parent();
+  },
+  
+  wantEvent: function(eventName) {
+    var hook = this.hooks.get(eventName);
+    if (hook === false) {
+      switch(eventName) {
+        case 'gridColumnEnter':
+        case 'gridColumnLeave':
+          this.colObj.addEvent('mousemove', this.moveColumnHeader);
+          this.hooks.set({
+            'gridColumnEnter': true,
+            'gridColumnLeave': true
+          });
+          break;
+        case 'gridColumnClick':
+          this.colObj.addEvent('click', this.clickColumnHeader);
+          this.hooks.set({
+            'gridColumnClick': true
+          });
+          break;
+        case 'gridRowEnter':
+        case 'gridRowLeave':
+          this.rowObj.addEvent('mousemove', this.moveRowHeader);
+          this.hooks.set({
+            'gridRowEnter': true,
+            'gridRowLeave': true
+          });
+          break;
+        case 'gridRowClick':
+          this.rowObj.addEvent('click', this.clickRowHeader);
+          this.hooks.set({
+            'gridRowClick': true
+          });
+          break;
+        case 'gridCellEnter':
+        case 'gridCellLeave':
+          this.gridObj.addEvent('mousemove', this.moveCell);
+          this.hooks.set({
+            'gridCellEnter': true,
+            'gridCellLeave': true
+          });
+          break;
+        case 'gridCellClick':
+          this.gridObj.addEvent('click', this.clickCell);
+          this.hooks.set('gridCellClick', true);
+          break;
+        case 'gridCellDblClick':
+          this.gridObj.addEvent('dblclick', this.dblclickCell);
+          this.hooks.set('gridCellDblClick', true);
+          break;
+        case 'gridMouseLeave':
+          this.rowObj.addEvent('mouseleave', this.leaveGrid);
+          this.colObj.addEvent('mouseleave', this.leaveGrid);
+          this.gridObj.addEvent('mouseleave', this.leaveGrid);
+          this.hooks.set('gridMouseLeave', true);
+          break;
+        case 'gridScroll':
+          this.contentContainer.addEvent('scroll', this.scroll);
+        default:
+          break;
+      }
+    }
+  },
+  
+  /**
+   * Method: scroll
+   * handle the grid scrolling by updating the position of the headers
+   */
+  scroll : function () {
+      this.columnContainer.scrollLeft = this.contentContainer.scrollLeft;
+      this.rowContainer.scrollTop = this.contentContainer.scrollTop;
+  },
+  
+  /**
+   * APIMethod: render
+   * Create the grid for the current model
+   */
+  render: function() {
+    if (this.domObj) {
+      this.redraw();
+      return;
+    }
+    this.parent();
+    var store = this.store;
+    
+    this.domObj.addClass(this.uniqueId);
+    new Jx.Layout(this.domObj, {
+      onSizeChange: this.resize
+    });
+    
+    if (store instanceof Jx.Store) {
+      store.addEvents(this.storeEvents);
+      if (store.loaded) {
+        this.storeLoaded(store);
+      }
+    }
+    if (!this.columns.useHeaders()) {
+      this.columnContainer.dispose();
+    } else {
+      this.wantEvent('gridScroll');
+    }
+    
+    if (!this.row.useHeaders()) {
+      this.rowContainer.dispose();
+    } else {
+      this.wantEvent('gridScroll');
+    }
+
+    this.contentContainer.setStyle('overflow', 'auto');
+    
+    // todo: very hacky!  can plugins 'wantEvent' between init and render?
+    this.hooks.each(function(value, key) {
+      if (value) {
+        this.hooks.set(key, false);
+        this.wantEvent(key);
+      }
+    }, this);
+    
+    if (document.id(this.options.parent)) {
+      this.addTo(this.options.parent);
+      this.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() {
+    var p = this.domObj.getParent(),
+        parentSize = p.getSize(),
+        colHeaderHeight = 0,
+        rowHeaderWidth = 0;
+    
+    if (this.columns.useHeaders()) {
+      colHeaderHeight = this.columns.getHeaderHeight();
+    }
+    
+    if (this.row.useHeaders()) {
+      rowHeaderWidth = this.row.getRowHeaderWidth();
+    }
+    
+    this.rowColContainer.setBorderBoxSize({
+        width : rowHeaderWidth,
+        height : colHeaderHeight
+    });
+    
+    this.columnContainer.setStyles({
+      top: 0,
+      left: rowHeaderWidth
+    }).setBorderBoxSize({
+      width: parentSize.x - rowHeaderWidth,
+      height: colHeaderHeight
+    });
+
+    this.rowContainer.setStyles({
+      top: colHeaderHeight,
+      left: 0
+    }).setBorderBoxSize({
+      width: rowHeaderWidth,
+      height: parentSize.y - colHeaderHeight
+    });
+
+    this.contentContainer.setStyles({
+      top: colHeaderHeight,
+      left: rowHeaderWidth
+    }).setBorderBoxSize({
+      width: parentSize.x - rowHeaderWidth,
+      height: parentSize.y - colHeaderHeight
+    });
+  },
+  
+  /**
+   * APIMethod: setStore
+   * set the store for the grid to display.  If a store is attached to the
+   * grid it is removed and the new store is displayed.
+   *
+   * Parameters:
+   * store - {Object} the store to use for this grid
+   */
+  setStore: function(store) {
+    if (this.store) {
+      this.store.removeEvents(this.storeEvents);
+    }
+    if (store instanceof Jx.Store) {
+      this.store = store;
+      store.addEvents(this.storeEvents);
+      if (store.loaded) {
+        this.storeLoaded(store);
+      }
+      this.render();
+      this.domObj.resize();
+    } else {
+      this.destroyGrid();
+    }
+  },
+  
+  /**
+   * APIMethod: getStore
+   * gets the store set for this grid.
+   */
+  getStore: function() { 
+    return this.store;
+  },
+  
+  storeLoaded: function(store) {
+    this.redraw();
+  },
+  
+  /**
+   */
+  storeChangesCompleted: function(results) {
+    if (results && results.successful) {
+      
+    }
+  },
+  
+  redraw: function() {
+    var store = this.store,
+        template = '',
+        tr,
+        columns = [],
+        useRowHeaders = this.row.useHeaders();
+    this.fireEvent('beginCreateGrid');
+    
+    this.gridObj.getElement('tbody').empty();
+    
+    this.hoverColumn = this.hoverRow = this.hoverCell = null;
+    
+    // TODO: consider moving whole thing into Jx.Columns ??
+    // create a suitable column representation for everything
+    // in the store that doesn't already have a representation
+    store.options.columns.each(function(col, index) {
+      if (!this.columns.getByName(col.name)) {
+        var renderer = new Jx.Grid.Renderer.Text(),
+            format = $defined(col.format) ? col.format : null,
+            template = "<span class='jxGridCellContent'>"+ ($defined(col.label) ? col.label : col.name).capitalize() + "</span>",
+            column;
+        if ($defined(col.renderer)) {
+          if ($type(col.renderer) == 'string') {
+            if (Jx.Grid.Renderer[col.renderer.capitalize()]) {
+              renderer = new Jx.Grid.Renderer[col.renderer.capitalize()]();
+            }
+          } else if ($type(col.renderer) == 'object' && 
+                     $defined(col.renderer.type) && 
+                     Jx.Grid.Renderer[col.renderer.type.capitalize()]) {
+            renderer = new Jx.Grid.Renderer[col.renderer.type.capitalize()](col.renderer);
+          }
+        }
+        if (format) {
+          if ($type(format) == 'string' && 
+              $defined(Jx.Formatter[format.capitalize()])) {
+            renderer.options.formatter = new Jx.Formatter[format.capitalize()]();
+          } else if ($type(format) == 'object' && 
+                     $defined(format.type) && 
+                     $defined(Jx.Formatter[format.type.capitalize()])) {
+             renderer.options.formatter = new Jx.Formatter[format.type.capitalize()](format);
+          }
+        }
+        column = new Jx.Column({
+          grid: this,
+          template: template,
+          renderMode: $defined(col.renderMode) ? col.renderMode : $defined(col.width) ? 'fixed' : 'fit',
+          width: $defined(col.width) ? col.width : null,
+          isEditable: $defined(col.editable) ? col.editable : false,
+          isSortable: $defined(col.sortable) ? col.sortable : false,
+          isResizable: $defined(col.resizable) ? col.resizable : false,
+          isHidden: $defined(col.hidden) ? col.hidden : false,
+          name: col.name || '',
+          renderer: renderer 
+        });
+        columns.push(column);
+      }
+    }, this);
+    this.columns.addColumns(columns);
+    if (this.columns.useHeaders()) {
+      tr = new Element('tr');
+      this.columns.getHeaders(tr);
+      tr.adopt(new Element('th', {
+        'class': 'jxGridColHead',
+        'html': '&nbsp',
+        styles: {
+          width: 1000
+        }
+      }))
+      this.colObj.getElement('thead').empty().adopt(tr);
+    }
+    this.columns.calculateWidths();
+    this.columns.createRules(this.styleSheet+'Columns', '.'+this.uniqueId);
+    this.drawStore();
+    this.fireEvent('doneCreateGrid');
+  },
+  
+  /**
+   * APIMethod: addRow
+   * Adds a row to the table. Can add to either the beginning or the end 
+   * based on passed flag
+   */
+  addRow: function (store, record, position) {
+    if (this.store.loaded) {
+      if (position === 'bottom') {
+        this.store.last();
+      } else {
+        this.store.first();
+      }
+      this.drawRow(record, this.store.index, position);
+    }
+  },
+  
+  /**
+   * APIMethod: updateRow
+   * update a single row in the grid
+   *
+   * Parameters:
+   * index - the row to update
+   */
+  updateRow: function(index) {
+    var record = this.store.getRecord(index);
+    this.drawRow(record, index, 'replace');
+  },
+  
+  /**
+   * APIMethod: removeRow
+   * remove a single row from the grid
+   *
+   * Parameters:
+   * store
+   * index
+   */
+  removeRow: function (store, index) {
+    this.gridObj.deleteRow(index);
+    this.rowObj.deleteRow(index);
+  },
+  
+  /**
+   * APIMethod: removeRows
+   * removes multiple rows from the grid
+   *
+   * Parameters:
+   * store
+   * index
+   */
+  removeRows: function (store, first, last) {
+    for (var i = first; i <= last; i++) {
+        this.removeRow(store, first);
+    }
+  },
+  
+  /**
+   * APIMethod: setColumnWidth
+   * set the width of a column in pixels
+   *
+   * Parameters:
+   * column
+   * width
+   */
+  setColumnWidth: function(column, width) {
+    if (column) {
+      column.width = width;
+      if (column.rule) {
+        column.rule.style.width = width + 'px';
+      }
+      if (column.cellRule) {
+        column.cellRule.style.width = width + 'px';
+      }
+    }
+  },
+  
+  /**
+   * Method: drawStore
+   * clears the grid and redraws the store.  Does not draw the column headers,
+   * that is handled by the render() method
+   */
+  drawStore: function() {
+    var useHeaders = this.row.useHeaders(), 
+        blank;
+    this.domObj.resize();
+    this.gridTableBody.empty();
+    if (useHeaders) {
+      this.rowTableBody.empty();
+    }
+    this.store.each(function(record,index) {
+      this.store.index = index;
+      this.drawRow(record, index);
+    }, this);
+    if (useHeaders) {
+      blank = new Element('tr', {
+        styles: { height: 1000 }
+      });
+      blank.adopt(new Element('th', {
+        'class':'jxGridRowHead', 
+        html: '&nbsp'
+      }));
+      this.rowTableBody.adopt(blank);
+    }
+  },
+  
+  /**
+   * Method: drawRow
+   * this method does the heavy lifting of drawing a single record into the
+   * grid
+   *
+   * Parameters:
+   * record - {Jx.Record} the record to render
+   * index - {Integer} the row index of the record in the store
+   * position - {String} 'top' or 'bottom' (default 'bottom') position to put
+   *     the new row in the grid.
+   */
+  drawRow: function(record, index, position) {
+    var columns = this.columns,
+        body = this.gridTableBody,
+        row = this.row,
+        store = this.store,
+        rowHeaders = row.useHeaders(),
+        autoRowHeight = row.options.rowHeight == 'auto',
+        rowBody = this.rowTableBody,
+        rowHeaderColumn,
+        rowHeaderColumnIndex,
+        renderer,
+        formatter, 
+        getData,
+        tr,
+        th,
+        text = index + 1,
+        rh;
+    if (!$defined(position) || !['top','bottom','replace'].contains(position)) {
+      position = 'bottom';
+    }
+    tr = row.getGridRowElement(index, '');
+    if (position == 'replace' && index < body.childNodes.length) {
+      tr.inject(body.childNodes[index], 'after');
+      body.childNodes[index].dispose();
+    } else {
+      tr.inject(body, position);
+    }
+    columns.getRow(tr, record);
+    if (rowHeaders) {
+      if (row.options.headerColumn) {
+        rowHeaderColumn = columns.getByName(row.options.headerColumn);
+        renderer = rowHeaderColumn.options.renderer;
+        if (!renderer.domInsert) {
+          formatter = rowHeaderColumn.options.formatter;
+          rowHeaderColumnIndex = columns.columns.indexOf(rowHeaderColumn);
+          getData = function(record) {
+            var data = {},
+                text = '';
+            if (renderer.options.textTemplate) {
+              text = store.fillTemplate(null, renderer.options.textTemplate, renderer.columnsNeeded);
+            } else {
+              text = record.data.get(rowHeaderColumn.name);
+            }
+            data['col'+rowHeaderColumnIndex] = text;
+            return data;
+          };
+          text = rowHeaderColumn.getTemplate(rowHeaderColumnIndex).substitute(getData(record));
+        } else {
+          text = '';
+        }
+      }
+      th = row.getRowHeaderCell(text);
+      if (row.options.headerColumn && renderer.domInsert) {
+        th.adopt(rowHeaderColumn.getHTML());
+      }
+      rh = new Element('tr').adopt(th);
+      if (position == 'replace' && index < rowBody.childNodes.length) {
+        rh.inject(rowBody.childNodes[index], 'after');
+        rowBody.childNodes[index].dispose();
+      } else {
+        rh.inject(rowBody, position);
+      }
+      if (autoRowHeight) {
+        // th.setBorderBoxSize({height: tr.childNodes[0].getBorderBoxSize().height});
+        rh.setBorderBoxSize({height: tr.getBorderBoxSize().height});
+      }
+    }
+    this.fireEvent('gridDrawRow', [index, record]);
+  },
+  
+  /**
+   * Method: clickColumnHeader
+   * handle clicks on the column header
+   */
+  clickColumnHeader: function(e) {
+    var target = e.target;
+    if (target.getParent('thead')) {
+      target = target.tagName == 'TH' ? target : target.getParent('th');
+      this.fireEvent('gridColumnClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveColumnHeader
+   * handle the mouse moving over the column header
+   */
+  moveColumnHeader: function(e) {
+    var target = e.target;
+    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridColHead');
+    if (target) {
+      if (this.hoverColumn != target) {
+        if (this.hoverColumn) {
+          this.fireEvent('gridColumnLeave', this.hoverColumn);
+        }
+        if (!target.hasClass('jxGridColHead')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverColumn = target;
+          this.fireEvent('gridColumnEnter', target);
+        }
+      }
+    }
+  },
+
+  /**
+   * Method: clickRowHeader
+   * handle clicks on the row header
+   */
+  clickRowHeader: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TH' ? target : target.getParent('th');
+      this.fireEvent('gridRowClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveRowHeader
+   * handle the mouse moving over the row header
+   */
+  moveRowHeader: function(e) {
+    var target = e.target;
+    target = target.tagName == 'TH' ? target : target.getParent('th.jxGridRowHead');
+    if (target) {
+      if (this.hoverRow != target) {
+        if (this.hoverRow) {
+          this.fireEvent('gridRowLeave', this.hoverRow);
+        }
+        if (!target.hasClass('jxGridRowHead')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverRow = target;
+          this.fireEvent('gridRowEnter', target);
+        }
+      }
+    }
+  },
+  
+  /**
+   * Method: clickCell
+   * handle clicks on cells in the grid
+   */
+  clickCell: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TD' ? target : target.getParent('td');
+      this.fireEvent('gridCellClick', target);
+    }
+  },
+  
+  /**
+   * Method: dblclickCell
+   * handle doubleclicks on cells in the grid
+   */
+  dblclickCell: function(e) {
+    var target = e.target;
+    if (target.getParent('tbody')) {
+      target = target.tagName == 'TD' ? target : target.getParent('td');
+      this.fireEvent('gridCellDblClick', target);
+    }
+  },
+  
+  /**
+   * Method: moveCell
+   * handle the mouse moving over cells in the grid
+   */
+  moveCell: function(e) {
+    var target = e.target,
+        data,
+        body,
+        row,
+        index,
+        column;
+    target = target.tagName == 'TD' ? target : target.getParent('td.jxGridCell');
+    if (target) {
+      if (this.hoverCell != target) {
+        if (this.hoverCell) {
+          this.fireEvent('gridCellLeave', this.hoverCell);
+        }
+        if (!target.hasClass('jxGridCell')) {
+          this.leaveGrid(e);
+        } else {
+          this.hoverCell = target;
+          this.getCellData(target);
+          this.fireEvent('gridCellEnter', target);
+        }
+      }
+    }
+  },
+  
+  getCellData: function(cell) {
+    var data = null,
+        index,
+        column,
+        row;
+    if (!cell.hasClass('jxGridCell')) {
+      cell = cell.getParent('td.jxGridCell');
+    }
+    if (cell) {
+      body = this.gridTableBody;
+      row = body.getChildren().indexOf(cell.getParent('tr'));
+      this.columns.columns.some(function(col,idx){
+        if (cell.hasClass('jxGridCol'+idx)) {
+          index = idx;
+          column = col;
+          return true;
+        }
+        return false;
+      });
+      data = {
+        row: row,
+        column: column,
+        index: index
+      };
+      cell.store('jxCellData', data);
+    }
+    return data;
+  },
+  
+  /**
+   * Method: leaveGrid
+   * handle the mouse leaving the grid
+   */
+  leaveGrid: function(e) {
+    this.hoverCell = null;
+    this.fireEvent('gridMouseLeave');
+  },
+  
+  /**
+   * Method: changeText
+   * rerender the grid when the language changes
+   */
+  changeText : function(lang) {
+      this.parent();
+      this.render();
+  },
+  
+  /**
+   * Method: addEvent
+   * override default addEvent to also trigger wanting the event
+   * which will then cause the underlying events to be registered
+   */
+  addEvent: function(name, fn) {
+    this.wantEvent(name);
+    this.parent(name, fn);
+  }
+});
+/*
+---
+
+name: Jx.Grid.Renderer
+
+description: Base class for all renderers. Used to create the contents of column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid
+
+provides: [Jx.Grid.Renderer]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer
+ * This is the base class and namespace for all grid renderers.
+ * 
+ * Extends: <Jx.Widget>
+ * We extended Jx.Widget to take advantage of templating support.
+ */
+Jx.Grid.Renderer = new Class({
+  
+  Family: 'Jx.Grid.Renderer',
+  Extends: Jx.Widget,
+  
+  parameters: ['options'],
+  
+  options: {
+    deferRender: true,
+    /**
+     * Option: template
+     * The template for rendering this cell. Will be processed as per
+     * the Jx.Widget standard.
+     */
+    template: '<span class="jxGridCellContent"></span>'
+  },
+    /**
+     * APIProperty: attached
+     * tells whether this renderer is used in attached mode
+     * or not. Should be set by renderers that get a reference to
+     * the store.
+     */
+  attached: null,
+  
+  /**
+   * Property: domInsert
+   * boolean, indicates if the renderer needs to insert a DOM element
+   * instead of just outputing some templated HTML.  Renderers that
+   * do use domInsert will be slower.
+   */
+  domInsert: false,
+
+  classes: $H({
+    domObj: 'jxGridCellContent'
+  }),
+
+  column: null,
+
+  init: function () {
+    this.parent();
+    this.attached = false;
+  },
+  
+  render: function () {
+    this.parent();
+  },
+  
+  setColumn: function (column) {
+    if (column instanceof Jx.Column) {
+      this.column = column;
+    }
+  }
+  
+});/*
+---
+
+name: Jx.Grid.Renderer.Text
+
+description: Renders data as straight text.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+
+provides: [Jx.Grid.Renderer.Text]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.Text
+ * This is the default renderer for grid cells. It works the same as the
+ * original column implementation. It needs a store, a field name, and an
+ * optional formatter as well as other options.
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Text = new Class({
+
+  Family: 'Jx.Grid.Renderer.Text',
+  Extends: Jx.Grid.Renderer,
+
+  options: {
+        /**
+         * Option: formatter
+         * an instance of <Jx.Formatter> or one of its subclasses which
+         * will be used to format the data in this column. It can also be
+         * an object containing the name (This should be the part after
+         * Jx.Formatter in the class name. For instance, to get a currency
+         * formatter, specify 'Currency' as the name.) and options for the
+         * needed formatter (see individual formatters for options).
+         * (code)
+         * {
+         *    name: 'formatter name',
+         *    options: {}
+         * }
+         * (end)
+         */
+        formatter: null,
+        /**
+         * Option: textTemplate
+         * Will be used for creating the text that goes iside the template. Use
+         * placeholders for indicating the field(s). You can add as much text
+         * as you want. for example, if you wanted to display someone's full
+         * name that is brokem up in the model with first and last names you
+         * can write a template like '{lastName}, {firstName}' and as long as
+         * the text between { and } are field names in the store they will be
+         * substituted properly.
+         */
+        textTemplate: null,
+        /**
+         * Option: css
+         * A string or function to use in adding classes to the text
+         */
+        css: null
+  },
+
+  store: null,
+
+  columnsNeeded: null,
+
+  init: function () {
+      this.parent();
+      var options = this.options,
+          t;
+      //check the formatter
+      if ($defined(options.formatter) &&
+          !(options.formatter instanceof Jx.Formatter)) {
+          t = Jx.type(options.formatter);
+          if (t === 'object') {
+              // allow users to leave the options object blank
+              if(!$defined(options.formatter.options)) {
+                  options.formatter.options = {};
+              }
+              options.formatter = new Jx.Formatter[options.formatter.name](
+                      options.formatter.options);
+          }
+      }
+  },
+
+  setColumn: function (column) {
+    this.parent();
+
+    this.store = column.grid.getStore();
+    this.attached = true;
+
+    if ($defined(this.options.textTemplate)) {
+      this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+    }
+  },
+
+  render: function () {
+    this.parent();
+
+    var text = '';
+    if ($defined(this.options.textTemplate)) {
+        if (!$defined(this.columnsNeeded) || (Jx.type(this.columnsNeeded) === 'array' && this.columnsNeeded.length === 0)) {
+            this.columnsNeeded = this.store.parseTemplate(this.options.textTemplate);
+        }
+        text = this.store.fillTemplate(null,this.options.textTemplate,this.columnsNeeded);
+    }
+    if ($defined(this.options.formatter)) {
+        text = this.options.formatter.format(text);
+    }
+
+    this.domObj.set('html',text);
+
+    if ($defined(this.options.css) && Jx.type(this.options.css) === 'function') {
+      this.domObj.addClass(this.options.css.run(text));
+    } else if ($defined(this.options.css) && Jx.type(this.options.css) === 'string'){
+      this.domObj.addClass(this.options.css);
+    }
+
+  }
+
+});/*
+---
+
+name: Jx.Grid.Renderer.Checkbox
+
+description: Renders a checkbox in a column. Can be connected to a store column or as a standalone check column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+ - Jx.Field.Checkbox
+
+provides: [Jx.Grid.Renderer.Checkbox]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.CheckBox
+ * Renders a checkbox into the cell. Allows options for connecting the cell
+ * to a model field and propogating changes back to the store.
+ * 
+ * Extends: <Jx.Grid.Renderer>
+ * 
+ */
+Jx.Grid.Renderer.Checkbox = new Class({
+  
+  Family: 'Jx.Grid.Renderer.Checkbox',
+  Extends: Jx.Grid.Renderer,
+  
+  Binds: ['onBlur','onChange'],
+  
+  options: {
+    useStore: false,
+    field: null,
+    updateStore: false,
+    checkboxOptions: {
+      template : '<input class="jxInputContainer jxInputCheck" type="checkbox" name="{name}"/>',
+      name: ''
+    }
+  },
+  
+  domInsert: true,
+  
+  init: function () {
+    this.parent();
+  },
+  
+  render: function () {
+    this.parent();
+    var checkbox = new Jx.Field.Checkbox(this.options.checkboxOptions);
+    this.domObj.adopt(document.id(checkbox));
+    
+    if (this.options.useStore) {
+      //set initial state
+      checkbox.setValue(this.store.get(this.options.field));
+    }
+    
+    //hook up change and blur events to change store field
+    checkbox.addEvents({
+      'blur': this.onBlur,
+      'change': this.onChange
+    });
+  },
+  
+  setColumn: function (column) {
+    this.column = column;
+    
+    if (this.options.useStore) {
+      this.store = this.column.grid.getStore();
+      this.attached = true;
+    }
+  },
+  
+  onBlur: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.column.grid.fireEvent('checkBlur',[this.column, field]);
+  },
+  
+  onChange: function (field) {
+    if (this.options.updateStore) {
+      this.updateStore(field);
+    }
+    this.fireEvent('change',[this.column, field]);
+  },
+  
+  updateStore: function (field) {
+    var newValue = field.getValue();
+    
+    var data = document.id(field).getParent().retrieve('jxCellData');
+    var row = data.row;
+    
+    if (this.store.get(this.options.field, row) !== newValue) {
+      this.store.set(this.options.field, newValue, row);
+    }
+  }
+  
+  
+});/*
+---
+
+name: Jx.Grid.Renderer.Button
+
+description: "Renders one or more buttons in a single column.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Grid.Renderer
+ - Jx.Button
+
+
+provides: [Jx.Grid.Renderer.Button]
+
+...
+ */
+/**
+ * Class: Jx.Grid.Renderer.Button
+ * Renders a <Jx.Button> into the cell. You can add s many buttons as you'd like per column by passing button configs
+ * in as an array option to options.buttonOptions
+ *
+ * Extends: <Jx.Grid.Renderer>
+ *
+ */
+Jx.Grid.Renderer.Button = new Class({
+
+    Family: 'Jx.Grid.Renderer.Button',
+    Extends: Jx.Grid.Renderer,
+
+    Binds: [],
+
+    options: {
+        template: '<span class="buttons"></span>',
+        /**
+         * Option: buttonOptions
+         * an array of option configurations for <Jx.Button>
+         */
+        buttonOptions: null
+    },
+    
+    domInsert: true,
+
+    classes:  $H({
+        domObj: 'buttons'
+    }),
+
+    init: function () {
+        this.parent();
+    },
+
+    render: function () {
+        this.parent();
+
+        $A(this.options.buttonOptions).each(function(opts){
+            var button = new Jx.Button(opts);
+            this.domObj.grab(document.id(button));
+        },this);
+
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid.Selector
+
+description: Allows selecting rows, columns, and cells in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Selector]
+
+...
+ */
+// $Id: grid.selector.js 1003 2010-12-17 20:58:01Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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({
+
+    Family: 'Jx.Plugin.Grid.Selector',
+    Extends : Jx.Plugin,
+    
+    name: 'Selector',
+
+    Binds: ['select','checkSelection','checkAll','afterGridRender','onCellClick', 'sort', 'updateCheckColumn', 'updateSelectedRows'],
+
+    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,
+        /**
+         * Option: multiple
+         * Allow multiple selections
+         */
+        multiple: false,
+        /**
+         * Option: useCheckColumn
+         * Whether to use a check box column as the row header or as the
+         * first column in the grid and use it for manipulating selections.
+         */
+        useCheckColumn: false,
+        /**
+         * Option: checkAsHeader
+         * Determines if the check column is the header of the rows
+         */
+        checkAsHeader: false,
+        /**
+         * Option: sortableColumn
+         * Determines if the check column is sortable
+         */
+        sortableColumn: false
+    },
+    
+    domInsert: true,
+    
+    /**
+     * Property: selected
+     * Holds arrays of selected rows and/or columns and their headers
+     */
+    selected: null,
+
+    /**
+     * 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.selected = $H({
+            cells: [],
+            columns: [],
+            rows: [],
+            rowHeads: [],
+            columnHeads: []
+        });
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * Parameters:
+     * grid - The instance of Jx.Grid to attach to
+     */
+    attach: function (grid) {
+        if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+            return;
+        }
+        this.parent(grid);
+        var options = this.options,
+            template;
+        this.grid = grid;
+        
+        this.grid.addEvent('gridSortFinished', this.updateSelectedRows);
+        
+        //setup check column if needed
+        if (options.useCheckColumn) {
+          grid.addEvent('gridDrawRow', this.updateCheckColumn);
+          template = '<span class="jxGridCellContent">';
+          if (options.multiple) {
+            template += '<span class="jxInputContainer jxInputContainerCheck"><input class="jxInputCheck" type="checkbox" name="checkAll" id="checkAll"/></span>';
+          } else {
+            template += '</span>';
+          }
+
+          template += "</span>";
+
+          this.checkColumn = new Jx.Column({
+            template: template,
+            renderMode: 'fixed',
+            width: 20,
+            renderer: null,
+            name: 'selection',
+            isSortable: options.sortableColumn || false,
+            sort: options.sortableColumn ? this.sort : null
+          }, grid);
+          this.checkColumn.options.renderer = this;
+          grid.columns.columns.reverse();
+          grid.columns.columns.push(this.checkColumn);
+          grid.columns.columns.reverse();
+
+          if (options.checkAsHeader) {
+              this.oldHeaderColumn = grid.row.options.headerColumn;
+              grid.row.options.useHeaders = true;
+              grid.row.options.headerColumn = 'selection';
+
+              if (options.multiple) {
+                  grid.addEvent('doneCreateGrid', this.afterGridRender);
+              }
+          }
+          //attach event to header
+          if (options.multiple) {
+              document.id(this.checkColumn).getElement('input').addEvents({
+                  'change': this.checkAll
+              });
+          }
+        } else {
+          grid.addEvent('gridCellClick', this.onCellClick);
+        }
+    },
+    
+    /**
+     * Method: render
+     * required for the renderer interface
+     */
+    render: function() {
+      this.domObj = new Element('span', {
+        'class': 'jxGridCellContent'
+      });
+      new Element('input', {
+        'class': 'jxGridSelector',
+        type: 'checkbox',
+        events: {
+          change: this.checkSelection
+        }
+      }).inject(this.domObj);
+    },
+    
+    /**
+     * Method: toElement
+     * required for the Renderer interface
+     */
+    toElement: function() {
+      return this.domObj;
+    },
+    
+    /**
+     * Method: updateCheckColumn
+     * check to see if a row needs to have its checkbox updated after its been drawn
+     *
+     * Parameters:
+     * index - {Integer} the row that was just rendered
+     * record - {<Jx.Record>} the record that was rendered into that row
+     */
+    updateCheckColumn: function(index, record) {
+      var state = this.selected.get('rows').contains(index),
+          r = this.grid.gridTableBody.rows,
+          tr = document.id((index >= 0 && index < r.length) ? r[index] : null);
+      
+      if (tr) {
+        tr.store('jxRowData', {row: index});
+        if (state) {
+          tr.addClass('jxGridRowSelected');
+        } else {
+          tr.removeClass('jxGridRowSelected');
+        }
+        this.setCheckField(index, state);
+      }
+    },
+
+    /**
+     * Method: afterGridRender
+     */
+    afterGridRender: function () {
+        if (this.options.checkAsHeader) {
+            var chkCol = document.id(this.checkColumn).clone();
+            chkCol.getElement('input').addEvent('change',this.checkAll);
+            this.grid.rowColContainer.adopt(chkCol);
+        }
+        this.grid.removeEvent('doneCreateGrid',this.afterGridRender);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        var grid = this.grid,
+            options = this.options,
+            col;
+        if (grid) {
+            grid.gridTableBody.removeEvents({
+              click: this.onCellClick
+            });
+            if (this.checkColumn) {
+                grid.columns.columns.erase(this.checkColumn);
+                this.checkColumn.destroy();
+                this.checkColumn = null;
+            }
+            if (options.useCheckColumn) {
+                grid.removeEvent('gridDrawRow', this.updateCheckColumn);
+                if (options.checkAsHeader) {
+                    grid.row.options.headerColumn = this.oldHeaderColumn;
+                }
+            }
+        }
+        this.grid.removeEvent('gridSortFinished', this.updateSelectedRows);
+        
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning selections on.
+     *
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'column', or 'row'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning selections off.
+     *
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'column', or 'row'
+     */
+    deactivate: function (opt) {
+        var gridTableRows = this.grid.gridTableBody.rows,
+            selected = this.selected,
+            i;
+        this.options[opt] = false;
+        if (opt === 'cell') {
+            selected.get('cells').each(function(cell) {
+              cell.removeClass('jxGridCellSelected');
+            });
+            selected.set('cells',[]);
+        } else if (opt === 'row') {
+          this.getSelectedRows().each(function(row){
+            idx = row.retrieve('jxRowData').row;
+            row.removeClass('jxGridRowSelected');
+            this.setCheckField(idx,false);
+          }, this);
+          selected.set('rows',[]);
+          selected.get('rowHeads').each(function(rowHead){
+            rowHead.removeClass('jxGridRowHeaderSelected');
+          });
+          selected.set('rowHeads',[]);
+        } else {
+            selected.get('columns').each(function(column){
+                for (i = 0; i < gridTableRows.length; i++) {
+                    gridTableRows[i].cells[column].removeClass('jxGridColumnSelected');
+                }
+            });
+            selected.set('columns',[]);
+
+            selected.get('columnHeads').each(function(rowHead){
+            rowHead.removeClass('jxGridColumnHeaderSelected');
+          },this);
+          selected.set('columnHeads',[]);
+        }
+    },
+    
+    /**
+     * Method: onCellClick
+     * dispatch clicking on a table cell
+     */
+    onCellClick: function(cell) {
+        if (cell) {
+            this.select(cell);
+        }
+    },
+    
+    /**
+     * Method: select
+     * dispatches the grid click to the various selection methods
+     */
+    select : function (cell) {
+        var data = cell.retrieve('jxCellData'),
+            options = this.options,
+            col;
+
+        if (options.cell && $defined(data.row) && $defined(data.index)) {
+          this.selectCell(cell);
+        }
+        
+        if (options.row && $defined(data.row)) {
+            this.selectRow(data.row);
+        }
+
+        if (options.column && $defined(data.index)) {
+            if (this.grid.row.useHeaders()) {
+                this.selectColumn(data.index - 1);
+            } else {
+                this.selectColumn(data.index);
+            }
+        }
+    },
+    
+    /**
+     * Method: selectCell
+     * select a cell
+     *
+     * Parameters: 
+     * cell - {DOMElement} the cell element to select
+     */
+    selectCell: function(cell) {
+        if (!this.options.cell) { return; }
+        var cells = this.selected.get('cells');
+        if (cell.hasClass('jxGridCellSelected')) {
+          cell.removeClass('jxGridCellSelected');
+          cells.erase(cell);
+          this.fireEvent('unselectCell', cell);
+        } else {
+          cell.addClass('jxGridCellSelected');
+          cells.push(cell);
+          this.fireEvent('selectCell', cell);
+        }
+    },
+    
+    updateSelectedRows: function() {
+      if (!this.options.row) { return; }
+      var options = this.options,
+          r = this.grid.gridTableBody.rows,
+          rows = [];
+          
+      for (var i=0; i<r.length; i++) {
+        if (r[i].hasClass('jxGridRowSelected')) {
+          rows.push(i);
+        }
+      }
+      this.selected.set('rows', rows);
+    },
+    
+    /**
+     * Method: selectRow
+     * Select a row and apply the jxGridRowSelected style to it.
+     *
+     * Parameters:
+     * row - {Integer} the row to select
+     */
+    selectRow: function (row, silently) {
+        if (!this.options.row) { return; }
+        var options = this.options,
+            r = this.grid.gridTableBody.rows,
+            tr = document.id((row >= 0 && row < r.length) ? r[row] : null),
+            rows = this.selected.get('rows'),
+            silently = $defined(silently) ? silently : false;
+        if (tr) {
+            if (tr.hasClass('jxGridRowSelected')) {
+                tr.removeClass('jxGridRowSelected');
+                this.setCheckField(row, false);
+                if (options.multiple && options.useCheckColumn) {
+                    if (options.checkAsHeader) {
+                        document.id(this.grid.rowColContainer).getElement('input').removeProperty('checked');
+                    } else {
+                        document.id(this.checkColumn).getElement('input').removeProperty('checked');
+                    }
+                }
+                //search array and remove this item
+                rows.erase(row);
+                if (!silently) {
+                  this.fireEvent('unselectRow', row);
+                }
+            } else {
+                tr.store('jxRowData', {row: row});
+                rows.push(row);
+                tr.addClass('jxGridRowSelected');
+                this.setCheckField(row, true);
+                if (!silently) {
+                  this.fireEvent('selectRow', row);
+                }
+            }
+
+            if (!this.options.multiple) {
+                var unselected = [];
+                this.getSelectedRows().each(function(row) {
+                  var idx;
+                  if (row !== tr) {
+                    idx = row.retrieve('jxRowData').row;
+                    row.removeClass('jxGridRowSelected');
+                    this.setCheckField(idx,false);
+                    rows.erase(row);
+                    unselected.push(idx);
+                    if (!silently) {
+                      this.fireEvent('unselectRow', row);
+                    }
+                  }
+                  
+                }, this);
+                if (unselected.length && !silently) {
+                  this.fireEvent('unselectRows', [unselected]);
+                }
+            }
+        }
+        this.selectRowHeader(row);
+    },
+
+    /**
+     * Method: setCheckField
+     */
+    setCheckField: function (row, checked) {
+        var grid = this.grid,
+            options = this.options,
+            check,
+            col,
+            cell;
+        if (options.useCheckColumn) {
+            if (options.checkAsHeader) {
+              cell = document.id(grid.rowTableBody.rows[row].cells[0]);
+            } else {
+              col = grid.columns.getIndexFromGrid(this.checkColumn.name);
+              cell = document.id(grid.gridTableBody.rows[row].cells[col]);
+            }
+            check = cell.getElement('.jxGridSelector')
+            check.set('checked', checked);
+        }
+    },
+    /**
+     * Method: selectRowHeader
+     * Apply the jxGridRowHeaderSelected 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 rows = this.grid.rowTableBody.rows,
+            cell = document.id((row >= 0 && row < rows.length) ? 
+                              rows[row].cells[0] : null),
+            cells;
+
+        if (!cell) {
+            return;
+        }
+        cells = this.selected.get('rowHeads');
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridRowHeaderSelected');
+            cells.erase(cell);
+        } else {
+          cell.addClass('jxGridRowHeaderSelected');
+          cells.push(cell);
+        }
+
+        if (!this.options.multiple) {
+          cells.each(function(c){
+            if (c !== cell) {
+              c.removeClass('jxGridRowHeaderSelected');
+              cells.erase(c);
+            }
+          },this);
+        }
+
+    },
+    /**
+     * Method: selectColumn
+     * Select a column.
+     * This deselects a previously selected column.
+     *
+     * Parameters:
+     * col - {Integer} the column to select
+     */
+    selectColumn: function (col) {
+        var gridTable = this.grid.gridTableBody,
+            cols = this.selected.get('columns'),
+            m = '',
+            i;
+        if (col >= 0 && col < gridTable.rows[0].cells.length) {
+            if (cols.contains(col)) {
+                //deselect
+                m = 'removeClass';
+                cols.erase(col);
+                this.fireEvent('unselectColumn', col);
+            } else {
+                //select
+                m = 'addClass';
+                cols.push(col);
+                this.fireEvent('selectColumn', col);
+            }
+            for (i = 0; i < gridTable.rows.length; i++) {
+                gridTable.rows[i].cells[col][m]('jxGridColumnSelected');
+            }
+
+            if (!this.options.multiple) {
+                cols.each(function(c){
+                  if (c !== col) {
+                      for (i = 0; i < gridTable.rows.length; i++) {
+                          gridTable.rows[i].cells[c].removeClass('jxGridColumnSelected');
+                      }
+                      cols.erase(c);
+                      this.fireEvent('unselectColumn', c);
+                  }
+                }, this);
+            }
+            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) {
+        var rows = this.grid.colTableBody;
+        if (rows.length === 0 || !this.grid.row.useHeaders()) {
+            return;
+        }
+
+        var cell = (col >= 0 && col < rows[0].cells.length) ?
+            rows[0].cells[col] : null;
+
+        if (cell === null) {
+            return;
+        }
+
+        cell = document.id(cell);
+        cells = this.selected.get('columnHeads');
+
+        if (cells.contains(cell)) {
+            cell.removeClass('jxGridColumnHeaderSelected');
+            cells.erase(cell);
+        } else {
+          cell.addClass('jxGridColumnHeaderSelected');
+          cells.push(cell);
+        }
+
+        if (!this.options.multiple) {
+          cells.each(function(c){
+            if (c !== cell) {
+              c.removeClass('jxGridColumnHeaderSelected');
+              cells.erase(c);
+            }
+          });
+        }
+    },
+    /**
+     * Method: checkSelection
+     * Checks whether a row's check box is/isn't checked and modifies the
+     * selection appropriately.
+     *
+     * Parameters:
+     * column - <Jx.Column> that created the checkbox
+     * field - <Jx.Field.Checkbox> instance that was checked/unchecked
+     * created the checkbox
+     */
+    checkSelection: function (event) {
+      var cell =  event.target.getParent('tr'),
+          row;
+      if (cell) {
+        row = cell.getParent().getChildren().indexOf(cell);
+        this.selectRow(row);
+      }
+    },
+    /**
+     * Method: checkAll
+     * Checks all checkboxes in the column the selector inserted.
+     */
+    checkAll: function () {
+        var grid = this.grid,
+            col,
+            rows,
+            selection = [],
+            checked = this.options.checkAsHeader ? 
+                          grid.rowColContainer.getElement('input').get('checked') :
+                          this.checkColumn.domObj.getElement('input').get('checked'),
+            event = checked ? 'selectRows' : 'unselectRows';
+
+        if (this.options.checkAsHeader) {
+            col = 0;
+            rows = grid.rowTableBody.rows;
+        } else {
+            col = grid.columns.getIndexFromGrid(this.checkColumn.name);
+            rows = grid.gridTableBody.rows;
+        }
+
+        $A(rows).each(function(row, idx) {
+            var check = row.cells[col].getElement('input');
+            if ($defined(check)) {
+                var rowChecked = check.get('checked');
+                if (rowChecked !== checked) {
+                    this.selectRow(idx, true);
+                    selection.push(idx);
+                }
+            }
+        }, this);
+        
+        this.fireEvent(event, [selection]);
+    },
+    
+    sort: function(dir) {
+      var grid = this.grid,
+          store = grid.store,
+          data = store.data,
+          gridTableBody= grid.gridTableBody,
+          gridParent = gridTableBody.getParent(),
+          useHeaders = grid.row.useHeaders(),
+          rowTableBody = grid.rowTableBody,
+          rowParent = rowTableBody.getParent(),
+          selected = this.getSelectedRows();
+      
+      // sorting only works for rows and when more than zero are selected
+      // in fact it is probably only useful if multiple selections are also enabled
+      // but that is not a hard rule for this method
+      if (!this.options.row || selected.length == 0) {
+        console.log('not sorting by selection, nothing to sort');
+        return;
+      }
+      
+      store.each(function(record, index) {
+        record.dom = {
+          cell: gridTableBody.childNodes[index],
+          row: useHeaders ? rowTableBody.childNodes[index] : null
+        };
+      });
+
+      gridTableBody.dispose();
+      if (useHeaders) {
+        rowTableBody.dispose();
+      }
+      selected.sort(function(a,b) {
+        return a.retrieve('jxRowData').row - b.retrieve('jxRowData').row;
+      }).each(function(row) {
+        console.log('moving row ' + row.retrieve('jxRowData').row + ' to beginning of array');
+        data.unshift(data.splice(row.retrieve('jxRowData').row,1)[0]);
+      });
+
+      if (dir == 'desc') {
+        data.reverse();
+      }
+
+      store.each(function(record, index) {
+        record.dom.cell.inject(gridTableBody);
+        record.dom.cell.store('jxRowData', {row: index});
+        if (useHeaders) {
+          record.dom.row.inject(rowTableBody);
+        }
+      });
+
+      if (gridParent) {
+        gridParent.adopt(gridTableBody);
+      }
+      if (useHeaders && rowParent) {
+        rowParent.adopt(rowTableBody);
+      }
+    },
+    
+    getSelectedRows: function() {
+      var rows = [],
+          selected = this.selected.get('rows'),
+          r = this.grid.gridTableBody.rows;
+      selected.each(function(row) {
+        var tr = document.id((row >= 0 && row < r.length) ? r[row] : null);
+        if (tr) {
+          rows.push(tr);
+        }
+      });
+      return rows;
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Grid.Prelighter
+
+description: Highlights rows, columns, cells, and headers in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Prelighter]
+
+...
+ */
+// $Id: grid.prelighter.js 981 2010-09-13 12:18:35Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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,
+    
+    name: 'Prelighter',
+    
+    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
+    },
+    /**
+     * 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.lighton = this.lighton.bind(this);
+        this.bound.lightoff = this.lightoff.bind(this);
+        this.bound.mouseleave = this.mouseleave.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.parent(grid);
+        this.grid = grid;
+        // this.grid.wantEvent('gridCellEnter');
+        // this.grid.wantEvent('gridCellLeave');
+        // this.grid.wantEvent('gridRowEnter');
+        // this.grid.wantEvent('gridRowLeave');
+        // this.grid.wantEvent('gridColumnEnter');
+        // this.grid.wantEvent('gridColumnLeave');
+        // this.grid.wantEvent('gridMouseLeave');
+        
+        this.grid.addEvent('gridCellEnter', this.bound.lighton);
+        this.grid.addEvent('gridCellLeave', this.bound.lightoff);
+        this.grid.addEvent('gridRowEnter', this.bound.lighton);
+        this.grid.addEvent('gridRowLeave', this.bound.lightoff);
+        this.grid.addEvent('gridColumnEnter', this.bound.lighton);
+        this.grid.addEvent('gridColumnLeave', this.bound.lightoff);
+        this.grid.addEvent('gridMouseLeave', this.bound.mouseleave);
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.grid) {
+            this.grid.removeEvent('gridCellEnter', this.bound.lighton);
+            this.grid.removeEvent('gridCellLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridRowEnter', this.bound.lighton);
+            this.grid.removeEvent('gridRowLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridColumnEnter', this.bound.lighton);
+            this.grid.removeEvent('gridColumnLeave', this.bound.lightoff);
+            this.grid.removeEvent('gridMouseLeave', this.bound.mouseleave);
+        }
+        this.grid = null;
+    },
+    /**
+     * APIMethod: activate
+     * Allows programatic access to turning prelighting on.
+     * 
+     * Parameters:
+     * opt - the option to turn on. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    activate: function (opt) {
+        this.options[opt] = true;
+    },
+    /**
+     * APIMethod: deactivate
+     * Allows programatic access to turning prelighting off.
+     * 
+     * Parameters:
+     * opt - the option to turn off. One of 'cell', 'row', 'rowHeader', 'column', or 'columnHeader'
+     */
+    deactivate: function (opt) {
+        this.options[opt] = false;
+    },
+    /**
+     * Method: lighton
+     */
+    lighton : function (cell) {
+        this.light(cell, true);
+
+    },
+    /**
+     * Method: lightoff
+     */
+    lightoff : function (cell) {
+        this.light(cell, false);
+
+    },
+    /**
+     * Method: light
+     * dispatches the event to the various prelight methods.
+     */
+    light: function (cell, on) {
+        var parent = cell.getParent(),
+            rowIndex = parent.getParent().getChildren().indexOf(parent),
+            colIndex = cell.getParent().getChildren().indexOf(cell);
+
+        if (this.options.cell) {
+            this.prelightCell(cell, on);
+        }
+        if (this.options.row) {
+            this.prelightRow(rowIndex, on);
+        }
+        if (this.options.column) {
+            this.prelightColumn(colIndex, on);
+        }
+        if (this.options.rowHeader) {
+            this.prelightRowHeader(rowIndex, on);
+        }
+        if (this.options.columnHeader) {
+            this.prelightColumnHeader(colIndex, on);
+        }
+    },
+
+    /**
+     * Method: prelightRowHeader
+     * apply the jxGridRowHeaderPrelight style to the header cell of a row.
+     * This removes the style from the previously pre-lit row header.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light the header cell of
+     */
+    prelightRowHeader : function (row, on) {
+        if ($defined(this.prelitRowHeader) && !on) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        } else if (on) {
+            this.prelitRowHeader = (row >= 0 && row < this.grid.rowTableBody.rows.length) ? this.grid.rowTableBody.rows[row].cells[0] : null;
+            if (this.prelitRowHeader) {
+                this.prelitRowHeader.addClass('jxGridRowHeaderPrelight');
+            }
+        }
+    },
+    /**
+     * Method: prelightColumnHeader
+     * apply the jxGridColumnHeaderPrelight style to the header cell of a column.
+     * This removes the style from the previously pre-lit column header.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light the header cell of
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumnHeader : function (col, on) {
+        if (this.grid.colTableBody.rows.length === 0) {
+            return;
+        }
+
+        if ($defined(this.prelitColumnHeader) && !on) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        } else if (on) {
+            this.prelitColumnHeader = (col >= 0 && col < this.grid.colTableBody.rows[0].cells.length) ? this.grid.colTableBody.rows[0].cells[col] : null;
+            if (this.prelitColumnHeader) {
+                this.prelitColumnHeader.addClass('jxGridColumnHeaderPrelight');
+            }
+        }
+
+    },
+    /**
+     * Method: prelightRow
+     * apply the jxGridRowPrelight style to row.
+     * This removes the style from the previously pre-lit row.
+     *
+     * Parameters:
+     * row - {Integer} the row to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightRow : function (row, on) {
+       if ($defined(this.prelitRow) && !on) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        } else if (on) {
+            this.prelitRow = (row >= 0 && row < this.grid.gridTableBody.rows.length) ? this.grid.gridTableBody.rows[row] : null;
+            if (this.prelitRow) {
+                this.prelitRow.addClass('jxGridRowPrelight');
+            }
+        }
+        this.prelightRowHeader(row, on);
+    },
+    /**
+     * Method: prelightColumn
+     * apply the jxGridColumnPrelight style to a column.
+     * This removes the style from the previously pre-lit column.
+     *
+     * Parameters:
+     * col - {Integer} the column to pre-light
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightColumn : function (col, on) {
+        if (col >= 0 && col < this.grid.gridTableBody.rows[0].cells.length) {
+            if ($defined(this.prelitColumn) && !on) {
+                for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                    this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+                }
+            } else if (on) {
+                this.prelitColumn = col;
+                for (i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                    this.grid.gridTableBody.rows[i].cells[col].addClass('jxGridColumnPrelight');
+                }
+            }
+            this.prelightColumnHeader(col, on);
+        }
+    },
+    /**
+     * Method: prelightCell
+     * apply the jxGridCellPrelight style to a cell.
+     * This removes the style from the previously pre-lit cell.
+     *
+     * Parameters:
+     * cell - the cell to lighton/off
+     * on - flag to tell if we're lighting on or off
+     */
+    prelightCell : function (cell, on) {
+        if ($defined(this.prelitCell) && !on) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        } else if (on) {
+            this.prelitCell = cell;
+            if (this.prelitCell) {
+                this.prelitCell.addClass('jxGridCellPrelight');
+            }
+        }
+    },
+    
+    mouseleave: function() {
+        //turn off all prelights when the mouse leaves the grid
+        if ($defined(this.prelitCell)) {
+            this.prelitCell.removeClass('jxGridCellPrelight');
+        }
+        if ($defined(this.prelitColumn)) {
+            for (var i = 0; i < this.grid.gridTableBody.rows.length; i++) {
+                this.grid.gridTableBody.rows[i].cells[this.prelitColumn].removeClass('jxGridColumnPrelight');
+            }
+        }
+        if ($defined(this.prelitRow)) {
+            this.prelitRow.removeClass('jxGridRowPrelight');
+        }
+        if ($defined(this.prelitColumnHeader)) {
+            this.prelitColumnHeader.removeClass('jxGridColumnHeaderPrelight');
+        }
+        if ($defined(this.prelitRowHeader)) {
+            this.prelitRowHeader.removeClass('jxGridRowHeaderPrelight');
+        }
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Grid.Sorter
+
+description: Enables column sorting in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Sorter]
+
+images:
+ - emblems.png
+...
+ */
+// $Id: grid.sorter.js 1002 2010-12-17 20:57:31Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.Sorter
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to sort the grid by a single column.
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Sorter = new Class({
+  Family: 'Jx.Plugin.Grid.Sorter',
+  Extends: Jx.Plugin,
+  name: 'Sorter',
+
+  Binds: ['sort', 'modifyHeaders'],
+
+  /**
+   * Property: current
+   * refernce to the currently sorted column
+   */
+  current: null,
+
+  /**
+   * Property: direction
+   * tell us what direction the sort is in (either 'asc' or 'desc')
+   */
+  direction: null,
+
+  options: {
+    sortableClass: 'jxColSortable',
+    ascendingClass: 'jxGridColumnSortedAsc',
+    descendingClass: 'jxGridColumnSortedDesc'
+  },
+
+  /**
+   * 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.parent(grid);
+
+    this.grid = grid;
+
+    // this.grid.wantEvent('gridColumnClick');
+    this.grid.addEvent('gridColumnClick', this.sort);
+    this.grid.addEvent('doneCreateGrid', this.modifyHeaders);
+  },
+
+  /**
+   * APIMethod: detach
+   */
+  detach: function() {
+    if (this.grid) {
+        this.grid.removeEvent('gridColumnClick', this.sort);
+    }
+    this.grid = null;
+  },
+
+  /**
+   * Method: modifyHeaders
+   */
+  modifyHeaders: function() {
+    var grid = this.grid,
+        columnTable = grid.colObj,
+        store = grid.store,
+        c = this.options.sortableClass;
+    if (grid.columns.useHeaders()) {
+      grid.columns.columns.each(function(col, index) {
+        if (!col.isHidden() && col.isSortable()) {
+          var th = columnTable.getElement('.jxGridCol'+index);
+          th.addClass(c);
+        }
+      });
+    }
+  },
+
+  /**
+   * Method: sort
+   * called when a grid header is clicked.
+   *
+   * Parameters:
+   * cell - The cell clicked
+   */
+  sort: function(el) {
+    var current = this.current,
+        grid = this.grid,
+        gridTableBody = grid.gridTableBody,
+        gridParent = gridTableBody.getParent(),
+        rowTableBody = grid.rowTableBody,
+        rowParent = rowTableBody.getParent(),
+        useHeaders = grid.row.useHeaders(),
+        store = grid.store,
+        sorter = store.getStrategy('sort'),
+        data = el.retrieve('jxCellData'),
+        dir = 'asc',
+        opt = this.options;
+    
+    if ($defined(data.column) && data.column.isSortable()){
+      if (el.hasClass(opt.ascendingClass)) {
+        el.removeClass(opt.ascendingClass).addClass(opt.descendingClass);
+        dir = 'desc';
+      } else if (el.hasClass(opt.descendingClass)) {
+        el.removeClass(opt.descendingClass).addClass(opt.ascendingClass);
+      } else {
+        el.addClass(opt.ascendingClass);
+      }
+      if (current && el != current) {
+        current.removeClass(opt.ascendingClass).removeClass(opt.descendingClass);
+      }
+      this.current = el;
+      
+      this.grid.fireEvent('gridSortStarting');
+      
+      if ($defined(data.column.options.sort) && Jx.type(data.column.options.sort) == 'function') {
+        data.column.options.sort(dir);
+      } else {
+        if (sorter) {
+          gridTableBody.dispose();
+          if (useHeaders) {
+            rowTableBody.dispose();
+          }
+          store.each(function(record, index) {
+            record.dom = {
+              cell: gridTableBody.childNodes[index],
+              row: useHeaders ? rowTableBody.childNodes[index] : null
+            };
+          });
+    
+          sorter.sort(data.column.name, null, dir);
+    
+          store.each(function(record, index) {
+            record.dom.cell.inject(gridTableBody);
+            if (useHeaders) {
+              record.dom.row.inject(rowTableBody);
+            }
+          });
+    
+          if (gridParent) {
+            gridParent.adopt(gridTableBody);
+          }
+          if (useHeaders && rowParent) {
+            rowParent.adopt(rowTableBody);
+          }
+        }
+      }
+      this.grid.fireEvent('gridSortFinished');
+    }
+  }
+});/*
+---
+
+name: Jx.Plugin.Grid.Resize
+
+description: Enables column resizing in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+
+provides: [Jx.Plugin.Grid.Resize]
+
+...
+ */
+// $Id: grid.resize.js 992 2010-10-07 19:28:37Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.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,
+    
+    name: 'Resize',
+    
+    Binds: ['createHandles','removeHandles'],
+    options: {
+        /**
+         * Option: column
+         * set to true to make column widths resizeable
+         */
+        column: false,
+        /**
+         * Option: row
+         * set to true to make row heights resizeable
+         */
+        row: false,
+        /**
+         * Option: tooltip
+         * the tooltip to display for the draggable portion of the
+         * cell header, localized with MooTools.lang.get('Jx','plugin.resize').tooltip for default
+         */
+        tooltip: ''
+    },
+    /**
+     * Property: els
+     * the DOM elements by which the rows/columns are resized.
+     */
+    els: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * Property: drags
+     * the Drag instances
+     */
+    drags: {
+      column: [],
+      row: []
+    },
+
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and connects it to the grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+          return;
+      }
+      this.parent(grid);
+      this.grid = grid;
+      if (grid.columns.useHeaders()) {
+        this.grid.addEvent('doneCreateGrid', this.createHandles);
+        this.grid.addEvent('beginCreateGrid', this.removeHandles);
+        this.createHandles();
+      }
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+      this.parent();
+      if (this.grid) {
+          this.grid.removeEvent('doneCreateGrid', this.createHandles);
+          this.grid.removeEvent('beginCreateGrid', this.removeHandles);
+      }
+      this.grid = null;
+    },
+
+    /**
+     * APIMethod: activate
+     */
+    activate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = true;
+        }
+        if (this.grid.columns.useHeaders()) {
+          this.createHandles();
+        }
+    },
+
+    /**
+     * APIMethod: deactivate
+     */
+    deactivate: function(option) {
+        if ($defined(this.options[option])) {
+          this.options[option] = false;
+        }
+        this.createHandles();
+    },
+    /**
+     * Method: removeHandles
+     * clean up any handles we created
+     */
+    removeHandles: function() {
+        ['column','row'].each(function(option) {
+          this.els[option].each(function(el) { el.dispose(); } );
+          this.els[option] = [];
+          this.drags[option].each(function(drag){ drag.detach(); });
+          this.drags[option] = [];
+        }, this);
+    },
+    /**
+     * Method: createHandles
+     * create handles that let the user drag to resize columns and rows
+     */
+    createHandles: function() {
+      var grid = this.grid,
+          store = grid.store;
+      this.removeHandles();
+      if (this.options.column && grid.columns.useHeaders()) {
+        grid.columns.columns.each(function(col, idx) {
+          if (col.isResizable() && !col.isHidden()) {
+            var colEl = grid.colObj.getElement('.jxGridCol'+idx+ ' .jxGridCellContent');
+            var el = new Element('div', {
+              'class':'jxGridColumnResize',
+              title: this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip),
+              events: {
+                dblclick: function() {
+                  // size to fit?
+                }
+              }
+            }).inject(colEl);
+            this.els.column.push(el);
+            this.drags.column.push(new Drag(el, {
+                limit: {y:[0,0]},
+                snap: 2,
+                onBeforeStart: function(el) {
+                  var l = el.getPosition(el.parentNode).x.toInt();
+                  el.setStyles({
+                    left: l,
+                    right: null
+                  });
+
+                },
+                onStart: function(el) {
+                  var l = el.getPosition(el.parentNode).x.toInt();
+                  el.setStyles({
+                    left: l,
+                    right: null
+                  });
+                },
+                onDrag: function(el) {
+                    var w = el.getPosition(el.parentNode).x.toInt();
+                    col.setWidth(w);
+                },
+                onComplete: function(el) {
+                  el.setStyle('left', null);
+                }
+            }));
+          }
+        }, this);
+      }
+      //if (this.options.row && this.grid.row.useHeaders()) {}
+    },
+    /**
+     * Method: createText
+     * respond to a language change by updating the tooltip
+     */
+    changeText: function (lang) {
+      this.parent();
+      var txt = this.options.tooltip == '' ? this.getText({set:'Jx',key:'plugin.resize',value:'tooltip'}) : this.getText(this.options.tooltip);
+      ['column','row'].each(function(option) {
+        this.els[option].each(function(el) { el.set('title',txt); } );
+      }, this);
+    }
+});/*
+---
+
+name: Jx.Plugin.Grid.Editor
+
+description: Enables inline editing in grids
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Grid
+ - More/Keyboard
+
+provides: [Jx.Plugin.Grid.Editor]
+
+images:
+ - icons.png
+...
+ */
+// $Id: grid.editor.js 981 2010-09-13 12:18:35Z pagameba $
+/**
+ * Class: Jx.Plugin.Grid.Editor
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Grid plugin to enable inline editing within a cell
+ *
+ * Original selection code from Jx.Grid's original class
+ *
+ * License:
+ * Original Copyright (c) 2008, DM Solutions Group Inc.
+ * This version Copyright (c) 2009, Conrad Barthelmes.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.Grid.Editor = new Class({
+
+    Extends : Jx.Plugin,
+    
+    name: 'Editor',
+    
+    Binds: ['activate','deactivate','changeText','onCellClick'],
+
+    options : {
+      /**
+       * Option: enabled
+       * Determines if inline editing is avaiable
+       */
+      enabled : true,
+      /**
+       * Option: blurDelay
+       * Set the time in miliseconds when the inputfield/popup shall hide. When
+       * the user refocuses the input/popup within this time, the timeout will be cleared
+       *
+       * set to 'false' if no hiding on blur is wanted
+       */
+      blurDelay : 500,
+      /**
+       * Option: popup
+       *
+       * Definitions for a PopUp to use.
+       * - use        - determines whether to use a PopUp or simply the input
+       * - useLabel   - determines whether to use labels on top of the input.
+       *                Text will be the column header
+       * - useButtons - determines whether to use Submit and Cancel Buttons
+       * - buttonLabel.submit - Text for Submit Button, uses MooTools.lang.get('Jx', 'plugin.editor').submitButton for default
+       * - buttonLabel.cancel - Text for Cancel Button, uses MooTools.lang.get('Jx', 'plugin.editor').cancelButton for default
+       */
+      popup : {
+        use           : true,
+        useLabels     : false,
+        useButtons    : true,
+        button        : {
+          submit : {
+            label : '',
+            image : 'images/accept.png'
+          },
+          cancel : {
+            label : '',
+            image : 'images/cancel.png'
+          }
+        },
+        template: '<div class="jxGridEditorPopup"><div class="jxGridEditorPopupInnerWrapper"></div></div>'
+      },
+      /**
+       * Option {boolean} validate
+       * - set to true to have all editable input fields as mandatory field
+       *   if they don't have 'mandatory:true' in their colOptions
+       */
+      validate : true,
+      /**
+       * Option: {Array} fieldOptions with objects
+       * Contains objects with options for the Jx.Field instances to show up.
+       * Default options will be added automatically if custom options are entered.
+       *
+       * Preferences:
+       *   field             - Default * for all types or the name of the column in the store (Jx.Store)
+       *   type              - Input type to show (Text, Password, Textarea, Select, Checkbox)
+       *   options           - All Jx.Field options for this column. More options depend on what type you are using.
+       *                       See Jx.Form.[yourField] for details
+       *   validatorOptions: - See Jx.Plugin.Field.Validator Options for details
+       *                       will only be used if this.options.validate is set to true
+       */
+      fieldOptions : [
+        {
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        }
+      ],
+      /**
+       * Option: {Boolean} fieldFormatted
+       * Displays the cell value also inside the input field as formatted
+       */
+      fieldFormatted : true,
+      /**
+       * Option cellChangeFx
+       * set use to false if no highlighting effect is wanted.
+       *
+       * this is just an idea how successfully changing could be highlighed for the user
+       */
+      cellChangeFx : {
+        use     : true,
+        success : '#090',
+        error   : '#F00'
+      },
+      /**
+       * Option cellOutline
+       * shows an outline style to the currently active cell to make it easier to see
+       * which cell is active
+       */
+      cellOutline : {
+        use   : true,
+        style : '2px solid #88c3e7'
+      },
+      /**
+       * Option: useKeyboard
+       * Set to false if no keyboard support is needed
+       */
+      useKeyboard : true,
+      /**
+       * Option: keys
+       * Contains the event codes for several commands that can be used when
+       * a field is active. Syntax is the same like for the Mootools Keyboard Class
+       * http://mootools.net/docs/more/Interface/Keyboard
+       */
+      keys : {
+        'ctrl+shift+enter' : 'saveNGoUp',
+        'tab'              : 'saveNGoRight',
+        'ctrl+enter'       : 'saveNGoDown',
+        'shift+tab'        : 'saveNGoLeft',
+        'enter'            : 'saveNClose',
+        'ctrl+up'          : 'cancelNGoUp',
+        'ctrl+right'       : 'cancelNGoRight',
+        'ctrl+down'        : 'cancelNGoDown',
+        'ctrl+left'        : 'cancelNGoLeft',
+        'esc'              : 'cancelNClose',
+        'up'               : 'valueIncrement',
+        'down'             : 'valueDecrement'
+      },
+      /**
+       * Option: keyboardMethods
+       *
+       * can be used to overwrite existing keyboard methods that are used inside
+       * this.options.keys - also possible to add new ones.
+       * Functions are bound to the editor plugin when using 'this'
+       *
+       * example:
+       *  keys : {
+       *    'ctrl+u' : 'cancelNGoRightNDown'
+       *  },
+       *  keyboardMethods: {
+       *    'cancelNGoRightNDown' : function(ev){
+       *      ev.preventDefault();
+       *      this.getNextCellInRow(false);
+       *      this.getNextCellInCol(false);
+       *    }
+       *  }
+       */
+      keyboardMethods : {},
+      /**
+       * Option: keypressLoop
+       * loop through the grid when pressing TAB (or some other method that uses
+       * this.getNextCellInRow() or this.getPrevCellInRow()). If set to false,
+       * the input field/popup will not start at the opposite site of the grid
+       * Defaults to true
+       */
+      keypressLoop : true,
+      /**
+       * Option: linkClickListener
+       * disables all click events on links that are formatted with Jx.Formatter.Uri
+       * - otherwise the link will open directly instead of open the input editor)
+       * - hold [ctrl] to open the link in a new tab
+       */
+      linkClickListener : true
+    },
+    classes: ['jxGridEditorPopup', 'jxGridEditorPopupInnerWrapper'],
+    /**
+     * Property: activeCell
+     *
+     * Containing Objects:
+     *   field        : Reference to the Jx.Field instance that will be created
+     *   cell         : Reference to the cell inside the table 
+     *   span         : Reference to the Dom Element inside the selected cell of the grid
+     *   oldValue     : Old value of the cell from the grid's store
+     *   newValue     : Object with <data> and <error> for better validation possibilites
+     *   timeoutId    : TimeoutId if the focus blurs the input.
+     *   data         : Reference to the cell data
+     *   fieldOptions : Reference to the field options of this column
+     */
+    activeCell : {
+      field       : null,
+      cell        : null,
+      span        : null,
+      oldValue    : null,
+      newValue    : { data: null, error: false },
+      timeoutId   : null,
+      data        : {},
+      fieldOptions: {}
+    },
+    /**
+     * Property : popup
+     *
+     * References to all contents within a popup (only 1 popup for 1 grid initialization)
+     *
+     * COMMENT: I don't know how deep we need to go into that.. innerWrapper and closeLink probably don't need
+     * own references.. I just made them here in case they are needed at some time..
+     *
+     * Containing Objects:
+     *   domObj         : Reference to the Dom Element of the popup (absolutely positioned)
+     *   innerWrapper   : Reference to the inner Wrapper inside the popup to provide relative positioning
+     *   closeIcon      : Reference to the Dom Element of a little [x] in the upper right to close it (not saving)
+     *   buttons        : References to all Jx.Buttons used inside the popup
+     *   buttons.submit : Reference to the Submit Button
+     *   buttons.cancel : Reference to the Cancel Button
+     */
+    popup : {
+      domObj       : null,
+      innerWarpper : null,
+      closeIcon    : null,
+      button       : {
+        submit : null,
+        cancel : null
+      }
+    },
+    /**
+     * Property: keyboard
+     * Instance of a Mootols Keyboard Class
+     */
+    keyboard : null,
+    /**
+     * Property keyboardMethods
+     * Editing and grid functions for keyboard functionality.
+     * Methods are defined and implemented inside this.attach() because of referencing troubles
+     */
+    keyboardMethods : {},
+    /**
+     * APIMethod: init
+     * construct a new instance of the plugin.  The plugin must be attached
+     * to a Jx.Grid instance to be useful though.
+     */
+    init: function() {
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Sets up the plugin and attaches the plugin to the grid events it
+     * will be monitoring
+     *
+     * @var {Object} grid - Instance of Class Jx.Grid
+     */
+    attach: function (grid) {
+      if (!$defined(grid) && !(grid instanceof Jx.Grid)) {
+        return;
+      }
+      this.parent(grid);
+      this.grid = grid;
+
+      //this.grid.gridTableBody.addEvent('click', this.onCellClick);
+      // this.grid.wantEvent('gridCellClick');
+      this.grid.addEvent('gridCellClick', this.onCellClick);
+
+      /*
+       * add default field options to the options in case some new options were entered
+       * to be still able to use them for the rest of the fields
+       */
+      if(this.getFieldOptionsByColName('*').field != '*') {
+        this.options.fieldOptions.unshift({
+          field   : '*',
+          type    : 'Text',
+          options : {},
+          validatorOptions: {
+            validators : [],
+            validateOnBlur: true,
+            validateOnChange : false
+          }
+        });
+      }
+
+      /**
+       * set the keyboard methods here to have a correct reference to the instance of
+       * the editor plugin
+       *
+       * @todo other names maybe? or even completely different way of handling the keyboard events?
+       * @todo more documentation than method name
+       */
+      var self = this;
+      this.keyboardMethods = {
+        saveNClose     : function(ev) {
+          if(self.activeCell.fieldOptions.type != 'Textarea' || (self.activeCell.fieldOptions.type == 'Textarea' && ev.key != 'enter')) {
+            self.deactivate();
+          }
+        },
+        saveNGoUp      : function(ev) {ev.preventDefault();self.getPrevCellInCol();},
+        saveNGoRight   : function(ev) {ev.preventDefault();self.getNextCellInRow();},
+        saveNGoDown    : function(ev) {ev.preventDefault();self.getNextCellInCol();},
+        saveNGoLeft    : function(ev) {ev.preventDefault();self.getPrevCellInRow();},
+        cancelNClose   : function(ev) {ev.preventDefault();self.deactivate(false);},
+        cancelNGoUp    : function(ev) {ev.preventDefault();self.getPrevCellInCol(false);},
+        cancelNGoRight : function(ev) {ev.preventDefault();self.getNextCellInRow(false);},
+        cancelNGoDown  : function(ev) {ev.preventDefault();self.getNextCellInCol(false);},
+        cancelNGoLeft  : function(ev) {ev.preventDefault();self.getPrevCellInRow(false);},
+        valueIncrement : function(ev) {ev.preventDefault();self.cellValueIncrement(true);},
+        valueDecrement : function(ev) {ev.preventDefault();self.cellValueIncrement(false);}
+      };
+
+      var keyboardEvents = {};
+      for(var i in this.options.keys) {
+        if($defined(this.keyboardMethods[this.options.keys[i]])) {
+          keyboardEvents[i] = this.keyboardMethods[this.options.keys[i]];
+        }else if($defined(this.options.keyboardMethods[this.options.keys[i]])){
+          keyboardEvents[i] = this.options.keyboardMethods[this.options.keys[i]].bind(self);
+        }else if(Jx.type(this.options.keys[i]) == 'function') {
+          keyboardEvents[i] = this.options.keys[i].bind(self);
+        }else{
+          $defined(console) ? console.warn("keyboard method %o not defined", this.options.keys[i]) : false;
+        }
+      }
+
+      // initalize keyboard support but do NOT activate it (this is done inside this.activate()).
+      this.keyboard = new Keyboard({
+        events: keyboardEvents
+      });
+
+      this.addFormatterUriClickListener();
+    },
+    /**
+     * APIMethod: detach
+     * detaches from the grid
+     * 
+     * @return void
+     */
+    detach: function() {
+      if (this.grid) {
+        this.grid.removeEvent('gridCellClick', this.onCellClick);
+      }
+      this.grid = null;
+      this.keyboard = null;
+    },
+    /**
+     * APIMethod: enable
+     * enables the grid 'externally'
+     *
+     * @return void
+     */
+    enable : function () {
+      this.options.enabled = true;
+    },
+    /**
+     * APIMethod: disable
+     * disables the grid 'externally'
+     *
+     * @var Boolean close - default true: also closes the currently open input/popup
+     * @var Boolean save - default false: also changes the currently open input/popup
+     * @return void
+     */
+    disable : function(close, save) {
+      close = $defined(close) ? close : true;
+      save = $defined(save) ? save : false;
+      if(close && this.activeCell.cell != null) {
+        this.deactivate(save);
+      }
+      this.options.enabled = false;
+    },
+
+    /**
+     * Method: onCellClick
+     * dispatch clicking on a table cell
+     */
+    onCellClick: function(cell) {
+      this.activate(cell);
+    },
+    /**
+     * Method: activate
+     * activates the input field or breaks up if conditions are not fulfilled
+     *
+     * @todo Field validation
+     *
+     * Parameters:
+     * @var {Object} cell Table Element
+     * @return void
+     */
+    activate: function(cell) {
+      // if not enabled or the cell is null, do nothing at all
+      if(!this.options.enabled || !cell)
+        return;
+
+      // activate can be called by clicking on the same cell or a
+      // different one
+      if (this.activeCell.cell) {
+        if (this.activeCell.cell != cell) {
+          if (!this.deactivate()) {
+            return;
+          }
+        } else {
+          // they are the same, ignore?
+          return;
+        }
+      }
+      
+      var data  = this.grid.getCellData(cell); //.retrieve('jxCellData');
+
+      if (!data || !$defined(data.row) || !$defined(data.column)) {
+        if($defined(console)) {
+          console.warn('out of grid %o',cell);
+          console.warn('data was %o', data);
+        }
+        return;
+      }
+
+      // column marked as not editable
+      if (!data.column.options.isEditable) {
+        return;
+      }
+
+      if (this.activeCell.timeoutId) {
+        clearTimeout(activeCell.timeoutId);
+      }
+
+      // set active record index to selected row
+      this.grid.store.moveTo(data.row);
+
+      // set up the data objects we need
+      var options = this.options,
+          grid = this.grid,
+          store = grid.getStore(),
+          index = grid.columns.getIndexFromGrid(data.column.name),
+          colOptions = data.column.options,
+          activeCell = {
+            oldValue      : store.get(data.column.name),
+            newValue      : {data: null, error: false},
+            fieldOptions  : this.getFieldOptionsByColName(data.column.name),
+            data          : data,
+            cell          : cell,
+            span          : cell.getElement('span.jxGridCellContent'),
+            validator     : null,
+            field         : null,
+            timeoutId     : null
+          },
+          jxFieldOptions = activeCell.fieldOptions.options,
+          oldValue,
+          groups,
+          k,
+          n;
+
+      // check if this column has special validation settings - 
+      // otherwise use default from this.options.validate
+      if(!$defined(data.column.options.validate) || typeof(data.column.options.validate) != 'boolean') {
+        data.column.options.validate = options.validate;
+        cell.store('jxCellData', data);
+      }
+
+      // check for different input field types
+      switch(activeCell.fieldOptions.type) {
+        case 'Text':
+        case 'Color':
+        case 'Password':
+        case 'File':
+          jxFieldOptions.value = activeCell.oldValue;
+          break;
+        case 'Textarea':
+          jxFieldOptions.value = activeCell.oldValue.replace(/<br \/>/gi, '\n');
+          break;
+        case 'Select':
+          // find out which visible value fits to the value inside
+          // <option>{value}</option> and set it to selected
+          jxFieldOptions.value = oldValue  = activeCell.oldValue.toString();
+          function setCombos(opts, oldValue) {
+            for(var i = 0, j = opts.length; i < j; i++) {
+              if(opts[i].value == oldValue) {
+                opts[i].selected = true;
+              }else{
+                opts[i].selected = false;
+              }
+            }
+            return opts;
+          }
+
+          if(jxFieldOptions.comboOpts) {
+            jxFieldOptions.comboOpts = setCombos(jxFieldOptions.comboOpts, oldValue);
+          }else if(jxFieldOptions.optGroups) {
+            groups = jxFieldOptions.optGroups;
+            for(k = 0, n = groups.length; k < n; k++) {
+              groups[k].options = setCombos(groups[k].options, oldValue);
+            }
+            jxFieldOptions.optGroups = groups;
+          }
+          break;
+        case 'Radio':
+        case 'Checkbox':
+        default:
+          $defined(console) ? console.warn("Fieldtype %o is not supported yet. If you have set a validator for a column, you maybe have forgotton to enter a field type.", activeCell.fieldOptions.type) : false;
+          return;
+          break;
+      }
+
+      // update the 'oldValue' to the formatted style, to compare the new value with the formatted one instead with the non-formatted-one
+      if(options.fieldFormatted && colOptions.renderer.options.formatter != null) {
+        if(!$defined(colOptions.fieldFormatted) || colOptions.fieldFormatted == true ) {
+          jxFieldOptions.value = colOptions.renderer.options.formatter.format(jxFieldOptions.value);
+          activeCell.oldValue = jxFieldOptions.value;
+        }
+      }
+
+      // create jx.field
+      activeCell.field = new Jx.Field[activeCell.fieldOptions.type.capitalize()](jxFieldOptions);
+      // create validator
+      if(options.validate && colOptions.validate) {
+        activeCell.validator = new Jx.Plugin.Field.Validator(activeCell.fieldOptions.validatorOptions);
+        activeCell.validator.attach(activeCell.field);
+      }
+
+      // store properties of the active cell
+      this.activeCell = activeCell;
+      this.setStyles(cell);
+
+      if(options.useKeyboard) {
+        this.keyboard.activate();
+      }
+
+      // convert a string to an integer if somebody entered a numeric value in quotes, if it failes: make false
+      if(typeof(options.blurDelay) == 'string') {
+        options.blurDelay = options.blurDelay.toInt() ? options.blurDelay.toInt() : false;
+      }
+
+      // add a onblur() and onfocus() event to the input field if enabled.
+      if(options.blurDelay !== false && typeof(options.blurDelay) == 'number') {
+        activeCell.field.field.addEvents({
+          // activate the timeout to close the input/poup
+          'blur' : function() {
+            // @todo For some reason, webkit does not clear the timeout correctly when navigating through the grid with keyboard
+            clearTimeout(activeCell.timeoutId);
+            activeCell.timeoutId = this.deactivate.delay(this.options.blurDelay);
+          }.bind(this),
+          // clear the timeout when the user focusses again
+          'focus' : function() {
+            clearTimeout(activeCell.timeoutId);
+          }, 
+          // clear the timeout when the user puts the mouse over the input
+          'mouseover' : function() {
+            clearTimeout(activeCell.timeoutId);
+          }
+        });
+        if(this.popup.domObj != null) {
+          this.popup.domObj.addEvent('mouseenter', function() {
+            clearTimeout(activeCell.timeoutId);
+          });
+        }
+      }
+
+      activeCell.field.field.focus();
+    }, 
+    /**
+     * APIMethod: deactivate
+     * hides the currently active field and stores the new entered data if the
+     * value has changed
+     *
+     * Parameters:
+     * @var {Boolean} save (Optional, default: true) - force aborting
+     * @return true if no data error occured, false if error (popup/input stays visible)
+     */
+    deactivate: function(save) {
+      var newValue = {data : null, error : false},
+          index,
+          activeCell = this.activeCell,
+          grid = this.grid,
+          store = grid.store,
+          options = this.options,
+          highlighter,
+          cellBg;
+
+      clearTimeout(activeCell.timeoutId);
+
+      if(activeCell.field !== null) {
+        save = $defined(save) ? save : true;
+
+
+        // update the value in the column
+        if(save && activeCell.field.getValue().toString() != activeCell.oldValue.toString()) {
+          store.moveTo(activeCell.data.row);
+          /*
+           * @todo webkit shrinks the rows when the value is updated... but refreshing the grid
+           *       immidiately returns in a wrong calculating of the cell position (getCoordinates)
+           */
+          switch (activeCell.fieldOptions.type) {
+            case 'Select':
+              index = activeCell.field.field.selectedIndex;
+              newValue.data = document.id(activeCell.field.field.options[index]).get('value');
+              break;
+            case 'Textarea':
+              newValue.data = activeCell.field.getValue().replace(/\n/gi, '<br />');
+              break;
+            default:
+              newValue.data = activeCell.field.getValue();
+              break;
+          }
+          if (save) {
+            activeCell.newValue.data = newValue.data;
+          }
+          // validation only if it should be saved!
+          if (activeCell.validator != null && !activeCell.validator.isValid()) {
+            newValue.error = true;
+            activeCell.field.field.focus.delay(50, activeCell.field.field);
+          }
+        } else {
+          activeCell.span.show();
+        }
+
+        // var data = activeCell.cell.retrieve('jxCellData');
+        if (save && newValue.data != null && newValue.error == false) {
+          store.set(activeCell.data.column.name, newValue.data);
+          this.addFormatterUriClickListener();
+        // else show error message and cell
+        } else if (newValue.error == true) {
+          activeCell.span.show();
+        }
+
+        // update reference to activeCell
+        if ($defined(activeCell.data.row) && $defined(activeCell.data.index)) {
+          var colIndex = grid.row.useHeaders() ? activeCell.data.index-1 : activeCell.data.index;
+          this.activeCell.cell = grid.gridTableBody.rows[this.activeCell.data.row].cells[colIndex];
+        }
+
+        if (options.useKeyboard) {
+          activeCell.field.removeEvent('keypress', this.setKeyboard);
+        }
+
+        /**
+         * COMMENT: this is just an idea how changing a value could be visualized
+         * we could also pass an Fx.Tween element?
+         * the row could probably be highlighted as well?
+         */
+        if(options.cellChangeFx.use) {
+          highlighter = new Fx.Tween(this.activeCell.cell, {
+            duration: 250,
+            onComplete: function(ev) {
+              this.element.removeProperty('style');
+            }
+          });
+          cellBg = activeCell.cell.getStyle('background-color');
+          cellBg = cellBg == 'transparent' ? '#fff' : cellBg;
+          if (newValue.data != null && newValue.error == false) {
+            highlighter.start('background-color',options.cellChangeFx.success, cellBg);
+          } else if (newValue.error){
+            highlighter.start('background-color',options.cellChangeFx.error, cellBg);
+          }
+        }
+
+        // check for error and keep input field alive
+        if (newValue.error) {
+          if(options.cellChangeFx.use) {
+            activeCell.field.field.highlight(options.cellChangeFx.error);
+          }
+          activeCell.field.field.setStyle('border','1px solid '+options.cellChangeFx.error);
+          activeCell.field.field.focus();
+          return false;
+        // otherwise hide it
+        }else{
+          this.keyboard.deactivate();
+          this.unsetActiveField();
+          return true;
+        }
+      }
+    },
+    /**
+     * Method: setStyles
+     * 
+     * sets some styles for the Jx.Field elements...
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     * @return void
+     */
+    setStyles : function(cell) {
+      var styles, 
+          size,
+          options = this.options,
+          activeCell = this.activeCell;
+      // popup
+      if (options.popup.use) {
+        if (options.popup.useLabels) {
+          activeCell.field.options.label = activeCell.data.column.options.header;
+          activeCell.field.render();
+        }
+        styles = {
+          field : {
+            'width'  : activeCell.field.type == 'Select' ?
+                         cell.getContentBoxSize().width + 5 + "px" :
+                         cell.getContentBoxSize().width - 14 + "px",
+            'margin' : 'auto 0'
+          }
+        };
+        activeCell.field.field.setStyles(styles.field);
+        this.showPopUp(cell);
+      // No popup
+      } else {
+        size   = cell.getContentBoxSize();
+        styles = {
+          domObj : {
+            position: 'absolute'
+          },
+          field : {
+            width : size.width + "px",
+            'margin-left' : 0
+          }
+        };
+
+        activeCell.field.domObj.setStyles(styles.domObj);
+        activeCell.field.field.setStyles(styles.field);
+
+        activeCell.field.domObj.inject(document.body);
+        Jx.Widget.prototype.position(activeCell.field.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+
+        activeCell.span.hide();
+      }
+
+      // COMMENT: an outline of the cell helps identifying the currently active cell
+      if(options.cellOutline.use) {
+        cell.setStyle('outline', options.cellOutline.style);
+      }
+    },
+    /**
+     * Method: showPopUp
+     *
+     * Shows the PopUp of of the editor if it already exists, otherwise calls Method
+     * this.createPopUp
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    showPopUp : function(cell) {
+      if(this.popup.domObj != null) {
+        Jx.Widget.prototype.position(this.popup.domObj, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+        });
+        this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+        this.popup.domObj.show();
+        this.setPopUpButtons();
+        this.setPopUpStylesAfterRendering();
+      }else{
+        this.createPopUp(cell);
+      }
+    },
+    /**
+     * Method: createPopUp
+     *
+     * creates the popup for the requested cell.
+     *
+     * COMMENT: this could also be an jx.dialog..? if we use jx.dialog, maybe without a title element?
+     *          Maybe a jx.dialog is too much for this little thing?
+     *
+     * Parameters:
+     * @var cell - table cell of the grid
+     */
+    createPopUp : function(cell) {
+      var coords = cell.getCoordinates(),
+          self      = this, popup  = null, innerWrapper = null,
+          closeIcon = null, submit = null, cancel       = null,
+          template  = Jx.Widget.prototype.processTemplate(this.options.popup.template, this.classes);
+
+      popup = template.jxGridEditorPopup;
+
+      innerWrapper = template.jxGridEditorPopupInnerWrapper;
+      /**
+       * COMMENT: first positioning is always in the top left of the grid..
+       * don't know why
+       * manual positioning is needed..?
+       */
+      popup.setStyles({
+        'left' : coords.left+'px',
+        'top'  : coords.top +'px'
+      });
+      /*
+      Jx.Widget.prototype.position(popup, cell, {
+            horizontal: ['left left'],
+            vertical: ['top top']
+      });
+      */
+
+      this.popup.domObj         = popup;
+      this.popup.innerWrapper   = innerWrapper;
+      this.popup.closeIcon      = closeIcon;
+      this.setPopUpButtons();
+
+      this.activeCell.field.domObj.inject(this.popup.innerWrapper, 'top');
+      this.popup.domObj.inject(document.body);
+
+      this.setPopUpStylesAfterRendering();
+    },
+    /**
+     * Method: setPopUpStylesAfterRendering
+     *
+     * - measures the widths of the buttons to set a new min-width for the popup
+     *   because custom labels could break the min-width and force a line-break
+     * - resets the size of the field to make it fit inside the popup (looks nicer)
+     *
+     * @return void
+     */
+    setPopUpStylesAfterRendering: function() {
+      if(this.options.popup.useButtons && this.popup.button.submit != null && this.popup.button.cancel != null) {
+        this.popup.domObj.setStyle('min-width', this.popup.button.submit.domObj.getSize().x + this.popup.button.cancel.domObj.getSize().x + "px");
+      }else{
+        if(this.popup.button.submit != null)
+          this.popup.button.submit.domObj.hide();
+        if(this.popup.button.cancel != null)
+          this.popup.button.cancel.domObj.hide();
+      }
+      this.activeCell.field.field.setStyle('width',
+        this.activeCell.field.type == 'Select' ?
+          this.popup.domObj.getSize().x - 7 + "px" :
+          this.popup.domObj.getSize().x - 17 + "px");
+    },
+    /**
+     * Method: setPopUpButtons
+     * creates the PopUp Buttons if enabled in options or deletes them if set to false
+     *
+     * @return void
+     */
+    setPopUpButtons : function() {
+      var self = this,
+          button = {
+            submit : null,
+            cancel : null
+          };
+      // check if buttons are needed, innerWrapper exists and no buttons already exist
+      if(this.options.popup.useButtons && this.popup.innerWrapper != null && this.popup.button.submit == null) {
+        button.submit = new Jx.Button({
+          label : this.options.popup.button.submit.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'submitButton'}) :
+                    this.getText(this.options.popup.button.submit.label),
+          image : this.options.popup.button.submit.image,
+          onClick: function() {
+            self.deactivate(true);
+          }
+        }).addTo(this.popup.innerWrapper);
+        button.cancel = new Jx.Button({
+          label : this.options.popup.button.cancel.label.length == 0 ? 
+                    this.getText({set:'Jx',key:'plugin.editor',value:'cancelButton'}) :
+                    this.getText(this.options.popup.button.cancel.label),
+          image : this.options.popup.button.cancel.image,
+          onClick: function() {
+            self.deactivate(false);
+          }
+        }).addTo(this.popup.innerWrapper);
+      }else if(this.options.popup.useButtons && this.popup.button.submit != null) {
+        button = {
+          submit : this.popup.button.submit,
+          cancel : this.popup.button.cancel
+        };
+      // check if buttons are not needed and buttons already exist to remove them
+      }else if(this.options.popup.useButtons == false && this.popup.button.submit != null) {
+        this.popup.button.submit.cleanup();
+        this.popup.button.cancel.cleanup();
+      }
+
+      this.popup.button = button;
+    },
+    /**
+     * Method: unsetActiveField
+     * resets the activeField and hides the popup
+     *
+     * @return void
+     */
+    unsetActiveField: function() {
+      this.activeCell.field.destroy();
+      if(this.popup.domObj != null) {
+        this.popup.domObj.removeEvent('mouseenter');
+        this.popup.domObj.hide();
+      }
+
+      this.activeCell.cell.setStyle('outline', '0px');
+
+      this.activeCell = {
+        field         : null,
+        oldValue      : null,
+        newValue      : { data: null, error: false},
+        cell          : null,
+        span          : null,
+        timeoutId     : null,
+        //popup         : null,   // do not destroy the popup, it might be used again
+        data           : {},
+        fieldOptions  : {},
+        validator     : null
+      };
+    },
+    /**
+     * Method: unsetPopUp
+     * resets the popup manually to be able to use it with different settings
+     */
+    unsetPopUp : function() {
+      if(this.popup.domObj != null) {
+        this.popup.domObj.destroy();
+        this.popup.innerWrapper   = null;
+        this.popup.closeIcon      = null;
+        this.popup.button.submit = null;
+        this.popup.button.cancel = null;
+      }
+    },
+    /**
+     * APIMethod: getNextCellInRow
+     * activates the next cell in a row if it is editable
+     * otherwise the focus jumps to the next editable cell in the next row
+     * or starts at the beginning
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      var nextCell = true,
+          nextRow = true,
+          sumCols = this.grid.columns.columns.length,
+          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
+          i = 0,
+          data,
+          cell = this.activeCell.cell,
+          options = this.options;
+      if (this.activeCell.cell != null) {
+        do {
+          nextCell = i > 0 ? nextCell.getNext(jxCellClass) : cell.getNext(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if (nextCell == null) {
+            nextRow  = cell.getParent('tr').getNext();
+            // check if this was the last row in the table
+            if (nextRow == null && options.keypressLoop) {
+              nextRow = cell.getParent('tbody').getFirst();
+            } else if(nextRow == null && !options.keypressLoop){
+              return;
+            }
+            nextCell = nextRow.getFirst(jxCellClass);
+          }
+          data = this.grid.getCellData(nextCell);
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if (i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        } while(data && !data.column.options.isEditable);
+
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInRow
+     * activates the previous cell in a row if it is editable
+     * otherwise the focus jumps to the previous editable cell in the previous row
+     * or starts at the last cell in the last row at the end
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInRow: function(save) {
+      save = $defined(save) ? save : true;
+      var prevCell, 
+          prevRow, 
+          i = 0,
+          data,
+          row,
+          index,
+          cell = this.activeCell.cell,
+          sumCols = this.grid.columns.columns.length,
+          jxCellClass = 'td.jxGridCell:not(.jxGridCellUnattached)',
+          options = this.options;
+      if(cell != null) {
+        do {
+          prevCell = i > 0 ? prevCell.getPrevious(jxCellClass) : cell.getPrevious(jxCellClass);
+          // check if cell is still in row, otherwise returns null
+          if(prevCell == null) {
+            prevRow  = cell.getParent('tr').getPrevious();
+            // check if this was the last row in the table
+            if(prevRow == null && options.keypressLoop) {
+              prevRow = cell.getParent('tbody').getLast();
+            }else if(prevRow == null && !options.keypressLoop) {
+              return;
+            }
+            prevCell = prevRow.getLast(jxCellClass);
+          }
+          data  = this.grid.getCellData(prevCell);
+          row   = data.row;
+          index = data.index;
+          i++;
+          // if all columns are set to uneditable during runtime, jump out of the loop after
+          // running through 2 times to prevent an endless-loop and browser crash :)
+          if(i == sumCols*2) {
+            this.deactivate(save);
+            return;
+          }
+        }while(data && !data.column.options.isEditable);
+
+        if(save === false) {
+          this.deactivate(save);
+        }
+        this.activate(prevCell);
+      }
+    },
+    /**
+     * APIMethod: getNextCellInCol
+     * activates the next cell in a column under the currently active one
+     * if the active cell is in the last row, the first one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getNextCellInCol : function(save) {
+      var nextRow,
+          nextCell,
+          activeCell = this.activeCell;
+      save = $defined(save) ? save : true;
+      if (activeCell.cell != null) {
+        nextRow = activeCell.cell.getParent().getNext();
+        if (nextRow == null) {
+          nextRow = activeCell.cell.getParent('tbody').getFirst();
+        }
+        nextCell = nextRow.getElement('td.jxGridCol'+activeCell.data.index);
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(nextCell);
+      }
+    },
+    /**
+     * APIMethod: getPrevCellInCol
+     * activates the previous cell in a column above the currently active one
+     * if the active cell is in the first row, the last one will be used
+     *
+     * @var  {Boolean} save (Optional, default: true)
+     * @return void
+     */
+    getPrevCellInCol : function(save) {
+      var prevRow,
+          prevCell,
+          activeCell = this.activeCell;
+      save = $defined(save) ? save : true;
+      if (activeCell.cell != null) {
+        prevRow = activeCell.cell.getParent().getPrevious();
+        if (prevRow == null) {
+          prevRow = activeCell.cell.getParent('tbody').getLast();
+        }
+        prevCell = prevRow.getElement('td.jxGridCol'+activeCell.data.index);
+        if (save === false) {
+          this.deactivate(save);
+        }
+        this.activate(prevCell);
+      }
+    },
+    /**
+     * Method: cellValueIncrement
+     * Whether increments or decrements the value of the active cell if the dataType is numeric
+     *
+     * Parameters
+     * @var {Boolean} bool
+     * @return void
+     */
+    cellValueIncrement : function(bool) {
+      var activeCell = this.activeCell,
+          dataType = activeCell.data.column.options.dataType,
+          valueNew = null,
+          formatter;
+      switch (dataType) {
+        case 'numeric':
+        case 'currency':
+          valueNew = activeCell.field.getValue().toInt();
+          if (typeof(valueNew) == 'number') {
+            if (bool) {
+              valueNew++;
+            } else {
+              valueNew--;
+            }
+          }
+          break;
+        case 'date':
+          valueNew = Date.parse(activeCell.field.getValue());
+          if (valueNew instanceof Date) {
+            if (bool) {
+              valueNew.increment();
+            } else {
+              valueNew.decrement();
+            }
+            formatter = new Jx.Formatter.Date();
+            valueNew = formatter.format(valueNew);
+          }
+          break;
+      }
+      if (valueNew != null) {
+        activeCell.field.setValue(valueNew);
+      }
+    },
+    /**
+     * Method: cellIsInGrid
+     * determins if the given coordinates are within the grid
+     *
+     * Parameters:
+     * @var {Integer} row
+     * @var {Integer} index
+     * @return {Boolean}
+     */
+    cellIsInGrid: function(row, index) {
+      if($defined(row) && $defined(index)) {
+        //console.log("Row %i - max Rows: %i, Col %i - max Cols %i", row, this.grid.gridTableBody.rows.length, index, this.grid.gridTableBody.rows[row].cells.length);
+        if( row >= 0 && index >= 0 &&
+            row <= this.grid.gridTableBody.rows.length &&
+            index <= this.grid.gridTableBody.rows[row].cells.length
+        ) {
+          return true;
+        }else{
+          return false;
+        }
+      }else{
+        return false;
+      }
+    },
+    /**
+     * APIMethod: getFieldOptionsByColName
+     * checks for the name of a column inside the fieldOptions and returns
+     * the object if found, otherwise the default options for the field
+     *
+     * Parameters:
+     * @var {String} colName
+     * @return {Object} default field options
+     */
+    getFieldOptionsByColName : function(colName) {
+      var fo = this.options.fieldOptions,
+          r  = this.options.fieldOptions[0];
+      for(var i = 0, j = fo.length; i < j; i++) {
+        if(fo[i].field == colName) {
+          r = fo[i];
+          break;
+        }
+      }
+      return r;
+    },
+    /**
+     * Method: addFormatterUriClickListener
+     *
+     * looks up for Jx.Formatter.Uri columns to disable the link and open the
+     * inline editor instead when CTRL is NOT pressed.
+     * set option linkClickListener to false to disable this
+     *
+     */
+    addFormatterUriClickListener : function() {
+      if(this.options.linkClickListener) {
+        // prevent a link from beeing opened if the editor should appear and the uri formatter is activated
+        var uriCols = [], tableCols, anchor;
+        // find out which columns are using a Jx.Formatter.Uri
+        this.grid.columns.columns.each(function(col,i) {
+          if(col.options.renderer.options.formatter != null && col.options.renderer.options.formatter instanceof Jx.Formatter.Uri) {
+            uriCols.push(i);
+          }
+        });
+        // add an event to all anchors inside these columns
+        this.grid.gridObj.getElements('tr').each(function(tr,i) {
+          tableCols = tr.getElements('td.jxGridCell');
+          for(var j = 0, k = uriCols.length; j < k; j++) {
+            anchor = tableCols[uriCols[j]-1].getElement('a');
+            if(anchor) {
+              anchor.removeEvent('click');
+              anchor.addEvent('click', function(ev) {
+                // open link if ctrl was clicked
+                if(!ev.control) {
+                  ev.preventDefault();
+                }
+              });
+            }
+          }
+        });
+      }
+    },
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     *
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    	if (this.options.popup.use && this.options.popup.useButtons) {
+        if(this.popup.button.submit != null) {
+          this.popup.button.submit.cleanup();
+          this.popup.button.cancel.cleanup();
+          this.popup.button.submit = null;
+          this.popup.button.cancel = null;
+          this.setPopUpButtons();
+        }
+    	}
+    }
+}); 
+/*
+---
+
+name: Jx.Plugin.DataView
+
+description: Namespace for DataView plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.DataView]
+...
+ */
+/**
+ * Namespace: Jx.Plugin.DataView
+ * The namespace for all dataview plugins
+ */
+Jx.Plugin.DataView = {};/*
+---
+
+name: Jx.Slide
+
+description: A class that shows and hides elements using a slide effect. Does not use a wrapper element or require a fixed width or height.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+ - Core/Fx.Tween
+
+provides: [Jx.Slide]
+
+...
+ */
+// $Id: slide.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Slide',
+    Implements: Jx.Object,
+    Binds: ['handleClick'],
+    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
+    },
+    /**
+     * Method: init
+     * sets up the slide
+     */
+    init: function () {
+
+        this.target = document.id(this.options.target);
+
+        this.target.set('tween', {onComplete: this.setDisplay.bind(this)});
+
+        if ($defined(this.options.trigger)) {
+            this.trigger = document.id(this.options.trigger);
+            this.trigger.addEvent('click', this.handleClick);
+        }
+
+        this.target.store('slider', this);
+
+    },
+    /**
+     * Method: 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 {
+            this.slide('out');
+        }
+    },
+    /**
+     * 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:
+     * dir - the direction to slide (either "in" or "out")
+     */
+    slide: function (dir) {
+        var h;
+        if (dir === 'in') {
+            h = this.target.retrieve(this.options.type);
+            this.target.setStyles({
+                overflow: 'hidden',
+                display: 'block'
+            });
+            this.target.setStyles(this.options.type, 0);
+            this.target.tween(this.options.type, h);
+        } else {
+            if (this.options.type === 'height') {
+                h = this.target.getMarginBoxSize().height;
+            } else {
+                h = this.target.getMarginBoxSize().width;
+            }
+            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);
+        }
+    }
+});/*
+---
+
+name: Jx.Plugin.DataView.GroupFolder
+
+description: Enables closing and opening groups in a group dataview
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.DataView
+ - Jx.Slide
+
+provides: [Jx.Plugin.DataView.GroupFolder]
+
+...
+ */
+/**
+ * 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
+    },
+    /**
+     * Property: headerState
+     * Hash that holds the open/closed state of each header
+     */
+    headerState: null,
+    init: function() {
+      this.headerState = new Hash();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this plugin to a dataview
+     */
+    attach: function (dataView) {
+        if (!$defined(dataView) && !(dataview instanceof Jx.Panel.DataView)) {
+            return;
+        }
+
+        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: onSlideIn
+     * Called when a group opens.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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: onSlideOut
+     * Called when a group closes.
+     *
+     * Parameters:
+     * header - the header that was clicked.
+     */
+    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');
+    }
+});
+/*
+---
+
+name: Jx.Plugin.Field
+
+description: Namespace for Jx.Field plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Field]
+
+...
+ */
+// $Id: plugin.field.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Plugin.Field.Validator
+
+description: Provides validation services for Jx.Field subclasses
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Field
+ - More/Form.Validator
+ - More/Form.Validator.Extras
+
+provides: [Jx.Plugin.Field.Validator]
+
+...
+ */
+// $Id: field.validator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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
+    },
+    /**
+     * Property: valid
+     * tells whether this field passed validation or not.
+     */
+    valid: null,
+    /**
+     * Property: errors
+     * array of errors found on this field
+     */
+    errors: null,
+    validators : null,
+    /**
+     * 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.errors = [];
+        this.validators = new Hash();
+        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;
+        }
+        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();
+        }
+        //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.set(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);
+    },
+    /**
+     * APIMethod: detach
+     */
+    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;
+    },
+
+    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('jxFieldSuccess').addClass('jxFieldError');
+            this.fireEvent('fieldValidationFailed', [this.field, this]);
+        } else {
+            this.field.domObj.removeClass('jxFieldError').addClass('jxFieldSuccess');
+            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('jxFieldError').removeClass('jxFieldSuccess');
+    },
+    /**
+     * APIMethod: getErrors
+     * USe this method to retrieve all of the errors noted for this field.
+     */
+    getErrors: function () {
+        return this.errors;
+    }
+
+
+});
+/*
+---
+
+name: Jx.Plugin.Form
+
+description: Namespace for Jx.Form plugins
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.Form]
+
+...
+ */
+// $Id: plugin.form.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 = {};/*
+---
+
+name: Jx.Plugin.Form.Validator
+
+description: Provides validation services for Jx.Form
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.Form
+ - Jx.Plugin.Field.Validator
+
+provides: [Jx.Plugin.Form.Validator]
+
+...
+ */
+// $Id: form.validator.js 970 2010-08-23 12:20:57Z pagameba $
+/**
+ * Class: Jx.Plugin.Form.Validator
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * Form plugin for enforcing validation on the fields 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.Form.Validator = new Class({
+
+    Extends : Jx.Plugin,
+    name: 'Form.Validator',
+
+    options: {
+        /**
+         * Option: fields
+         * This will be key/value pairs for each of the fields as shown here:
+         * (code)
+         * {
+         *     fieldID: {
+         *          ... options for Field.Validator plugin ...
+         *     },
+         *     fieldID: {...
+         *     }
+         * }
+         * (end)
+         */
+        fields: null,
+        /**
+         * Option: fieldDefaults
+         * {Object} contains named defaults for field validators to be
+         * triggered on blur or change.  Default is:
+         * (code)
+         * {
+         *    validateOnBlur: true
+         *    validateOnChange: false
+         * }
+         * (end)
+         */
+        fieldDefaults: {
+            validateOnBlur: true,
+            validateOnChange: true
+        },
+        /**
+         * Option: validateOnSubmit
+         * {Boolean} default true.  Trigger validation on submission of
+         * form if true.
+         */
+        validateOnSubmit: true,
+        /**
+         * Option: suspendSubmit
+         * {Boolean} default false.  Stop form submission when validator is
+         * attached.
+         */
+        suspendSubmit: false
+    },
+    /**
+     * Property: errorMessagess
+     * element holding
+     */
+    errorMessage: null,
+    /**
+     * 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);
+    },
+    /**
+     * 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,
+            options = this.options;
+        //override the isValid function in the form
+        form.isValid = function () {
+            return plugin.isValid();
+        };
+
+        if (options.validateOnSubmit && !options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', this.bound.validate);
+        } else if (options.suspendSubmit) {
+            document.id(this.form).addEvent('submit', function (ev) {
+                ev.stop();
+            });
+        }
+
+        this.plugins = $H();
+
+        //setup the fields
+        $H(options.fields).each(function (val, key) {
+            var opts = $merge(this.options.fieldDefaults, val),
+                fields = this.form.getFieldsByName(key).
+                p;
+            if (fields && fields.length) {
+                p = new Jx.Plugin.Field.Validator(opts);
+                this.plugins.set(key, p);
+                p.attach(fields[0]);
+                p.addEvent('fieldValidationFailed', this.bound.failed);
+                p.addEvent('fieldValidationPassed', this.bound.passed);
+            }
+        }, this);
+
+    },
+    /**
+     * APIMethod: detach
+     */
+    detach: function() {
+        if (this.form) {
+            document.id(this.form).removeEvent('submit');
+        }
+        this.form = null;
+        this.plugins.each(function(plugin){
+            plugin.detach();
+            plugin = null;
+        },this);
+        this.plugins = null;
+    },
+    /**
+     * 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]);
+        }
+        return valid;
+    },
+    /**
+     * Method: fieldFailed
+     * Refires the fieldValidationFailed event from the field validators it contains
+     */
+    fieldFailed: function (field, validator) {
+        this.fireEvent('fieldValidationFailed', [field, validator]);
+    },
+    /**
+     * Method: fieldPassed
+     * 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;
+    }
+
+
+});
+/*
+---
+
+name: Jx.Plugin.ToolbarContainer
+
+description: Namespace for Jx.Toolbar.Container
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Plugin.ToolbarContainer]
+
+...
+ */
+/**
+ * Class: Jx.Plugin.Toolbar
+ * Toolbar plugin namespace
+ *
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer = {};/*
+---
+
+name: Jx.Plugin.ToolbarContainer.TabMenu
+
+description: Adds a menu of tabs to the toolbar container for easy access to all tabs.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin.ToolbarContainer
+
+provides: [Jx.Plugin.ToolbarContainer.TabMenu]
+
+...
+ */
+/**
+ * Class: Jx.Plugin.ToolbarContainer.TabMenu
+ *
+ * Extends: <Jx.Plugin>
+ *
+ * This plugin provides a menu of tabs in a toolbar (similar to the button in firefox at the end of the row of tabs).
+ * It is designed to be used only when the toolbar contains tabs and only when the container is allowed to scroll. Also,
+ * this plugin must be added directly to the Toolbar container. You can get a reference to the container for a
+ * <Jx.TabBox> by doing
+ *
+ * (code)
+ * var tabbox = new Jx.TabBox();
+ * var toolbarContainer = document.id(tabBox.tabBar).getParent('.jxBarContainer').retrieve('jxBarContainer');
+ * (end)
+ *
+ * You can then use the attach method to connect the plugin. Otherwise, you can add it via any normal means to a
+ * directly instantiated Container.
+ *
+ * License:
+ * Copyright (c) 2010, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Plugin.ToolbarContainer.TabMenu = new Class({
+
+    Family: 'Jx.Plugin.ToolbarContainer.TabMenu',
+    Extends: Jx.Plugin,
+
+    Binds: ['addButton'],
+
+    options: {
+    },
+    /**
+     * Property: tabs
+     * holds all of the tabs that we're tracking
+     */
+    tabs: [],
+
+    init: function () {
+        this.parent();
+    },
+
+    attach: function (toolbarContainer) {
+        this.parent(toolbarContainer);
+
+        this.container = toolbarContainer;
+
+        //we will only be used if the container is allowed to scroll
+        if (!this.container.options.scroll) {
+            return;
+        }
+
+        this.menu = new Jx.Menu({},{
+            buttonTemplate: '<span class="jxButtonContainer"><a class="jxButton jxButtonMenu jxDiscloser"><span class="jxButtonContent"><span class="jxButtonLabel"></span></span></a></span>'
+        }).addTo(this.container.controls,'bottom');
+        document.id(this.menu).addClass('jxTabMenuRevealer');
+        this.container.update();
+
+        //go through all of the existing tabs and add them to the menu
+        //grab the toolbar...
+        var tb = document.id(this.container).getElement('ul').retrieve('jxToolbar');
+        tb.list.each(function(item){
+            this.addButton(item);
+        },this);
+
+        //connect to the add event of the toolbar list to monitor the addition of buttons
+        tb.list.addEvent('add',this.addButton);
+    },
+
+    detach: function () {
+        this.parent();
+    },
+
+    addButton: function (item) {
+        var tab;
+        tab = (item instanceof Jx.Tab) ? item : document.id(item).getFirst().retrieve('jxTab');
+
+
+        var l = tab.getLabel();
+        if (!$defined(l)) {
+            l = '';
+        }
+        var mi = new Jx.Menu.Item({
+            label: l,
+            image: tab.options.image,
+            onClick: function() {
+                if (tab.isActive()) {
+                    this.container.scrollIntoView(tab);
+                } else {
+                    tab.setActive(true);
+                }
+            }.bind(this)
+        });
+
+        document.id(tab).store('menuItem', mi);
+
+        tab.addEvent('close', function() {
+            this.menu.remove(mi);
+        }.bind(this));
+
+        this.menu.add([mi]);
+    }
+});/*
+---
+
+name: Jx.Adaptor
+
+description: Base class for all Adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Plugin
+
+provides: [Jx.Adaptor]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor
+ * Base class for all adaptor implementations. Provides a place to locate all
+ * common code and the Jx.Adaptor namespace.  Since it extends <Jx.Plugin> all
+ * adaptors will be able to be used as plugins for their respective classes.
+ * Also as such, they must have the attach() and detach() methods.
+ *
+ * Adaptors are specifically used to conform a <Jx.Store> to any one of
+ * the different widgets (i.e. Jx.Tree, Jx.ListView, etc...) that could
+ * benefit from integration with the store. This approach was taken to minimize
+ * data access code in the widgets themselves. Widgets should have no idea where
+ * the data/items come from so that they will be usable in the broadest number
+ * of situations.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor = new Class({
+
+
+  Extends: Jx.Plugin,
+  Family: 'Jx.Adaptor',
+
+  name: 'Jx.Adaptor',
+
+  options: {
+        /**
+         * Option: template
+         * The text template to use in creating the items for this adaptor
+         */
+      template: '',
+        /**
+         * Option: useTemplate
+         * Whether or not to use the text template above. Defaults to true.
+         */
+      useTemplate: true,
+        /**
+         * Option: store
+         * The store to use with the adaptor.
+         */
+      store: null
+  },
+    /**
+     * Property: columnsNeeded
+     * Will hold an array of the column names needed for processing the
+     * template
+     */
+  columnsNeeded: null,
+
+  init: function () {
+      var options = this.options;
+      this.parent();
+
+      this.store = options.store;
+
+      if (options.useTemplate && $defined(this.store.getColumns())) {
+          this.columnsNeeded = this.store.parseTemplate(options.template);
+      }
+  },
+
+  attach: function (widget) {
+    this.parent(widget);
+    this.widget = widget;
+  },
+
+  detach: function () {
+    this.parent();
+  }
+
+});/*
+---
+
+name: Jx.Adaptor.Tree
+
+description: Base class for all adaptors that fill Jx.Tree widgets. Also acts as the namespace for other Jx.Tree adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor
+
+provides: [Jx.Adaptor.Tree]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor.Tree
+ * This base class is used to change a store (a flat list of records) into the
+ * data structure needed for a Jx.Tree. It will have 2 subclasses:
+ * <Jx.Adapter.Tree.Mptt> and <Jx.Adapter.Tree.Parent>.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree = new Class({
+
+
+    Extends: Jx.Adaptor,
+    Family: 'Jx.Adaptor.Tree',
+
+    Binds: ['fill','checkFolder'],
+
+    options: {
+        /**
+         * Option: monitorFolders
+         * Determines if this adapter should use monitor the TreeFolder items in
+         * order to request any items they should contain if they are empty.
+         */
+        monitorFolders: false,
+        /**
+         * Option: startingNodeKey
+         * The store primary key to use as the node that we're requesting.
+         * Initially set to -1 to indicate that we're request the first set of
+         * data
+         */
+        startingNodeKey: -1,
+        /**
+         * Option: folderOptions
+         * A Hash containing the options for <Jx.TreeFolder>. Defaults to null.
+         */
+        folderOptions: null,
+        /**
+         * Option: itemOptions
+         * A Hash containing the options for <Jx.TreeItem>. Defaults to null.
+         */
+        itemOptions: null
+    },
+    /**
+     * Property: folders
+     * A Hash containing all of the <Jx.TreeFolders> in this tree.
+     */
+    folders: null,
+    /**
+     * Property: currentRecord
+     * An integer indicating the last position we were at in the store. Used to
+     * allow the adaptor to pick up rendering items after we request additional
+     * data.
+     */
+    currentRecord: -1,
+    init: function() {
+      this.folders = new Hash();
+      this.parent();
+    },
+    /**
+     * APIMethod: attach
+     * Attaches this adaptor to a specific tree instance.
+     *
+     * Parameters:
+     * tree - an instance of <Jx.Tree>
+     */
+    attach: function (tree) {
+        this.parent(tree);
+
+        this.tree = tree;
+
+        if (this.options.monitorFolders) {
+            this.strategy = this.store.getStrategy('progressive');
+
+            if (!$defined(this.strategy)) {
+                this.strategy = new Jx.Store.Strategy.Progressive({
+                    dropRecords: false,
+                    getPaginationParams: function () { return {}; }
+                });
+                this.store.addStrategy(this.strategy);
+            } else {
+                this.strategy.options.dropRecords = false;
+                this.strategy.options.getPaginationParams = function () { return {}; };
+            }
+
+        }
+
+        this.store.addEvent('storeDataLoaded', this.fill);
+
+
+    },
+    /**
+     * APIMethod: detach
+     * removes this adaptor from the current tree.
+     */
+    detach: function () {
+      this.parent();
+      this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+    /**
+     * APIMethod: firstLoad
+     * Method used to start the first store load.
+     */
+    firstLoad: function () {
+      //initial store load
+      this.busy = 'tree';
+      this.tree.setBusy(true);
+        this.store.load({
+            node: this.options.startingNodeKey
+        });
+    },
+
+    /**
+     * APIMethod: fill
+     * This function will start at this.currentRecord and add the remaining
+     * items to the tree.
+     */
+    fill: function () {
+      var i,
+          template,
+          item,
+          p,
+          folder,
+          options = this.option;
+
+      if (this.busy == 'tree') {
+        this.tree.setBusy(false);
+        this.busy = 'none';
+      } else if (this.busy == 'folder') {
+        this.busyFolder.setBusy(false);
+        this.busy = 'none';
+      }
+        var l = this.store.count() - 1;
+        for (i = this.currentRecord + 1; i <= l; i++) {
+            template = this.store.fillTemplate(i,options.template,this.columnsNeeded);
+
+            if (this.hasChildren(i)) {
+                //add as folder
+                item = new Jx.TreeFolder($merge(options.folderOptions, {
+                    label: template
+                }));
+
+                if (options.monitorFolders) {
+                  item.addEvent('disclosed', this.checkFolder);
+                }
+
+                this.folders.set(i,item);
+            } else {
+                //add as item
+                item = new Jx.TreeItem($merge(options.itemOptions, {
+                    label: template
+                }));
+            }
+            document.id(item).store('index', i).store('jxAdaptor', this);
+            //check for a parent
+            if (this.hasParent(i)) {
+                //add as child of parent
+                var p = this.getParentIndex(i);
+                var folder = this.folders.get(p);
+                folder.add(item);
+            } else {
+                //otherwise add to the tree itself
+                this.tree.add(item);
+            }
+        }
+        this.currentRecord = l;
+    },
+    /**
+     * Method: checkFolder
+     * Called by the disclose event of the tree to determine if we need to
+     * request additional items for a branch of the tree.
+     */
+    checkFolder: function (folder) {
+        var items = folder.items(),
+            index,
+            node;
+        if (!$defined(items) || items.length === 0) {
+            //get items via the store
+          index = document.id(folder).retrieve('index');
+          node = this.store.get('primaryKey', index);
+          this.busyFolder = folder;
+          this.busyFolder.setBusy(true);
+          this.busy = 'folder';
+            this.store.load({
+                node: node
+            });
+        }
+    },
+    /**
+     * Method: hasChildren
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has any children.
+     */
+    hasChildren: $empty,
+    /**
+     * Method: hasParent
+     * Virtual method to be overridden by sublcasses. Determines if a specific
+     * node has a parent node.
+     */
+    hasParent: $empty,
+    /**
+     * Method: getParentIndex
+     * Virtual method to be overridden by sublcasses. Determines the store index
+     * of the parent node.
+     */
+    getParentIndex: $empty
+});/*
+---
+
+name: Jx.Adaptor.Tree.Mptt
+
+description: Fills a Jx.Tree instance from a remote table that represents an MPTT (Modified Preorder Table Traversal) data source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Tree
+
+provides: [Jx.Adaptor.Tree.Mptt]
+
+...
+ */
+/**
+ * Class: Jx.Adaptor.Tree.Mptt
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ *
+ * This class requires an MPTT (Modified Preorder Tree Traversal) table. The MPTT
+ * has a 'left' and a 'right' column that indicates the order of nesting. For
+ * more details see the sitepoint.com article at
+ * http://articles.sitepoint.com/article/hierarchical-data-database
+ *
+ * if useAjax option is set to true then this adapter will send an Ajax request
+ * to the server, through the store's strategy (should be Jx.Store.Strategy.Progressive)
+ * to request additional nodes.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Mptt = new Class({
+
+
+    Family: 'Jx.Adaptor.Tree.Mptt',
+    Extends: Jx.Adaptor.Tree,
+
+    name: 'tree.mptt',
+
+    options: {
+        left: 'left',
+        right: 'right'
+    },
+
+    /**
+     * APIMethod: hasChildren
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    hasChildren: function (index) {
+        var l = this.store.get(this.options.left, index).toInt(),
+            r = this.store.get(this.options.right, index).toInt();
+        return (l + 1 !== r);
+    },
+
+    /**
+     * APIMethod: hasParent
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    hasParent: function (index) {
+        var i = this.getParentIndex(index),
+            result = false;
+        if ($defined(i)) {
+            result = true;
+        }
+        return result;
+    },
+
+    /**
+     * APIMethod: getParentIndex
+     *
+     * Parameters:
+     * index - {integer} the array index of the row in the store (not the
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        var store = this.store,
+            options = this.options,
+            l,
+            r,
+            i,
+            pl,
+            pr;
+        l = store.get(options.left, index).toInt();
+        r = store.get(options.right, index).toInt();
+        for (i = index-1; i >= 0; i--) {
+            pl = store.get(options.left, i).toInt();
+            pr = store.get(options.right, i).toInt();
+            if (pl < l && pr > r) {
+                return i;
+            }
+        }
+        return null;
+    }
+});/*
+---
+
+name: Jx.Adaptor.Tree.Parent
+
+description: Fills a Jx.Tree instance from a standard parent/child/folder style data table.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Tree
+
+provides: [Jx.Adaptor.Tree.Parent]
+
+
+...
+ */
+/**
+ * Class: Jx.Adapter.Tree.Parent
+ * This class adapts a table adhering to the classic Parent-style "tree table".
+ * 
+ * Basically, the store needs to have a column that will indicate the
+ * parent of each row. The root(s) of the tree should be indicated by a "-1" 
+ * in this column. The name of the "parent" column is configurable in the 
+ * options.
+ * 
+ * if the monitorFolders option is set to true then this adapter will send
+ * an Ajax request to the server, through the store's strategy (should be
+ * Jx.Store.Strategy.Progressive) to request additional nodes. Also, a column
+ * indicating whether this is a folder needs to be set as there is no way to
+ * tell if a node has children without it.
+ *
+ * Copyright 2010 by Jonathan Bomgardner
+ * License: mit-style
+ */
+Jx.Adaptor.Tree.Parent = new Class({
+    
+
+    Extends: Jx.Adaptor.Tree,
+    Family: 'Jx.Adaptor.Tree.Parent',
+    
+    options: {
+        parentColumn: 'parent',
+        folderColumn: 'folder'
+    },
+        
+    /**
+     * APIMethod: hasChildren
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasChildren: function (index) {
+    	return this.store.get(this.options.folderColumn, index);
+    },
+    
+    /**
+     * APIMethod: hasParent
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    hasParent: function (index) {
+        if (this.store.get(this.options.parentColumn, index).toInt() !== -1) {
+            return true;
+        } 
+        return false;
+    },
+    
+    /**
+     * APIMethod: getParentIndex
+     * 
+     * Parameters: 
+     * index - {integer} the array index of the row in the store (not the 
+     *          primary key).
+     */
+    getParentIndex: function (index) {
+        //get the parent based on the index
+        var pk = this.store.get(this.options.parentColumn, index);
+        return this.store.findByColumn('primaryKey', pk);
+    }
+});/*
+---
+
+name: Jx.Adaptor.Combo
+
+description: Namespace for all Jx.Combo adaptors.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor
+
+provides: [Jx.Adaptor.Combo]
+
+...
+*/
+/**
+ * Class: Jx.Adaptor.Combo
+ * The namespace for all combo adaptors
+ */
+Jx.Adaptor.Combo = {};/*
+---
+
+name: Jx.Adaptor.Combo.Fill
+
+description: Loads data into a Jx.Combo instance from designated column(s) of a data source.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Adaptor.Combo
+
+provides: [Jx.Adaptor.Combo.Fill]
+
+...
+ */
+Jx.Adaptor.Combo.Fill = new Class({
+
+    Family: 'Jx.Adaptor.Combo.Fill',
+    Extends: Jx.Adaptor,
+    name: 'combo.fill',
+    Binds: ['fill'],
+
+    /**
+     * Note: option.template is used for constructing the text for the label
+     */
+    options: {
+        /**
+         * Option: imagePathColumn
+         * points to a store column that holds the image information
+         * for the combo items.
+         */
+        imagePathColumn: null,
+        /**
+         * Option: imageClassColumn
+         * Points to a store column that holds the image class
+         * information for the combo items
+         */
+        imageClassColumn: null,
+        /**
+         * Option: selectedFn
+         * This should be a function that could be run to determine if
+         * an item should be selected. It will get passed the current store
+         * record as the only parameter. It should return either true or false.
+         */
+        selectedFn: null,
+        /**
+         * Option: noRepeats
+         * This option allows you to use any store even if it has duplicate
+         * values in it. With this option set to true the adaptor will keep
+         * track of all of teh labels it adds and will not add anything that's
+         * a duplicate.
+         */
+        noRepeats: false
+    },
+
+    labels: null,
+
+    init: function () {
+        this.parent();
+
+        if (this.options.noRepeat) {
+            this.labels = [];
+        }
+    },
+
+    attach: function (combo) {
+        this.parent(combo);
+
+        this.store.addEvent('storeDataLoaded', this.fill);
+        if (this.store.loaded) {
+            this.fill();
+        }
+    },
+
+    detach: function () {
+        this.parent();
+
+        this.store.removeEvent('storeDataLoaded', this.fill);
+    },
+
+    fill: function () {
+        var template,
+            items=[],
+            selected,
+            obj,
+            options = this.options,
+            noRepeat = this.options.noRepeat;
+        //empty the combo
+        this.widget.empty();
+        //reset the store and cycle through creating the objects
+        //to pass to combo.add()
+        this.store.first();
+        items = [];
+        this.store.each(function(record){
+            template = this.store.fillTemplate(record,options.template,this.columnsNeeded);
+            if (!noRepeat || (noRepeat && !this.labels.contains(template))) {
+                selected = false;
+                if ($type(options.selectedFn) == 'function') {
+                    selected = options.selectedFn.run(record);
+                }
+                obj = {
+                    label: template,
+                    image: record.get(options.imagePathColumn),
+                    imageClass: record.get(options.imageClassColumn),
+                    selected: selected
+                };
+                items.push(obj);
+
+                if (noRepeat) {
+                    this.labels.push(template);
+                }
+            }
+
+        },this);
+        //pass all of the objects at once
+        this.widget.add(items);
+    }
+});/*
+---
+
+name: Jx.Menu.Context
+
+description: A Jx.Menu that has no button but can be opened at a specific browser location to implement context menus (for instance).
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Context]
+
+css:
+ - menu
+
+...
+ */
+// $Id: context.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Menu.Context
+ *
+ * Extends: Jx.Menu
+ *
+ * A <Jx.Menu> that has no button but can be opened at a specific
+ * browser location to implement context menus (for instance).
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * TODO - add open/close events?
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Context = new Class({
+    Family: 'Jx.Menu.Context',
+    Extends: Jx.Menu,
+
+    parameters: ['id'],
+
+    /**
+     * APIMethod: render
+     * create a new context menu
+     */
+    render: function() {
+        this.id = document.id(this.options.id);
+        if (this.id) {
+            this.id.addEvent('contextmenu', this.show.bindWithEvent(this));
+        }
+        this.parent();
+    },
+    /**
+     * Method: show
+     * Show the context menu at the location of the mouse click
+     *
+     * Parameters:
+     * e - {Event} the mouse event
+     */
+    show : function(e) {
+        if (this.list.count() ==0) {
+            return;
+        }
+        
+        this.target = e.target;
+
+        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.setStyles({
+           width: null,
+           height: null
+         });
+        this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+        this.position(this.contentContainer, document.body, {
+            horizontal: [e.page.x + ' left'],
+            vertical: [e.page.y + ' top', e.page.y + ' bottom'],
+            offsets: this.chromeOffsets
+        });
+
+        this.contentContainer.setStyle('visibility','');
+        this.showChrome(this.contentContainer);
+
+        document.addEvent('mousedown', this.bound.hide);
+        document.addEvent('keyup', this.bound.keypress);
+
+        e.stop();
+    }
+});/*
+---
+
+name: Jx.Menu.Separator
+
+description: Convenience class to create a visual separator in a menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu
+
+provides: [Jx.Menu.Separator]
+
+images:
+ - toolbar_separator_v.png
+
+...
+ */
+// $Id: menu.separator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Menu.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A convenience class to create a visual separator in a menu.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.Separator = new Class({
+    Family: 'Jx.Menu.Separator',
+    Extends: Jx.Widget,
+    /**
+     * Property: domObj
+     * {HTMLElement} the HTML element that the separator is contained
+     * within
+     */
+    domObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu>, <Jx.Menu.SubMenu>} the menu that the separator is in.
+     */
+    owner: null,
+    options: {
+        template: "<li class='jxMenuItemContainer jxMenuItem'><span class='jxMenuSeparator'>&nbsp;</span></li>"
+    },
+    classes: new Hash({
+        domObj: 'jxMenuItem'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of a menu separator
+     */
+    render: function() {
+        this.parent();
+        this.domObj.store('jxMenuItem', this);
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxMenuItem');
+      this.owner = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the ownder of this menu item
+     *
+     * Parameters:
+     * obj - {Object} the new owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+    },
+    /**
+     * Method: hide
+     * Hide the menu item.
+     */
+    hide: $empty,
+    /**
+     * Method: show
+     * Show the menu item
+     */
+    show: $empty
+});/*
+---
+
+name: Jx.Menu.SubMenu
+
+description: A sub menu contains menu items within a main menu or another sub menu.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Menu.Item
+ - Jx.Menu
+
+provides: [Jx.Menu.SubMenu]
+
+...
+ */
+// $Id: submenu.js 1012 2011-03-03 20:37:26Z pagameba $
+/**
+ * Class: Jx.Menu.SubMenu
+ *
+ * Extends: <Jx.Menu.Item>
+ *
+ * Implements: <Jx.AutoPosition>, <Jx.Chrome>
+ *
+ * A sub menu contains menu items within a main menu or another
+ * sub menu.
+ *
+ * The structure of a SubMenu is the same as a <Jx.Menu.Item> with
+ * an additional unordered list element appended to the container.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Menu.SubMenu = new Class({
+    Family: 'Jx.Menu.SubMenu',
+    Extends: Jx.Menu.Item,
+    /**
+     * Property: subDomObj
+     * {HTMLElement} the HTML container for the sub menu.
+     */
+    subDomObj: null,
+    /**
+     * Property: owner
+     * {<Jx.Menu> or <Jx.SubMenu>} the menu or sub menu that this sub menu
+     * belongs
+     */
+    owner: null,
+    /**
+     * Property: visibleItem
+     * {<Jx.MenuItem>} the visible item within the menu
+     */
+    visibleItem: null,
+    /**
+     * Property: list
+     * {<Jx.List>} a list to manage menu items
+     */
+    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']
+        }
+    },
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.SubMenu
+     */
+    render: function() {
+        this.parent();
+        this.open = false;
+
+        this.menu = new Jx.Menu(null, {
+            position: this.options.position
+        });
+        this.menu.domObj = this.domObj;
+    },
+    cleanup: function() {
+      this.menu.domObj = null;
+      this.menu.destroy();
+      this.menu = null;
+      this.parent();
+    },
+    /**
+     * Method: setOwner
+     * Set the owner of this sub menu
+     *
+     * Parameters:
+     * obj - {Object} the owner
+     */
+    setOwner: function(obj) {
+        this.owner = obj;
+        this.menu.owner = obj;
+    },
+    /**
+     * Method: show
+     * Show the sub menu
+     */
+    show: function() {
+        if (this.open || this.menu.list.count() == 0) {
+            return;
+        }
+        this.menu.show();
+        this.open = true;
+        // this.setActive(true);
+    },
+
+    eventInMenu: function(e) {
+        if (this.visibleItem &&
+            this.visibleItem.eventInMenu &&
+            this.visibleItem.eventInMenu(e)) {
+            return true;
+        }
+        return document.id(e.target).descendantOf(this.domObj) ||
+               this.menu.eventInMenu(e);
+    },
+
+    /**
+     * Method: hide
+     * Hide the sub menu
+     */
+    hide: function() {
+        if (!this.open) {
+            return;
+        }
+        this.open = false;
+        this.menu.hide();
+        this.visibleItem = null;
+    },
+    /**
+     * Method: add
+     * Add menu items to the sub menu.
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to add.  Multiple menu items
+     * can be added by passing multiple arguments to this function.
+     */
+    add: function(item, position) {
+        this.menu.add(item, position, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a menu item from the menu
+     *
+     * Parameters:
+     * item - {<Jx.MenuItem>} the menu item to remove
+     */
+    remove: function(item) {
+        this.menu.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.menu.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: empty
+     * remove all items from the sub menu
+     */
+    empty: function() {
+      this.menu.empty();
+    },
+    /**
+     * Method: deactivate
+     * Deactivate the sub menu
+     *
+     * Parameters:
+     * e - {Event} the event that triggered the menu being
+     * deactivated.
+     */
+    deactivate: function(e) {
+        if (this.owner) {
+            this.owner.deactivate(e);
+        }
+    },
+    /**
+     * Method: isActive
+     * Indicate if this sub menu is active
+     *
+     * Returns:
+     * {Boolean} true if the <Jx.Menu> that ultimately contains
+     * this sub menu is active, false otherwise.
+     */
+    isActive: function() {
+        if (this.owner) {
+            return this.owner.isActive();
+        } else {
+            return false;
+        }
+    },
+    /**
+     * Method: setActive
+     * Set the active state of the <Jx.Menu> that contains this sub menu
+     *
+     * Parameters:
+     * isActive - {Boolean} the new active state
+     */
+    setActive: function(isActive) {
+        if (this.owner && this.owner.setActive) {
+            this.owner.setActive(isActive);
+        }
+    },
+    /**
+     * Method: setVisibleItem
+     * Set a sub menu of this menu to be visible and hide the previously
+     * visible one.
+     *
+     * Parameters:
+     * obj - {<Jx.SubMenu>} the sub menu that should be visible
+     */
+    setVisibleItem: function(obj) {
+        if (this.visibleItem != obj) {
+            if (this.visibleItem && this.visibleItem.hide) {
+                this.visibleItem.hide();
+            }
+            this.visibleItem = obj;
+            this.visibleItem.show();
+        }
+    }
+});/*
+---
+
+name: Jx.Splitter.Snap
+
+description: A helper class to create an element that can snap a split panel open or closed.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Splitter
+
+provides: [Jx.Splitter.Snap]
+
+...
+ */
+// $Id: snap.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Splitter.Snap
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class to create an element that can snap a split panel open or
+ * closed.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+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
+     * clicked).
+     */
+    snap: null,
+    /**
+     * Property: element
+     * {HTMLElement} An element of the <Jx.Splitter> that gets controlled
+     * by this snap
+     */
+    element: null,
+    /**
+     * Property: splitter
+     * {<Jx.Splitter>} the splitter that this snap is associated with.
+     */
+    splitter: null,
+    /**
+     * Property: layout
+     * {String} track the layout of the splitter for convenience.
+     */
+    layout: 'vertical',
+    /**
+     * 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.
+     */
+    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.layout = this.splitter.options.layout;
+        var jxo = jxl.options;
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            this.originalSize = size.height;
+            this.minimumSize = jxo.minHeight ? jxo.minHeight : 0;
+        } else {
+            this.originalSize = size.width;
+            this.minimumSize = jxo.minWidth ? jxo.minWidth : 0;
+        }
+        this.events.each(function(eventName) {
+            this.snap.addEvent(eventName, this.toggleElement.bind(this));
+        }, this);
+    },
+
+    /**
+     * Method: toggleElement
+     * Snap the element open or closed.
+     */
+    toggleElement: function() {
+        var size = this.element.getContentBoxSize();
+        var newSize = {};
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                newSize.height = this.originalSize;
+            } else {
+                this.originalSize = size.height;
+                newSize.height = this.minimumSize;
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                newSize.width = this.originalSize;
+            } else {
+                this.originalSize = size.width;
+                newSize.width = this.minimumSize;
+            }
+        }
+        this.element.resize(newSize);
+        this.splitter.sizeChanged();
+    },
+
+    /**
+     * Method: sizeChanged
+     * Handle the size of the element changing to see if the
+     * toggle state has changed.
+     */
+    sizeChange: function() {
+        var size = this.element.getContentBoxSize();
+        if (this.layout == 'vertical') {
+            if (size.height == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        } else {
+            if (size.width == this.minimumSize) {
+                this.snap.addClass('jxSnapClosed');
+                this.snap.removeClass('jxSnapOpened');
+            } else {
+                this.snap.addClass('jxSnapOpened');
+                this.snap.removeClass('jxSnapClosed');
+            }
+        }
+    }
+});/*
+---
+
+name: Jx.Tab
+
+description: A single tab in a tab set.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Button
+ - Jx.Layout
+
+provides: [Jx.Tab]
+
+css:
+ - tab
+
+images:
+ - tab_top.png
+ - tab_bottom.png
+ - tab_left.png
+ - tab_right.png
+ - tab_close.png
+
+...
+ */
+// $Id: tab.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Tab
+ *
+ * Extends: <Jx.Button>
+ *
+ * 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
+ * actual tab itself) in order to be useful.  Alternately, you can use
+ * a <Jx.TabBox> which combines both into a single control at the cost of
+ * some flexibility in layout options.
+ *
+ * A tab is a <Jx.ContentLoader> and you can specify the initial content of
+ * the tab using any of the methods supported by
+ * <Jx.ContentLoader::loadContent>.  You can acccess the actual DOM element
+ * that contains the content (if you want to dynamically insert content
+ * for instance) via the <Jx.Tab::content> property.
+ *
+ * A tab is a button of type *toggle* which means that it emits the *up*
+ * and *down* events.
+ *
+ * Example:
+ * (code)
+ * var tab1 = new Jx.Tab({
+ *     label: 'tab 1',
+ *     content: 'content1',
+ *     onDown: function(tab) {
+ *         console.log('tab became active');
+ *     },
+ *     onUp: function(tab) {
+ *         console.log('tab became inactive');
+ *     }
+ * });
+ * (end)
+ *
+ *
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Tab = new Class({
+    Family: 'Jx.Tab',
+    Extends: Jx.Button,
+    /**
+     * Property: content
+     * {HTMLElement} The content area that is displayed when the tab is
+     * active.
+     */
+    content: null,
+
+    options: {
+        /* Option: toggleClass
+         * the CSS class to use for the button, 'jxTabToggle' by default
+         */
+        toggleClass: 'jxTabToggle',
+        /* Option: pressedClass
+         * the CSS class to use when the tab is pressed, 'jxTabPressed' by
+         * default
+         */
+        pressedClass: 'jxTabPressed',
+        /* Option: activeClass
+         * the CSS class to use when the tab is active, 'jxTabActive' by 
+         * default.
+         */
+        activeClass: 'jxTabActive',
+        /* Option: activeTabClass
+         * the CSS class to use on the content area of the active tab,
+         * 'tabContentActive' by default.
+         */
+        activeTabClass: 'tabContentActive',
+        /* Option: template
+         * the HTML template for a tab
+         */
+        template: '<span class="jxTabContainer"><a class="jxTab"><span class="jxTabContent"><img class="jxTabIcon" src="'+Jx.aPixel.src+'"><span class="jxTabLabel"></span></span></a><a class="jxTabClose"></a></span>',
+        /* Option: contentTemplate
+         * the HTML template for a tab's content area
+         */
+        contentTemplate: '<div class="tabContent"></div>',
+        /* Option: close
+         * {Boolean} can the tab be closed by the user?  False by default.
+         */
+        close: false,
+        /* Option: shouldClose
+         * {Mixed} when a tab is closeable, the shouldClose option is checked
+         * first to see if the tab should close.  You can provide a function
+         * for this option that can be used to return a boolean value.  This
+         * is useful if your tab contains something the user can edit and you
+         * want to see if they want to discard the changes before closing.
+         * The default value is true, meaning the tab will close immediately.
+         * (code)
+         * new Jx.Tab({
+         *   label: 'test close',
+         *   close: true,
+         *   shouldClose: function() {
+         *     return window.confirm('Are you sure?');
+         *   }
+         * });
+         * (end)
+         */
+        shouldClose: true
+    },
+    /**
+     * Property: classes
+     * {<Hash>} a hash of object properties to CSS class names used to
+     * automatically extract references to important DOM elements when
+     * processing a widget template.  This allows developers to provide custom
+     * HTML structures without affecting the functionality of widgets.
+     */
+    classes: new Hash({
+        domObj: 'jxTabContainer',
+        domA: 'jxTab',
+        domImg: 'jxTabIcon',
+        domLabel: 'jxTabLabel',
+        domClose: 'jxTabClose',
+        content: 'tabContent'
+    }),
+
+    /**
+     * Method: render
+     * Create a new instance of Jx.Tab.  Any layout options passed are used
+     * to create a <Jx.Layout> for the tab content area.
+     */
+    render : function( ) {
+        this.options = $merge(this.options, {toggle:true});
+        this.parent();
+        this.domObj.store('jxTab', this);
+        this.processElements(this.options.contentTemplate, this.classes);
+        new Jx.Layout(this.content, this.options);
+        
+        // load content onDemand if needed
+        if(!this.options.loadOnDemand || this.options.active) {
+          this.loadContent(this.content);
+          // set active if needed
+          if(this.options.active) {
+            this.clicked();
+          }
+        }else{
+          this.addEvent('contentLoaded', function(ev) {
+            this.setActive(true);
+          }.bind(this));
+        }
+        this.addEvent('down', function(){
+            this.content.addClass(this.options.activeTabClass);
+        }.bind(this));
+        this.addEvent('up', function(){
+            this.content.removeClass(this.options.activeTabClass);
+        }.bind(this));
+
+        //remove the close button if necessary
+        if (this.domClose) {
+            if (this.options.close) {
+                this.domObj.addClass('jxTabClose');
+                this.domClose.addEvent('click', (function(){
+                  var shouldClose = true;
+                  if ($defined(this.options.shouldClose)) {
+                    if (typeof this.options.shouldClose == 'function') {
+                      shouldClose = this.options.shouldClose();
+                    } else {
+                      shouldClose = this.options.shouldClose;
+                    }
+                  }
+                  if (shouldClose) {
+                    this.fireEvent('close');
+                  }
+                }).bind(this));
+            } else {
+                this.domClose.dispose();
+            }
+        }
+    },
+    /**
+     * APIMethod: clicked
+     * triggered when the user clicks the button, processes the
+     * actionPerformed event
+     */
+    clicked : function(evt) {
+      if(this.options.enabled) {
+        // just set active when caching is enabled
+        if(this.contentIsLoaded && this.options.cacheContent) {
+          this.setActive(true);
+        // load on demand or reload content if caching is disabled
+        }else if(this.options.loadOnDemand || !this.options.cacheContent){
+          this.loadContent(this.content);
+        }else{
+          this.setActive(true);
+        }
+      }
+    }
+});
+
+/* keep the old location temporarily */
+Jx.Button.Tab = new Class({
+  Extends: Jx.Tab,
+  init: function() {
+    if (console.warn) {
+      console.warn('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    } else {
+      console.log('WARNING: Jx.Button.Tab has been renamed to Jx.Tab');
+    }
+    this.parent();
+  }
+});/*
+---
+
+name: Jx.TabSet
+
+description: A TabSet manages a set of Jx.Tab content areas by ensuring that only one of the content areas is visible (i.e. the active tab).
+
+license: MIT-style license.
+
+requires:
+ - Jx.Tab
+
+provides: [Jx.TabSet]
+
+...
+ */
+// $Id: tabset.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TabSet
+ *
+ * Extends: <Jx.Object>
+ *
+ * A TabSet manages a set of <Jx.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.Tab> that are to be managed
+ * as a set have to be added to both a TabSet and a <Jx.Toolbar>.  The content
+ * areas of the <Jx.Tab>s are sized to fit the content area that the TabSet
+ * is managing.
+ *
+ * Example:
+ * (code)
+ * var tabBar = new Jx.Toolbar('tabBar');
+ * var tabSet = new Jx.TabSet('tabArea');
+ *
+ * var tab1 = new Jx.Tab('tab 1', {contentID: 'content1'});
+ * var tab2 = new Jx.Tab('tab 2', {contentID: 'content2'});
+ * var tab3 = new Jx.Tab('tab 3', {contentID: 'content3'});
+ * var tab4 = new Jx.Tab('tab 4', {contentURL: 'test_content.html'});
+ *
+ * tabSet.add(t1, t2, t3, t4);
+ * tabBar.add(t1, t2, t3, t4);
+ * (end)
+ *
+ * Events:
+ * tabChange - the current tab has changed
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabSet = new Class({
+    Family: 'Jx.TabSet',
+    Extends: Jx.Object,
+    /**
+     * Property: tabs
+     * {Array} array of tabs that are managed by this tab set
+     */
+    tabs: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} The HTML element that represents this tab set in the DOM.
+     * The content areas of each tab are sized to fill the domObj.
+     */
+    domObj : null,
+    /**
+     * 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.
+     */
+    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 = document.id(this.options.domObj);
+        if (!this.domObj.hasClass('jxTabSetContainer')) {
+            this.domObj.addClass('jxTabSetContainer');
+        }
+        this.setActiveTabFn = this.setActiveTab.bind(this);
+    },
+    /**
+     * Method: resizeTabBox
+     * Resize the tab set content area and propogate the changes to
+     * each of the tabs managed by the tab set.
+     */
+    resizeTabBox: function() {
+        if (this.activeTab && this.activeTab.content.resize) {
+            this.activeTab.content.resize({forceResize: true});
+        }
+    },
+
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab set.  More
+     * than one tab can be added by passing extra parameters to this method.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(tab) {
+            if (tab instanceof Jx.Tab) {
+                tab.addEvent('down',this.setActiveTabFn);
+                tab.tabSet = this;
+                this.domObj.appendChild(tab.content);
+                this.tabs.push(tab);
+                if ((!this.activeTab || tab.options.active) && tab.options.enabled) {
+                    tab.options.active = false;
+                    tab.setActive(true);
+                }
+            }
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from this TabSet.  Note that it is the caller's responsibility
+     * to remove the tab from the <Jx.Toolbar>.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove: function(tab) {
+        if (tab instanceof Jx.Tab && this.tabs.indexOf(tab) != -1) {
+            this.tabs.erase(tab);
+            if (this.activeTab == tab) {
+                if (this.tabs.length) {
+                    this.tabs[0].setActive(true);
+                }
+            }
+            tab.removeEvent('down',this.setActiveTabFn);
+            tab.content.dispose();
+        }
+    },
+    /**
+     * Method: setActiveTab
+     * Set the active tab to the one passed to this method
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to make active.
+     */
+    setActiveTab: function(tab) {
+        if (this.activeTab && this.activeTab != tab) {
+            this.activeTab.setActive(false);
+        }
+        this.activeTab = tab;
+        if (this.activeTab.content.resize) {
+          this.activeTab.content.resize({forceResize: true});
+        }
+        this.fireEvent('tabChange', [this, tab]);
+    }
+});
+
+
+
+/*
+---
+
+name: Jx.TabBox
+
+description: A convenience class to handle the common case of a single toolbar directly attached to the content area of the tabs.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+ - Jx.Panel
+ - Jx.TabSet
+
+provides: [Jx.TabBox]
+
+images:
+ - tabbar.png
+ - tabbar_bottom.png
+ - tabbar_left.png
+ - tabbar_right.png
+
+...
+ */
+// $Id: tabbox.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TabBox
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * a TabBox, then tabs only have to be added to the TabBox rather than to
+ * both a <Jx.TabSet> and a <Jx.Toolbar>.
+ *
+ * Example:
+ * (code)
+ * var tabBox = new Jx.TabBox('subTabArea', 'top');
+ *
+ * var tab1 = new Jx.Button.Tab('Tab 1', {contentID: 'content4'});
+ * var tab2 = new Jx.Button.Tab('Tab 2', {contentID: 'content5'});
+ *
+ * tabBox.add(tab1, tab2);
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TabBox = new Class({
+    Family: 'Jx.TabBox',
+    Extends: Jx.Widget,
+    options: {
+        /* Option: parent
+         * a DOM element to add the tab box to
+         */
+        parent: null,
+        /* Option: position
+         * the position of the tab bar in the box, one of 'top', 'right',
+         * 'bottom' or 'left'.  Top by default.
+         */
+        position: 'top',
+        /* Option: height
+         * a fixed height in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        height: null,
+        /* Option: width
+         * a fixed width in pixels for the tab box.  If not set, it will fill
+         * its container
+         */
+        width: null,
+        /* Option: scroll
+         * should the tab bar scroll its tabs if there are too many to fit
+         * in the toolbar, true by default
+         */
+        scroll:true
+    },
+
+    /**
+     * Property: tabBar
+     * {<Jx.Toolbar>} the toolbar for this tab box.
+     */
+    tabBar: null,
+    /**
+     * Property: tabSet
+     * {<Jx.TabSet>} the tab set for this tab box.
+     */
+    tabSet: null,
+    /**
+     * APIMethod: render
+     * Create a new instance of a TabBox.
+     */
+    render : function() {
+        this.parent();
+        this.tabBar = new Jx.Toolbar({
+            position: this.options.position,
+            scroll: this.options.scroll
+        });
+        this.panel = new Jx.Panel({
+            toolbars: [this.tabBar],
+            hideTitle: true,
+            height: this.options.height,
+            width: this.options.width
+        });
+        this.panel.domObj.addClass('jxTabBox');
+        this.tabSet = new Jx.TabSet(this.panel.content);
+        this.tabSet.addEvent('tabChange', function(tabSet, tab) {
+            this.showItem(tab);
+        }.bind(this.tabBar));
+        this.domObj = this.panel.domObj;
+        /* when the panel changes size, the tab set needs to update
+         * the content areas.
+         */
+         this.panel.addEvent('sizeChange', (function() {
+             this.tabSet.resizeTabBox();
+             this.tabBar.domObj.getParent('.jxBarContainer').retrieve('jxBarContainer').update();
+             this.tabBar.domObj.getParent('.jxBarContainer').addClass('jxTabBar'+this.options.position.capitalize());
+         }).bind(this));
+        /* when tabs are added or removed, we might need to layout
+         * the panel if the toolbar is or becomes empty
+         */
+        this.tabBar.addEvents({
+            add: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this),
+            remove: (function() {
+                this.domObj.resize({forceResize: true});
+            }).bind(this)
+        });
+        /* trigger an initial resize when first added to the DOM */
+        this.addEvent('addTo', function() {
+            this.domObj.resize({forceResize: true});
+        });
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * Method: add
+     * Add one or more <Jx.Tab>s to the TabBox.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} an instance of <Jx.Tab> to add to the tab box.  More
+     * than one tab can be added by passing extra parameters to this method.
+     * Unlike <Jx.TabSet>, tabs do not have to be added to a separate
+     * <Jx.Toolbar>.
+     */
+    add : function() {
+        this.tabBar.add.apply(this.tabBar, arguments);
+        this.tabSet.add.apply(this.tabSet, arguments);
+        $A(arguments).flatten().each(function(tab){
+            tab.addEvents({
+                close: (function(){
+                    this.tabBar.remove(tab);
+                    this.tabSet.remove(tab);
+                }).bind(this)
+            });
+        }, this);
+        return this;
+    },
+    /**
+     * Method: remove
+     * Remove a tab from the TabSet.
+     *
+     * Parameters:
+     * tab - {<Jx.Tab>} the tab to remove.
+     */
+    remove : function(tab) {
+        this.tabBar.remove(tab);
+        this.tabSet.remove(tab);
+    }
+});
+/*
+---
+
+name: Jx.Toolbar.Separator
+
+description:  A helper class that represents a visual separator in a Jx.Toolbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Toolbar
+
+provides: [Jx.Toolbar.Separator]
+
+images:
+ - toolbar_separator_h.png
+ - toolbar_separator_v.png
+
+...
+ */
+// $Id: toolbar.separator.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Toolbar.Separator
+ *
+ * Extends: <Jx.Object>
+ *
+ * A helper class that represents a visual separator in a <Jx.Toolbar>
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Toolbar.Separator = new Class({
+    Family: 'Jx.Toolbar.Separator',
+    Extends: Jx.Widget,
+    /**
+     * APIMethod: render
+     * Create a new Jx.Toolbar.Separator
+     */
+    render: function() {
+        this.domObj = new Element('li', {'class':'jxToolItem'});
+        this.domSpan = new Element('span', {'class':'jxBarSeparator'});
+        this.domObj.appendChild(this.domSpan);
+    }
+});
+/*
+---
+
+name: Jx.Tree
+
+description: Jx.Tree displays hierarchical data in a tree structure of folders and nodes.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - Jx.List
+
+provides: [Jx.Tree]
+
+css:
+ - tree
+
+images:
+ - tree.png
+ - tree_vert_line.png
+...
+ */
+// $Id: tree.js 1011 2011-01-24 18:18:42Z pagameba $
+/**
+ * Class: Jx.Tree
+ *
+ * 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'],
+    pluginNamespace: 'Tree',
+    /**
+     * APIProperty: selection
+     * {<Jx.Selection>} the selection object for this tree.
+     */
+    selection: null,
+    /**
+     * Property: ownsSelection
+     * {Boolean} indicates if this object created the <Jx.Selection> object
+     * or not.  If true then the selection object will be destroyed when the
+     * tree is destroyed, otherwise the selection object will not be
+     * destroyed.
+     */
+    ownsSelection: false,
+    /**
+     * Property: list
+     * {<Jx.List>} the list object is used to manage the DOM elements of the
+     * items added to the tree.
+     */
+    list: null,
+    dirty: true,
+    /**
+     * APIProperty: domObj
+     * {HTMLElement} the DOM element that contains the visual representation
+     * of the tree.
+     */
+    domObj: null,
+    options: {
+        /**
+         * Option: select
+         * {Boolean} are items in the tree selectable?  See <Jx.Selection>
+         * for other options relating to selections that can be set here.
+         */
+        select: true,
+        /**
+         * Option: template
+         * the default HTML template for a tree can be overridden
+         */
+        template: '<ul class="jxTreeRoot"></ul>'
+    },
+    /**
+     * APIProperty: classes
+     * {Hash} a hash of property to CSS class names for extracting references
+     * to DOM elements from the supplied templates.  Requires
+     * domObj element, anything else is optional.
+     */
+    classes: new Hash({domObj: 'jxTreeRoot'}),
+    
+    frozen: false,
+    
+    /**
+     * APIMethod: render
+     * Render the Jx.Tree.
+     */
+    render: function() {
+        this.parent();
+        if ($defined(this.options.container) &&
+            document.id(this.options.container)) {
+            this.domObj = this.options.container;
+        }
+
+        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.bound.select = function(item) {
+            this.fireEvent('select', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.unselect = function(item) {
+            this.fireEvent('unselect', item.retrieve('jxTreeItem'));
+        }.bind(this);
+        this.bound.onAdd = function(item) {this.update();}.bind(this);
+        this.bound.onRemove = function(item) {this.update();}.bind(this);
+
+        if (this.selection && this.ownsSelection) {
+            this.selection.addEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+        }
+
+        this.list = new Jx.List(this.domObj, {
+                hover: true,
+                press: true,
+                select: true,
+                onAdd: this.bound.onAdd,
+                onRemove: this.bound.onRemove
+            }, this.selection);
+        if (this.options.parent) {
+            this.addTo(this.options.parent);
+        }
+    },
+    /**
+     * APIMethod: freeze
+     * stop the tree from processing updates every time something is added or
+     * removed.  Used for bulk changes, call thaw() when done updating.  Note
+     * the tree will still display the changes but it will delay potentially
+     * expensive recursion across the entire tree on every change just to
+     * update visual styles.
+     */
+    freeze: function() { this.frozen = true; },
+    /**
+     * APIMethod: thaw
+     * unfreeze the tree and recursively update styles
+     */
+    thaw: function() { this.frozen = false; this.update(true); },
+    
+    setDirty: function(state) {
+      this.dirty = state;
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+
+    /**
+     * APIMethod: add
+     * add one or more items to the tree at a particular position in the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        if ($type(item) == 'array') {
+            item.each(function(what){ this.add(what, position); }.bind(this) );
+            return;
+        }
+        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);
+        item.owner = this;
+        this.list.add(item, position);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the tree
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        item.removeEvents('add');
+        item.removeEvents('remove');
+        item.removeEvents('disclose');
+        item.owner = null;
+        this.list.remove(item);
+        item.setSelection(null);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        item.owner = null;
+        withItem.owner = this;
+        this.list.replace(item, withItem);
+        withItem.setSelection(this.selection);
+        item.setSelection(null);
+        this.setDirty(true);
+        this.update(true);
+        return this;
+    },
+
+    /**
+     * Method: cleanup
+     * Clean up a Jx.Tree instance
+     */
+    cleanup: function() {
+        // stop updates when removing existing items.
+        this.freeze();
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents({
+                select: this.bound.select,
+                unselect: this.bound.unselect
+            });
+            this.selection.destroy();
+            this.selection = null;
+        }
+        this.list.removeEvents({
+          remove: this.bound.onRemove,
+          add: this.bound.onAdd
+        });
+        this.list.destroy();
+        this.list = null;
+        this.bound.select = null;
+        this.bound.unselect = null;
+        this.bound.onRemove = null;
+        this.bound.onAdd = null;
+        this.parent();
+    },
+    
+    /**
+     * 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?
+     */
+    update: function(shouldDescend, isLast) {
+        // since the memory leak cleanup, it seems that update gets called
+        // from the bound onRemove event after the list has been cleaned
+        // up.  I suspect that there is a delayed function call for IE in
+        // event handling (or some such thing) PS
+        if (!this.list) return;
+        if (this.frozen) return;
+        
+        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);
+            }
+        });
+        this.setDirty(false);
+    },
+
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.list.items().map(function(item) {
+            return item.retrieve('jxTreeItem');
+        });
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this tree and any folders in it
+     */
+    empty: function() {
+        this.list.items().each(function(item){
+          var f = item.retrieve('jxTreeItem');
+          if (f && f instanceof Jx.TreeFolder) {
+            f.empty();
+          }
+          if (f && f instanceof Jx.TreeItem) {
+            this.remove(f);
+            f.destroy();
+          }
+        }, this);
+        this.setDirty(true);
+    },
+
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return false;
+        }
+        //path has more than one thing in it, find a folder and descend into it
+        var name = path[0];
+        var result = false;
+        this.list.items().some(function(item) {
+            var treeItem = item.retrieve('jxTreeItem');
+            if (treeItem && treeItem.getLabel() == name) {
+                if (path.length > 0) {
+                    var folder = item.retrieve('jxTreeFolder');
+                    if (folder && path.length > 1) {
+                        result = folder.findChild(path.slice(1, path.length));
+                    } else {
+                      result = treeItem;
+                    }
+                } else {
+                    result = treeItem;
+                }
+            }
+            return result;
+        });
+        return result;
+    },
+    
+    /**
+     * APIMethod: setSelection
+     * sets the <Jx.Selection> object to be used by this tree.  Used primarily
+     * by <Jx.TreeFolder> to propogate a single selection object throughout a
+     * tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining
+     */
+    setSelection: function(selection) {
+        if (this.selection && this.ownsSelection) {
+            this.selection.removeEvents(this.bound);
+            this.selection.destroy();
+            this.ownsSelection = false;
+        }
+        this.selection = selection;
+        this.list.setSelection(selection);
+        this.list.each(function(item) {
+            item.retrieve('jxTreeItem').setSelection(selection);
+        });
+        return this;
+    }
+});
+
+/*
+---
+
+name: Jx.TreeItem
+
+description: An item in a tree.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+
+optional:
+ - More/Drag
+
+provides: [Jx.TreeItem]
+
+images:
+ - tree_hover.png
+
+...
+ */
+// $Id: treeitem.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.TreeItem
+ *
+ * Extends: <Jx.Widget>
+ *
+ * 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
+ * is responsible for changing the style of the selected item in the tree
+ * and for tracking selection if that is important.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * click - triggered when the tree item is clicked
+ *
+ * Implements:
+ * Events - MooTools Class.Extras
+ * Options - MooTools Class.Extras
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeItem = new Class ({
+    Family: 'Jx.TreeItem',
+    Extends: Jx.Widget,
+    selection: null,
+    /**
+     * Property: domObj
+     * {HTMLElement} a reference to the HTML element that is the TreeItem
+     * in the DOM
+     */
+    domObj : null,
+    /**
+     * Property: owner
+     * {Object} the folder or tree that this item belongs to
+     */
+    owner: null,
+    options: {
+        /* Option: label
+         * {String} the label to display for the TreeItem
+         */
+        label: '',
+        /* Option: contextMenu
+         * {<Jx.ContextMenu>} the context menu to trigger if there
+         * is a right click on the node
+         */
+        contextMenu: null,
+        /* Option: enabled
+         * {Boolean} the initial state of the TreeItem.  If the
+         * TreeItem is not enabled, it cannot be clicked.
+         */
+        enabled: true,
+        selectable: true,
+        /* Option: image
+         * {String} URL to an image to use as the icon next to the
+         * label of this TreeItem
+         */
+        image: null,
+        /* Option: imageClass
+         * {String} CSS class to apply to the image, useful for using CSS
+         * sprites
+         */
+        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>',
+        busyMask: {
+          message: null
+        }
+    },
+    classes: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel'
+    }),
+
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeItem with the associated options
+     */
+    render : function() {
+        this.parent();
+
+        this.domObj = this.elements.get('jxTreeContainer');
+        this.domObj.store('jxTreeItem', this);
+        this.domA.store('jxTreeItem', this);
+        if (this.options.contextMenu) {
+          this.domA.store('jxContextMenu', this.options.contextMenu);
+        }
+        /* the target for jxPressed, jxSelected, jxHover classes */
+        this.domObj.store('jxListTarget', this.domA);
+
+        if (!this.options.selectable) {
+            this.domObj.addClass('jxUnselectable');
+        }
+
+        if (this.options.id) {
+            this.domObj.id = this.options.id;
+        }
+        if (!this.options.enabled) {
+            this.domObj.addClass('jxDisabled');
+        }
+
+        if (this.options.image && this.domIcon) {
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            if (this.options.imageClass) {
+                this.domIcon.addClass(this.options.imageClass);
+            }
+
+        }
+
+        if (this.options.label && this.domLabel) {
+            this.setLabel(this.options.label);
+        }
+
+        if (this.domA) {
+            this.domA.addEvents({
+                click: this.click.bind(this),
+                dblclick: this.dblclick.bind(this),
+                drag: function(e) { e.stop(); }
+            });
+            if (typeof Drag != 'undefined') {
+                new Drag(this.domA, {
+                    onStart: function() {this.stop();}
+                });
+            }
+        }
+
+        if ($defined(this.options.enabled)) {
+            this.enable(this.options.enabled, true);
+        }
+    },
+    
+    setDirty: function(state) {
+      if (state && this.owner && this.owner.setDirty) {
+        this.owner.setDirty(state);
+      }
+    },
+    
+    /**
+     * Method: finalize
+     * Clean up the TreeItem and remove all DOM references
+     */
+    finalize: function() { this.destroy(); },
+    /**
+     * Method: finalizeItem
+     * Clean up the TreeItem and remove all DOM references
+     */
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeItem');
+      this.domA.eliminate('jxTreeItem');
+      this.domA.eliminate('jxContextMenu');
+      this.domObj.eliminate('jxListTarget');
+      this.domObj.eliminate('jxListTargetItem');
+      this.domA.removeEvents();
+      this.owner = null;
+      this.selection = null;
+      this.parent();
+    },
+    /**
+     * Method: update
+     * Update the CSS of the TreeItem's DOM element in case it has changed
+     * position
+     *
+     * Parameters:
+     * isLast - {Boolean} is the item the last one or not?
+     */
+    update : function(isLast) {
+        if (isLast) {
+            this.domObj.addClass(this.options.lastLeafClass);
+        } else {
+            this.domObj.removeClass(this.options.lastLeafClass);
+        }
+    },
+    click: function() {
+        if (this.options.enabled) {
+            this.fireEvent('click', this);
+        }
+    },
+    dblclick: function() {
+        if (this.options.enabled) {
+            this.fireEvent('dblclick', this);
+        }
+    },
+    /**
+     * Method: select
+     * Select a tree node.
+     */
+    select: function() {
+        if (this.selection && this.options.enabled) {
+            this.selection.select(this.domA);
+        }
+    },
+
+    /**
+     * Method: getLabel
+     * Get the label associated with a TreeItem
+     *
+     * Returns:
+     * {String} the name
+     */
+    getLabel: function() {
+        return this.options.label;
+    },
+
+    /**
+     * Method: setLabel
+     * set the label of a tree item
+     */
+    setLabel: function(label) {
+        this.options.label = label;
+        if (this.domLabel) {
+            this.domLabel.set('html',this.getText(label));
+            this.setDirty(true);
+        }
+    },
+
+    setImage: function(url, imageClass) {
+        if (this.domIcon && $defined(url)) {
+            this.options.image = url;
+            this.domIcon.setStyle('backgroundImage', 'url('+this.options.image+')');
+            this.setDirty(true);
+        }
+        if (this.domIcon && $defined(imageClass)) {
+            this.domIcon.removeClass(this.options.imageClass);
+            this.options.imageClass = imageClass;
+            this.domIcon.addClass(imageClass);
+            this.setDirty(true);
+        }
+    },
+    enable: function(state, force) {
+        if (this.options.enabled != state || force) {
+            this.options.enabled = state;
+            if (this.options.enabled) {
+                this.domObj.removeClass('jxDisabled');
+                this.fireEvent('enabled', this);
+            } else {
+                this.domObj.addClass('jxDisabled');
+                this.fireEvent('disabled', this);
+                if (this.selection) {
+                    this.selection.unselect(document.id(this));
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: propertyChanged
+     * A property of an object has changed, synchronize the state of the
+     * TreeItem with the state of the object
+     *
+     * Parameters:
+     * obj - {Object} the object whose state has changed
+     */
+    propertyChanged : function(obj) {
+        this.options.enabled = obj.isEnabled();
+        if (this.options.enabled) {
+            this.domObj.removeClass('jxDisabled');
+        } else {
+            this.domObj.addClass('jxDisabled');
+        }
+    },
+    setSelection: function(selection){
+        this.selection = selection;
+    },
+    
+    /**
+     * APIMethod: setBusy
+     * set the busy state of the widget
+     *
+     * Parameters:
+     * busy - {Boolean} true to set the widget as busy, false to set it as
+     *    idle.
+     */
+    setBusy: function(state) {
+      if (this.busy == state) {
+        return;
+      }
+      this.busy = state;
+      this.fireEvent('busy', this.busy);
+      if (this.busy) {
+        this.domImg.addClass(this.options.busyClass)
+      } else {
+        if (this.options.busyClass) {
+          this.domImg.removeClass(this.options.busyClass);
+        }
+      }
+    },
+    changeText : function(lang) {
+      this.parent();
+      this.setLabel(this.options.label);
+    }
+});
+/*
+---
+
+name: Jx.TreeFolder
+
+description: A Jx.TreeFolder is an item in a tree that can contain other items. It is expandable and collapsible.
+
+license: MIT-style license.
+
+requires:
+ - Jx.TreeItem
+ - Jx.Tree
+
+provides: [Jx.TreeFolder]
+
+...
+ */
+// $Id: treefolder.js 1011 2011-01-24 18:18:42Z pagameba $
+/**
+ * Class: Jx.TreeFolder
+ *
+ * A Jx.TreeFolder is an item in a tree that can contain other items.  It is
+ * expandable and collapsible.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Extends:
+ * <Jx.TreeItem>
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.TreeFolder = new Class({
+    Family: 'Jx.TreeFolder',
+    Extends: Jx.TreeItem,
+    /**
+     * Property: tree
+     * {<Jx.Tree>} a Jx.Tree instance for managing the folder contents
+     */
+    tree : null,
+    
+    options: {
+        /* Option: open
+         * is the folder open?  false by default.
+         */
+        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: new Hash({
+        domObj: 'jxTreeContainer',
+        domA: 'jxTreeItem',
+        domImg: 'jxTreeImage',
+        domIcon: 'jxTreeIcon',
+        domLabel: 'jxTreeLabel',
+        domTree: 'jxTree'
+    }),
+    /**
+     * APIMethod: render
+     * Create a new instance of Jx.TreeFolder
+     */
+    render : function() {
+        this.parent();
+        this.domObj.store('jxTreeFolder', this);
+
+        this.bound.toggle = this.toggle.bind(this);
+
+        this.addEvents({
+            click: this.bound.toggle,
+            dblclick: this.bound.toggle
+        });
+
+        if (this.domImg) {
+            this.domImg.addEvent('click', this.bound.toggle);
+        }
+
+        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.domTree);
+        if (this.options.open) {
+            this.expand();
+        } else {
+            this.collapse();
+        }
+
+    },
+    cleanup: function() {
+      this.domObj.eliminate('jxTreeFolder');
+      this.removeEvents({
+        click: this.bound.toggle,
+        dblclick: this.bound.toggle
+      });
+      if (this.domImg) {
+        this.domImg.removeEvent('click', this.bound.toggle);
+      }
+      this.bound.toggle = null;
+      this.tree.destroy();
+      this.tree = null;
+      this.parent();
+    },
+    /**
+     * APIMethod: add
+     * add one or more items to the folder at a particular position in the
+     * folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} or an array of items to be added
+     * position - {mixed} optional location to add the items.  By default,
+     * this is 'bottom' meaning the items are added at the end of the list.
+     * See <Jx.List::add> for options
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this object for chaining calls
+     */
+    add: function(item, position) {
+        this.tree.add(item, position);
+        return this;
+    },
+    /**
+     * APIMethod: remove
+     * remove an item from the folder
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the folder item to remove
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    remove: function(item) {
+        this.tree.remove(item);
+        return this;
+    },
+    /**
+     * APIMethod: replace
+     * replaces one item with another
+     *
+     * Parameters:
+     * item - {<Jx.TreeItem>} the tree item to remove
+     * withItem - {<Jx.TreeItem>} the tree item to insert
+     *
+     * Returns:
+     * {<Jx.Tree>} a reference to this object for chaining calls
+     */
+    replace: function(item, withItem) {
+        this.tree.replace(item, withItem);
+        return this;
+    },
+    /**
+     * APIMethod: items
+     * return an array of tree item instances contained in this tree.
+     * Does not descend into folders but does return a reference to the
+     * folders
+     */
+    items: function() {
+        return this.tree.items();
+    },
+    /**
+     * APIMethod: empty
+     * recursively empty this folder and any folders in it
+     */
+    empty: function() {
+        this.tree.empty();
+    },
+    /**
+     * 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?
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    update: function(shouldDescend,isLast) {
+        /* avoid update if not attached to tree yet */
+        if (!this.domObj.parentNode) return;
+        
+        if (this.tree.dirty || (this.owner && this.owner.dirty)) {
+          if (!$defined(isLast)) {
+              isLast = this.domObj.hasClass('jxTreeBranchLastOpen') ||
+                       this.domObj.hasClass('jxTreeBranchLastClosed');
+          }
+
+          ['jxTreeBranchOpen','jxTreeBranchLastOpen','jxTreeBranchClosed',
+          'jxTreeBranchLastClosed'].each(function(c){
+              this.removeClass(c);
+          }, this.domObj);
+
+          var c = 'jxTreeBranch';
+          c += isLast ? 'Last' : '';
+          c += this.options.open ? 'Open' : 'Closed';
+          this.domObj.addClass(c);
+        }
+
+        this.tree.update(shouldDescend, isLast);
+    },
+    /**
+     * APIMethod: toggle
+     * toggle the state of the folder between open and closed
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    toggle: function() {
+        if (this.options.enabled) {
+            if (this.options.open) {
+                this.collapse();
+            } else {
+                this.expand();
+            }
+        }
+        return this;
+    },
+    /**
+     * APIMethod: expand
+     * Expands the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    expand : function() {
+        this.options.open = true;
+        document.id(this.tree).setStyle('display', 'block');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: collapse
+     * Collapses the folder
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    collapse : function() {
+        this.options.open = false;
+        document.id(this.tree).setStyle('display', 'none');
+        this.setDirty(true);
+        this.update(true);
+        this.fireEvent('disclosed', this);
+        return this;
+    },
+    /**
+     * APIMethod: 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:
+     * {Object} the node or null if the path was not found
+     */
+    findChild : function(path) {
+        //path is empty - we are asking for this node
+        if (path.length == 0) {
+            return this;
+        } else {
+            return this.tree.findChild(path);
+        }
+    },
+    /**
+     * Method: setSelection
+     * sets the <Jx.Selection> object to be used by this folder.  Used
+     * to propogate a single selection object throughout a tree.
+     *
+     * Parameters:
+     * selection - {<Jx.Selection>} the new selection object to use
+     *
+     * Returns:
+     * {<Jx.TreeFolder>} a reference to this for chaining
+     */
+    setSelection: function(selection) {
+        this.tree.setSelection(selection);
+        return this;
+    },
+    
+    setDirty: function(state) {
+      this.parent(state);
+      if (this.tree) {
+        this.tree.setDirty(true);
+      }
+    }
+    
+});/*
+---
+
+name: Jx.Slider
+
+description: A wrapper for mootools' slider class to make it more Jx Friendly.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Widget
+ - More/Slider
+
+provides: [Jx.Slider]
+
+css:
+ - slider
+
+...
+ */
+// $Id: slider.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Slider
+ * This class wraps the mootools-more slider class to make it more Jx friendly
+ *
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ */
+Jx.Slider = new Class({
+    Family: 'Jx.Slider',
+    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
+    },
+    classes: new Hash({
+        domObj: 'jxSliderContainer',
+        knob: 'jxSliderKnob'
+    }),
+    slider: null,
+    knob: null,
+    sliderOpts: null,
+    /**
+     * 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.
+     */
+    render: function () {
+        this.parent();
+        
+        /** 
+         * Not sure why this is here...
+         */
+        /**
+        if (this.domObj) {
+            return;
+        }
+        **/
+
+        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 moving and 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);
+    },
+    /**
+     * APIMethod: set
+     * set the value of the slider
+     */
+    set: function(value) {
+      this.slider.set(value);
+    }
+});/*
+---
+
+name: Jx.Notice
+
+description: Represents a single item used in a notifier.
+
+license: MIT-style license.
+
+requires:
+ - Jx.ListItem
+
+provides: [Jx.Notice]
+
+images:
+ - notice.png
+ - notice_error.png
+ - notice_warning.png
+ - notice_success.png
+ - icons.png
+
+
+...
+ */
+// $Id: notice.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notice
+ *
+ * Extends: <Jx.ListItem>
+ *
+ * Events:
+ * 
+ * MooTools.lang Keys:
+ * - notice.closeTip
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notice = new Class({
+
+    Family: 'Jx.Notice',
+    Extends: Jx.ListItem,
+
+    options: {
+        /**
+         * Option: fx
+         * the effect to use on the notice when it is shown and hidden,
+         * 'fade' by default
+         */
+        fx: 'fade',
+        /**
+         * Option: chrome
+         * {Boolean} should the notice be displayed with chrome or not,
+         * default is false
+         */
+        chrome: false,
+        /**
+         * Option: enabled
+         * {Boolean} default is false
+         */
+        enabled: true,
+        /**
+         * Option: template
+         * {String} the HTML template of a notice
+         */
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        /**
+         * Option: klass
+         * {String} css class to add to the notice
+         */
+        klass: ''
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeItemContainer',
+        domItem: 'jxNoticeItem',
+        domContent: 'jxNotice',
+        domClose: 'jxNoticeClose'
+    }),
+
+    /**
+     * Method: render
+     */
+    render: function () {
+        this.parent();
+        
+        if (this.options.klass) {
+            this.domObj.addClass(this.options.klass);
+        }
+        if (this.domClose) {
+            this.domClose.addEvent('click', this.close.bind(this));
+        }
+    },
+    /**
+     * APIMethod: close
+     * close the notice
+     */
+    close: function() {
+        this.fireEvent('close', this);
+    },
+    /**
+     * APIMethod: show
+     * show the notice
+     */
+    show: function(el, onComplete) {
+        if (this.options.chrome) {
+            this.showChrome();
+        }
+        if (this.options.fx) {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        } else {
+            document.id(el).adopt(this);
+            if (onComplete) onComplete();
+        }
+    },
+    /**
+     * APIMethod: hide
+     * hide the notice
+     */
+    hide: function(onComplete) {
+        if (this.options.chrome) {
+            this.hideChrome();
+        }
+        if (this.options.fx) {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        } else {
+            document.id(this).dispose();
+            if (onComplete) onComplete();
+        }
+    },
+
+    changeText : function(lang) {
+        this.parent();
+        //this.render();
+        //this.processElements(this.options.template, this.classes);
+    }
+});
+/**
+ * Class: Jx.Notice.Information
+ * A <Jx.Notice> subclass useful for displaying informational messages
+ */
+Jx.Notice.Information = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeInformation'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying success messages
+ */
+Jx.Notice.Success = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Success"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeSuccess'
+    }
+});
+/**
+ * Class: Jx.Notice.Success
+ * A <Jx.Notice> subclass useful for displaying warning messages
+ */
+Jx.Notice.Warning = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Warning"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeWarning'
+    }
+});
+/**
+ * Class: Jx.Notice.Error
+ * A <Jx.Notice> subclass useful for displaying error messages
+ */
+Jx.Notice.Error = new Class({
+    Extends: Jx.Notice,
+    options: {
+        template: '<li class="jxNoticeItemContainer"><div class="jxNoticeItem"><img class="jxNoticeIcon" src="'+Jx.aPixel.src+'" title="Error"><span class="jxNotice"></span><a class="jxNoticeClose" href="javascript:void(0);" title="' + MooTools.lang.get('Jx','notice').closeTip + '"></a></div></li>',
+        klass: 'jxNoticeError'
+    }
+});
+/*
+---
+
+name: Jx.Notifier
+
+description: Base class for notification areas that can hold temporary notices.
+
+license: MIT-style license.
+
+requires:
+ - Jx.ListView
+ - Jx.Notice
+ - Core/Fx.Tween
+
+provides: [Jx.Notifier]
+
+css:
+ - notification
+
+
+...
+ */
+// $Id: notifier.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notifier
+ *
+ * Extends: <Jx.ListView>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier = new Class({
+    
+    Family: 'Jx.Notifier',
+    Extends: Jx.ListView,
+    
+    options: {
+        /**
+         * Option: parent
+         * The parent this notifier is to be placed in. If not specified, it
+         * will be placed in the body of the document.
+         */
+        parent: null,
+        /**
+         * Option: template
+         * This is the template for the notification container itself, not the
+         * actual notice. The actual notice is below in the class property 
+         * noticeTemplate.
+         */
+        template: '<div class="jxNoticeListContainer"><ul class="jxNoticeList"></ul></div>',
+        /**
+         * Option: listOptions
+         * An object holding custom options for the internal Jx.List instance.
+         */
+        listOptions: { }
+    },
+
+    classes: new Hash({
+        domObj: 'jxNoticeListContainer',
+        listObj: 'jxNoticeList'
+    }),
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        
+        if (!$defined(this.options.parent)) {
+            this.options.parent = document.body;
+        }
+        document.id(this.options.parent).adopt(this.domObj);
+        
+        this.addEvent('postRender', function() {
+            if (Jx.type(this.options.items) == 'array') {
+                this.options.items.each(function(item){
+                    this.add(item);
+                },this);
+            }
+        }.bind(this));
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function (notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.addEvent('close', this.remove.bind(this));
+        notice.show(this.listObj);
+    },
+    
+    /**
+     * APIMethod: remove
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to remove
+     */
+    remove: function (notice) {
+        if (this.domObj.hasChild(notice)) {
+            notice.removeEvents('close');
+            notice.hide();
+        }
+    }
+});/*
+---
+
+name: Jx.Notifier.Float
+
+description: A notification area that floats in a container above other content.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Notifier
+
+provides: [Jx.Notifier.Float]
+
+...
+ */
+// $Id: notifier.float.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Notifier.Float
+ * A floating notice area for displaying notices, notices get chrome if
+ * the notifier has chrome
+ *
+ * Extends: <Jx.Notifier>
+ *
+ * Events:
+ *
+ * License:
+ * Copyright (c) 2009, DM Solutions Group.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Notifier.Float = new Class({
+    
+    Family: 'Jx.Notifier.Float',
+    Extends: Jx.Notifier,
+    
+    options: {
+        /**
+         * Option: chrome
+         * {Boolean} should the notifier have chrome - default true
+         */
+        chrome: true,
+        /**
+         * Option: fx
+         * {String} the effect to use when showing and hiding the notifier,
+         * default is null
+         */
+        fx: null,
+        /**
+         * Option: width
+         * {Integer} the width in pixels of the notifier, default is 250
+         */
+        width: 250,
+        /**
+         * Option: position
+         * {Object} position options to use with <Jx.Widget::position>
+         * for positioning the Notifier
+         */
+        position: {
+            horizontal: 'center center',
+            vertical: 'top top'
+        }
+    },
+
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.domObj.setStyle('position','absolute');
+        if ($defined(this.options.width)) {
+            this.domObj.setStyle('width',this.options.width);
+        }
+        this.position(this.domObj, 
+                      this.options.parent,
+                      this.options.position);
+    },
+    
+    /**
+     * APIMethod: add
+     * Add a new notice to the notifier
+     *
+     * Parameters:
+     * notice - {<Jx.Notice>} the notice to add
+     */
+    add: function(notice) {
+        if (!(notice instanceof Jx.Notice)) {
+            notice = new Jx.Notice({content: notice});
+        }
+        notice.options.chrome = this.options.chrome;
+        this.parent(notice);
+    }
+});/*
+---
+
+name: Jx.Scrollbar
+
+description: An implementation of a custom CSS-styled scrollbar.
+
+license: MIT-style license.
+
+requires:
+ - Jx.Slider
+
+provides: [Jx.Scrollbar]
+
+css:
+ - scrollbar
+
+...
+ */
+// $Id: scrollbar.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Scrollbar
+ * Creates a custom scrollbar either vertically or horizontally (determined by
+ * options). These scrollbars are designed to be styled entirely through CSS.
+ * 
+ * Copyright 2009 by Jonathan Bomgardner
+ * License: MIT-style
+ * 
+ * Based in part on 'Mootools CSS Styled Scrollbar' on
+ * http://solutoire.com/2008/03/10/mootools-css-styled-scrollbar/
+ */
+Jx.Scrollbar = new Class({
+    
+    Family: 'Jx.Scrollbar',
+    
+    Extends: Jx.Widget,
+    
+    Binds: ['scrollIt'],
+    
+    options: {
+        /**
+         * Option: direction
+         * Determines which bars are visible. Valid options are 'horizontal'
+         * or 'vertical'
+         */
+        direction: 'vertical',
+        /**
+         * Option: useMouseWheel
+         * Whether to allow the mouse wheel to move the content. Defaults 
+         * to true.
+         */
+        useMouseWheel: true,
+        /**
+         * Option: useScrollers
+         * Whether to show the scrollers. Defaults to true.
+         */
+        useScrollers: true,
+        /**
+         * Option: scrollerInterval
+         * The amount to scroll the content when using the scrollers. 
+         * useScrollers option must be true. Default is 50 (px).
+         */
+        scrollerInterval: 50,
+        /**
+         * Option: template
+         * the HTML template for a scrollbar
+         */
+        template: '<div class="jxScrollbarContainer"><div class="jxScrollLeft"></div><div class="jxSlider"></div><div class="jxScrollRight"></div></div>'
+    },
+    
+    classes: new Hash({
+        domObj: 'jxScrollbarContainer',
+        scrollLeft: 'jxScrollLeft',
+        scrollRight: 'jxScrollRight',
+        sliderHolder: 'jxSlider'
+    }),
+    
+    el: null,
+    //element is the element we want to scroll. 
+    parameters: ['element', 'options'],
+    
+    /**
+     * Method: render
+     * render the widget
+     */
+    render: function () {
+        this.parent();
+        this.el = document.id(this.options.element);
+        if (this.el) {
+            this.el.addClass('jxHas'+this.options.direction.capitalize()+'Scrollbar');
+            
+            //wrap content to make scroll work correctly
+            var children = this.el.getChildren();
+            this.wrapper = new Element('div',{
+                'class': 'jxScrollbarChildWrapper'
+            });
+            
+            /**
+             * the wrapper needs the same settings as the original container
+             * specifically, the width and height
+             */ 
+            this.wrapper.setStyles({
+                width: this.el.getStyle('width'),
+                height: this.el.getStyle('height')
+            });
+            
+            children.inject(this.wrapper);
+            this.wrapper.inject(this.el);
+            
+            this.domObj.inject(this.el);
+            
+            var scrollSize = this.wrapper.getScrollSize();
+            var size = this.wrapper.getContentBoxSize();
+            this.steps = this.options.direction==='horizontal'?scrollSize.x-size.width:scrollSize.y-size.height;
+            this.slider = new Jx.Slider({
+                snap: false,
+                min: 0,
+                max: this.steps,
+                step: 1,
+                mode: this.options.direction,
+                onChange: this.scrollIt
+                
+            });
+            
+            if (!this.options.useScrollers) {
+                this.scrollLeft.dispose();
+                this.scrollRight.dispose();
+                //set size of the sliderHolder
+                if (this.options.direction === 'horizontal') {
+                    this.sliderHolder.setStyle('width','100%');
+                } else {
+                    this.sliderHolder.setStyle('height', '100%');
+                }
+                
+            } else {
+                this.scrollLeft.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step - this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                this.scrollRight.addEvents({
+                    mousedown: function () {
+                        this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        this.pid = function () {
+                            this.slider.slider.set(this.slider.slider.step + this.options.scrollerInterval);
+                        }.periodical(1000, this);
+                    }.bind(this),
+                    mouseup: function () {
+                        $clear(this.pid);
+                    }.bind(this)
+                });
+                //set size of the sliderHolder
+                var holderSize, scrollerRightSize, scrollerLeftSize;
+                if (this.options.direction === 'horizontal') {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().width;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().width;
+                    holderSize = size.width - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('width', holderSize + 'px');
+                } else {
+                    scrollerRightSize = this.scrollRight.getMarginBoxSize().height;
+                    scrollerLeftSize = this.scrollLeft.getMarginBoxSize().height;
+                    holderSize = size.height - scrollerRightSize - scrollerLeftSize;
+                    this.sliderHolder.setStyle('height', holderSize + 'px');
+                }
+            }
+            document.id(this.slider).inject(this.sliderHolder);
+            
+            //allows mouse wheel to function
+            if (this.options.useMouseWheel) {
+                $$(this.el, this.domObj).addEvent('mousewheel', function(e){
+                    e = new Event(e).stop();
+                    var step = this.slider.slider.step - e.wheel * 30;
+                    this.slider.slider.set(step);
+                }.bind(this));
+            }
+            
+            //stop slider if we leave the window
+            document.id(document.body).addEvent('mouseleave', function(){ 
+                this.slider.slider.drag.stop();
+            }.bind(this));
+
+            this.slider.start();
+        }
+    },
+    
+    /**
+     * Method: scrollIt
+     * scroll the content in response to the slider being moved.
+     */
+    scrollIt: function (step) {
+        var x = this.options.direction==='horizontal'?step:0;
+        var y = this.options.direction==='horizontal'?0:step;
+        this.wrapper.scrollTo(x,y);
+    }
+});/*
+---
+
+name: Jx.Formatter
+
+description: Base formatter object
+
+license: MIT-style license.
+
+requires:
+ - Jx.Object
+
+provides: [Jx.Formatter]
+
+...
+ */
+ // $Id: formatter.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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({
+    Family: 'Jx.Formatter',
+    Extends: Jx.Object,
+
+    /**
+     * APIMethod: format
+     * Empty method that must be overridden by subclasses to provide
+     * the needed formatting functionality.
+     */
+    format: $empty
+});/*
+---
+
+name: Jx.Formatter.Number
+
+description: Formats numbers including negative and floats
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Number]
+
+...
+ */
+// $Id: number.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.number'.decimalSeparator
+ * - 'formatter.number'.thousandsSeparator
+ *
+ * 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: 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);
+
+        //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.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                } else if (i >= left) {
+                    j++;
+                    if (j === 3 && i !== l - 1) {
+                        ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'thousandsSeparator'});
+                        j = 0;
+                    }
+                }
+
+            }
+        } else {
+            ret = parts[0];
+        }
+
+        if (dec) {
+            ret = ret + this.getText({set:'Jx',key:'formatter.number',value:'decimalSeparator'}) + parts[1];
+        }
+        if (neg && this.options.useParens) {
+            ret = "(" + ret + ")";
+        } else if (neg && !this.options.useParens) {
+            ret = "-" + ret;
+        }
+
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});/*
+---
+
+name: Jx.Formatter.Currency
+
+description: Formats input as currency. Currently only US currency is supported
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter.Number
+
+provides: [Jx.Formatter.Currency]
+
+...
+ */
+// $Id: currency.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ * 
+ * MooTools.lang Keys:
+ * - 'formatter.currency'.sign
+ *
+ * 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: {},
+    /**
+     * APIMethod: format
+     * Takes a number and formats it as currency.
+     *
+     * Parameters:
+     * value - the number to format
+     */
+    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.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value.substring(1, value.length);
+        } else {
+            ret = this.getText({set:'Jx',key:'formatter.currency',value:'sign'}) + value;
+        }
+        return ret;
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+});/*
+---
+
+name: Jx.Formatter.Date
+
+description: Formats dates using the mootools-more Date extensions
+
+license: MIT-style license.
+
+requires:
+ - More/Date.Extras
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Date]
+...
+ */
+// $Id: date.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+        var d = Date.parse(value);
+        return d.format(this.options.format);
+    }
+});/*
+---
+
+name: Jx.Formatter.URI
+
+description: Formats uris using the mootools-more URI extensions
+
+license: MIT-style license.
+
+requires:
+ - More/String.Extras
+ - Jx.Formatter
+ - More/URI
+
+provides: [Jx.Formatter.URI]
+
+...
+ */
+// $Id: uri.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Formatter.URI
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats URIs using the mootools-more's
+ * URI extensions. See the -more docs for details of
+ * supported formats for parsing and formatting.
+ * 
+ * @url http://mootools.net/docs/more/Native/URI
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Uri = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {
+        /**
+         * Option: format
+         * The format to use. See the mootools-more URI options
+         * to use within a {pattern}
+         *   {string} will call the URI.toString() method
+         */
+        format: '<a href="{string}" target="_blank">{host}</a>'
+    },
+    /**
+     * APIMethod: format
+     * Does the work of formatting dates
+     *
+     * Parameters:
+     * value - the text to format
+     */
+    format: function (value) {
+      var uri        = new URI(value),
+          uriContent = {},
+          pattern    = new Array(),
+          patternTmp = this.options.format.match(/\\?\{([^{}]+)\}/g);
+
+      // remove bracktes
+      patternTmp.each(function(e) {
+        pattern.push(e.slice(1, e.length-1));
+      });
+
+      // build object that contains replacements
+      for(var i = 0, j = pattern.length; i < j; i++) {
+        switch(pattern[i]) {
+          case 'string':
+            uriContent[pattern[i]] = uri.toString();
+            break;
+          default:
+            uriContent[pattern[i]] = uri.get(pattern[i]);
+            break;
+        }
+      }
+      return this.options.format.substitute(uriContent);
+    }
+});/*
+---
+
+name: Jx.Formatter.Boolean
+
+description: Formats boolean input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Boolean]
+...
+ */
+// $Id: boolean.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * Class: Jx.Formatter.Boolean
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats boolean values. You supply the
+ * text values for true and false in the options.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * MooTools.lang Keys:
+ * - 'formatter.boolean'.true
+ * - 'formatter.boolean'.false
+ * 
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Boolean = new Class({
+
+    Extends: Jx.Formatter,
+
+    options: {},
+    /**
+     * APIMethod: format
+     * Takes a value, determines boolean equivalent and
+     * displays the appropriate text value.
+     *
+     * 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.getText({set:'Jx',key:'formatter.boolean',value:'true'}) : this.getText({set:'Jx',key:'formatter.boolean',value:'false'});
+    },
+    
+    /**
+     * APIMethod: changeText
+     * This method should be overridden by subclasses. It should be used
+     * to change any language specific default text that is used by the widget.
+     * 
+     * Parameters:
+     * lang - the language being changed to or that had it's data set of 
+     * 		translations changed.
+     */
+    changeText: function (lang) {
+    	this.parent();
+    }
+
+});/*
+---
+
+name: Jx.Formatter.Phone
+
+description: Formats phone numbers in US format including area code
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+
+provides: [Jx.Formatter.Phone]
+
+...
+ */
+// $Id: phone.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 {
+                ret = ret + sep + v.substring(0, 3) + sep;
+            }
+            v = v.substring(3);
+        }
+        //do the rest of the number
+        ret = ret + v.substring(0, 3) + sep + v.substring(3);
+        return ret;
+    }
+});/*
+---
+
+name: Jx.Formatter.Text
+
+description: Formats strings by limiting to a max length
+
+license: MIT-style license.
+
+requires:
+ - Jx.Formatter
+
+provides: [Jx.Formatter.Text]
+
+...
+ */
+// $Id: $
+/**
+ * Class: Jx.Formatter.Text
+ *
+ * Extends: <Jx.Formatter>
+ *
+ * This class formats strings by limiting them to a maximum length
+ * and replacing the remainder with an ellipsis.
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * License:
+ * Copyright (c) 2010, Hughes Gauthier.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Formatter.Text = new Class({
+
+  Extends: Jx.Formatter,
+
+  options: {
+    /**
+     * Option: length
+     * {Integer} default null, if set to an integer value greater than
+     * 0 then the value will be truncated to length characters and
+     * the remaining characters will be replaced by an ellipsis (...)
+     */
+    length: null,
+    /**
+     * Option: ellipsis
+     * {String} the text to use as the ellipsis when truncating a string
+     * default is three periods (...)
+     */
+    ellipsis: '...'
+  },
+
+  format : function (value) {
+    var text = '' + value,
+        max = this.options.length,
+        ellipsis = this.options.ellipsis;
+
+    if (max && text.length > max) {
+      text = text.substr(0,max-ellipsis.length) + ellipsis;
+    }
+
+    return text;
+  }
+});/*
+---
+
+name: Jx.Field.Check
+
+description: Represents a checkbox input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Checkbox]
+
+...
+ */
+// $Id: checkbox.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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 : '<span class="jxInputContainer"><input class="jxInputCheck" type="checkbox" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * Whether this field is checked or not
+         */
+        checked : false,
+
+        labelSeparator: ''
+    },
+    /**
+     * Property: type
+     * The type of this field
+     */
+    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.id(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");
+            }
+        }
+
+        // add click event to the label to toggle the checkbox
+        if(this.label) {
+          this.label.addEvent('click', function(ev) {
+            this.setValue(this.getValue() != null ? false : true)
+          }.bind(this));
+        }
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - Whether the box shouldbe checked or not. "checked" or "true" if it should be checked.
+     */
+    setValue : function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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.
+     */
+    reset : function () {
+        if (this.options.checked) {
+            this.field.set('checked', "checked");
+        } else {
+            this.field.erase('checked');
+        }
+    },
+
+    getChecked: function () {
+        return this.field.get("checked");
+    }
+
+});
+/*
+---
+
+name: Jx.Field.Radio
+
+description: Represents a radio button input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Radio]
+
+...
+ */
+// $Id: radio.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><input class="jxInputRadio" type="radio" name="{name}"/><label class="jxInputLabel"></label><span class="jxInputTag"></span></span>',
+        /**
+         * Option: checked
+         * whether this radio button is checked or not
+         */
+        checked: false,
+
+        labelSeparator: ''
+    },
+    /**
+     * 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.id(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");
+            }
+        }
+
+        // add click event to toggle the radio buttons
+        this.label.addEvent('click', function(ev) {
+          this.field.checked ? this.setValue(false) : this.setValue(true);
+        }.bind(this));
+
+    },
+
+    /**
+     * APIMethod: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to, "checked" it should be checked.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            if (v === 'checked' || v === 'true' || v === true) {
+                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;
+        }
+    },
+
+    /**
+     * 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');
+        }
+    }
+
+});
+
+
+
+
+/*
+---
+
+name: Jx.Field.Select
+
+description: Represents a select, or drop down, input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Select]
+
+...
+ */
+// $Id: select.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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)
+ *
+ * License:
+ * Copyright (c) 2009, Jon Bomgardner.
+ *
+ * This file is licensed under an MIT style license
+ *
+ */
+
+Jx.Field.Select = new Class({
+
+    Extends: Jx.Field,
+
+    options: {
+        /**
+         * Option: multiple
+         * {Boolean} optional, defaults to false.  If true, then the select
+         * will support multi-select
+         */
+        mulitple: false,
+        /**
+         * Option: size
+         * {Integer} optional, defaults to 1.  If set, then this specifies
+         * the number of rows of the select that are visible
+         */
+        size: 1,
+        /**
+         * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><select class="jxInputSelect" name="{name}"></select><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * Indictes this type of field.
+     */
+    type: 'Select',
+
+    /**
+     * APIMethod: render
+     * Creates a select field.
+     */
+    render: function () {
+        this.parent();
+        this.field.addEvent('change', function() {this.fireEvent('change', this);}.bind(this));
+        if ($defined(this.options.multiple)) {
+          this.field.set('multiple', this.options.multiple);
+        }
+        if ($defined(this.options.size)) {
+          this.field.set('size', this.options.size);
+        }
+        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': this.getText(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) {
+                this.addOption(item);
+            }, this);
+        }
+    },
+
+    /**
+     * Method: addOption
+     * add an option to the select list
+     *
+     * Parameters:
+     * item - The option to add.
+     * position (optional) - an integer index or the string 'top'.
+     *                     - default is to add at the bottom.
+     */
+    addOption: function (item, position) {
+        var opt = new Element('option', {
+            'value': item.value,
+            'html': this.getText(item.text)
+        });
+        if ($defined(item.selected) && item.selected) {
+            opt.set("selected", "selected");
+        }
+        var where = 'bottom';
+        var field = this.field;
+        if ($defined(position)) {
+            if (Jx.type(position) == 'integer' &&
+                (position >= 0  && position < field.options.length)) {
+                field = this.field.options[position];
+                where = 'before';
+            } else if (position == 'top') {
+                where = 'top';
+            }
+
+        }
+        opt.inject(field, where);
+    },
+
+    /**
+     * Method: removeOption
+     * removes an option from the select list
+     *
+     * Parameters:
+     *  item - The option to remove.
+     */
+    removeOption: function (item) {
+        //TBD
+    },
+    /**
+     * Method: setValue
+     * Sets the value property of the field
+     *
+     * Parameters:
+     * v - The value to set the field to.
+     */
+    setValue: function (v) {
+        if (!this.options.readonly) {
+            //loop through the options and set the one that matches v
+            $$(this.field.options).each(function (opt) {
+                if (opt.get('value') === v) {
+                    document.id(opt).set("selected", true);
+                }
+            }, this);
+        }
+    },
+
+    /**
+     * Method: getValue
+     * Returns the current value of the field.
+     */
+    getValue: function () {
+        var index = this.field.selectedIndex;
+        //check for a set "value" attribute. If not there return the text
+        if (index > -1) {
+            var ret = this.field.options[index].get("value");
+            if (!$defined(ret)) {
+                ret = this.field.options[index].get("text");
+            }
+            return ret;
+        }
+    },
+    
+    /**
+     * APIMethod: empty
+     * Empties all options from this select
+     */
+    empty: function () {
+        if ($defined(this.field.options)) {
+            $A(this.field.options).each(function (option) {
+                this.field.remove(option);
+            }, this);
+        }
+    }
+});/*
+---
+
+name: Jx.Field.Textarea
+
+description: Represents a textarea input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+
+provides: [Jx.Field.Textarea]
+
+...
+ */
+// $Id: textarea.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel"></label><textarea class="jxInputTextarea" name="{name}"></textarea><span class="jxInputTag"></span></span>'
+    },
+    /**
+     * Property: type
+     * The type of field this is.
+     */
+    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 ($defined(this.options.columns)) {
+            this.field.set('cols', this.options.columns);
+        }
+
+        //TODO: Do we need to use OverText here as well??
+
+    }
+});/*
+---
+
+name: Jx.Field.Button
+
+description: Represents a button input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+ - Jx.Button
+
+provides: [Jx.Field.Button]
+
+...
+ */
+/**
+ * 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: buttonClass
+         * choose the actual Jx.Button subclass to create for this form
+         * field.  The default is to create a basic Jx.Button.  To create
+         * a different kind of button, pass the class to this option, for
+         * instance:
+         * (code)
+         * buttonClass: Jx.Button.Color
+         * (end)
+         */
+        buttonClass: Jx.Button,
+        
+        /**
+         * Option: buttonOptions
+         */
+        buttonOptions: {},
+        /**
+         * Option: template
+         * The template used to render this field
+         */
+        template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><div class="jxInputButton"></div><span class="jxInputTag"></span></span>'
+    },
+    
+    button: null,
+    
+    /**
+     * Property: type
+     * The type of this field
+     */
+    type: 'Button',
+
+    processTemplate: function(template, classes, container) {
+        var h = this.parent(template, classes, container);
+        this.button = new this.options.buttonClass(this.options.buttonOptions);
+        this.button.addEvent('click', function(){
+          this.fireEvent('click');
+        }.bind(this));
+        var c = h.get('jxInputButton');
+        if (c) {
+            this.button.domObj.replaces(c);
+        }
+        this.button.setEnabled(!this.options.disabled);
+        return h;
+    },
+    
+    click: function() {
+        this.button.clicked();
+    },
+    
+    enable: function() {
+      this.parent();
+      this.button.setEnabled(true);
+    },
+    
+    disable: function() {
+      this.parent();
+      this.button.setEnabled(false);
+    }
+});/*
+---
+
+name: Jx.Field.Combo
+
+description: Represents an editable combo
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field
+ - Jx.Button
+ - Jx.Menu
+ - Jx.Menu.Item
+ - Jx.ButtonSet
+
+provides: [Jx.Field.Combo]
+
+...
+ */
+// $Id: jxcombo.js 993 2010-10-07 19:29:08Z pagameba $
+/**
+ * Class: Jx.Field.Combo
+ *
+ * Extends: <Jx.Field>
+ *
+ *
+ * Example:
+ * (code)
+ * (end)
+ *
+ * Events:
+ * change - 
+ *
+ * License:
+ * Copyright (c) 2008, DM Solutions Group Inc.
+ *
+ * This file is licensed under an MIT style license
+ */
+Jx.Field.Combo = new Class({
+    Family: 'Jx.Field.Combo',
+    Extends: Jx.Field,
+    pluginNamespace: 'Combo',
+
+    options: {
+        buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+        /* Option: template
+         */
+         template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputCombo"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>'
+     },
+     
+     type: 'Combo',
+     
+    /**
+     * APIMethod: render
+     * create a new instance of Jx.Field.Combo
+     */
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+        
+        var button = new Jx.Button({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon'
+        }).addTo(this.revealer);
+
+        this.menu = new Jx.Menu();
+        this.menu.button = button;
+        this.buttonSet = new Jx.ButtonSet();
+
+        this.buttonSet = new Jx.ButtonSet({
+            onChange: (function(set) {
+                var button = set.activeButton;
+                var l = button.options.label;
+                if (l == '&nbsp;') {
+                    l = '';
+                }
+                this.setLabel(l);
+                var img = button.options.image;
+                if (img.indexOf('a_pixel') != -1) {
+                    img = '';
+                }
+                this.setImage(img, button.options.imageClass);
+
+                this.fireEvent('change', this);
+            }).bind(this)
+        });
+        if (this.options.items) {
+            this.add(this.options.items);
+        }
+        var that = this;
+        button.addEvent('click', function(e) {
+            if (this.list.count() === 0) {
+                return;
+            }
+            if (!button.options.enabled) {
+                return;
+            }
+            this.contentContainer.setStyle('visibility','hidden');
+            this.contentContainer.setStyle('display','block');
+            document.id(document.body).adopt(this.contentContainer);
+            /* we have to size the container for IE to render the chrome correctly
+             * but just in the menu/sub menu case - there is some horrible peekaboo
+             * bug in IE related to ULs that we just couldn't figure out
+             */
+            this.contentContainer.setContentBoxSize(this.subDomObj.getMarginBoxSize());
+
+            this.showChrome(this.contentContainer);
+
+            this.position(this.contentContainer, that.field, {
+                horizontal: ['left left', 'right right'],
+                vertical: ['bottom top', 'top bottom'],
+                offsets: this.chromeOffsets
+            });
+
+            this.contentContainer.setStyle('visibility','');
+
+            document.addEvent('mousedown', this.bound.hide);
+            document.addEvent('keyup', this.bound.keypress);
+
+            this.fireEvent('show', this);
+        }.bindWithEvent(this.menu));
+
+        this.menu.addEvents({
+            'show': (function() {
+                //this.setActive(true);
+            }).bind(this),
+            'hide': (function() {
+                //this.setActive(false);
+            }).bind(this)
+        });
+    },
+    
+    setLabel: function(label) {
+      if ($defined(this.field)) {
+        this.field.value = this.getText(label);
+      }
+    },
+    
+    setImage: function(url, imageClass) {
+      if ($defined(this.icon)) {
+        this.icon.setStyle('background-image', 'url('+url+')');
+        this.icon.setStyle('background-repeat', 'no-repeat');
+
+        if (this.options.imageClass) {
+            this.icon.removeClass(this.options.imageClass);
+        }
+        if (imageClass) {
+            this.options.imageClass = imageClass;
+            this.icon.addClass(imageClass);
+            this.icon.setStyle('background-position','');
+        } else {
+            this.options.imageClass = null;
+            this.icon.setStyle('background-position','center center');
+        }
+      }
+      if (!url) {
+        this.wrapper.addClass('jxInputIconHidden');
+      } else {
+        this.wrapper.removeClass('jxInputIconHidden');
+      }
+    },
+
+    /**
+     * Method: valueChanged
+     * invoked when the current value is changed
+     */
+    valueChanged: function() {
+        this.fireEvent('change', this);
+    },
+
+    setValue: function(value) {
+        this.field.set('value', value);
+        this.buttonSet.buttons.each(function(button){
+          button.setActive(button.options.label === value);
+        },this);
+    },
+
+    /**
+     * Method: onKeyPress
+     * Handle the user pressing a key by looking for an ENTER key to set the
+     * value.
+     *
+     * Parameters:
+     * e - {Event} the keypress event
+     */
+    onKeyPress: function(e) {
+        if (e.key == 'enter') {
+            this.valueChanged();
+        }
+    },
+
+    /**
+     * Method: add
+     * add a new item to the pick list
+     *
+     * Parameters:
+     * options - {Object} object with properties suitable to be passed to
+     * a <Jx.Menu.Item.Options> object.  More than one options object can be
+     * passed, comma separated or in an array.
+     */
+    add: function() {
+        $A(arguments).flatten().each(function(opt) {
+            var button = new Jx.Menu.Item($merge(opt,{
+                toggle: true
+            }));
+            this.menu.add(button);
+            this.buttonSet.add(button);
+            if (opt.selected) {
+              this.buttonSet.setActiveButton(button);
+            }
+        }, this);
+    },
+
+    /**
+     * Method: remove
+     * Remove the item at the given index.  Not implemented.
+     *
+     * Parameters:
+     * idx - {Mixed} the item to remove by reference or by index.
+     */
+    remove: function(idx) {
+      var item;
+      if ($type(idx) == 'number' && idx < this.buttonSet.buttons.length) {
+        item = this.buttonSet.buttons[idx];
+      } else if ($type(idx) == 'string'){
+        this.buttonSet.buttons.some(function(button){
+            if (button.options.label === idx) {
+                item = button;
+                return true;
+            }
+            return false;
+        },this);
+      }
+      if (item) {
+        this.buttonSet.remove(item);
+        this.menu.remove(item);
+      }
+    },
+    /**
+     * APIMethod: empty
+     * remove all values from the combo
+     */
+    empty: function() {
+      this.menu.empty();
+      this.buttonSet.empty();
+      this.setLabel('');
+      this.setImage(Jx.aPixel.src);
+    },
+    
+    enable: function() {
+      this.parent();
+      this.menu.setEnabled(true);
+    },
+    
+    disable: function() {
+      this.parent();
+      this.menu.setEnabled(false);
+    }
+    
+});/*
+---
+
+name: Jx.Field.Password
+
+description: Represents a password input
+
+license: MIT-style license.
+
+requires:
+ - Jx.Field.Text
+
+provides: [Jx.Field.Password]
+
+...
+ */
+// $Id: password.js 960 2010-06-06 22:23:16Z jonlb at comcast.net $
+/**
+ * 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: '<span class="jxInputContainer"><label class="jxInputLabel" ></label><input class="jxInputPassword" type="password" name="{name}"/><span class="jxInputTag"></span></span>'
+    },
+
+    type: 'Password'
+});/*
+---
+
+name: Jx.Field.Color
+
+description: Represents an input field with a jx.button.color
+
+license: MIT-style license.
+
+requires:
+ - Jx.Text
+ - Jx.Button.Color
+ - Jx.Form
+ - Jx.Plugin.Field.Validator
+
+provides: [Jx.Field.Color]
+
+...
+ */
+/**
+ * Class: Jx.Field.Color
+ *
+ * Extends: <Jx.Field>
+ *
+ * This class provides a Jx.Field.Text in combination with a Jx.Button.Color
+ * to have a Colorpicker with an input field.
+ *
+ * License:
+ * Copyright (c) 2010, Paul Spener, Fred Warnock, Conrad Barthelmes
+ *
+ * This file is licensed under an MIT style license
+ */
+  Jx.Field.Color = new Class({
+    Extends: Jx.Field,
+    Binds: ['changed','hide','keyup','changeText'],
+    type: 'Color',
+    options: {
+      buttonTemplate: '<a class="jxButtonContainer jxButton" href="javascript:void(0);"><img class="jxButtonIcon" src="'+Jx.aPixel.src+'"></a>',
+      /**
+       * Option: template
+       * The template used to render this field
+       */
+      template: '<span class="jxInputContainer"><label class="jxInputLabel"></label><span class="jxInputWrapper"><input type="text" class="jxInputColor"  name="{name}"><img class="jxInputIcon" src="'+Jx.aPixel.src+'"><span class="jxInputRevealer"></span></span><span class="jxInputTag"></span></span>',
+      /**
+       * Option: showOnHover
+       * {Boolean} show the color palette when hovering over the input, default 
+       * is false
+       */
+      showOnHover: false,
+      /**
+       *  Option: showDelay
+       *  set time in milliseconds when to show the color field on mouseenter
+       */
+      showDelay: 250,
+      /**
+       * Option: errorMsg
+       * error message for the validator.
+       */
+      errorMsg: 'Invalid Web-Color',
+      /**
+       * Option: color
+       * a color to initialize the field with, defaults to #000000
+       * (black) if not specified.
+       */
+      color: '#000000'
+
+    },
+    button: null,
+    validator: null,
+    render: function() {
+        this.classes.combine({
+          wrapper: 'jxInputWrapper',
+          revealer: 'jxInputRevealer',
+          icon: 'jxInputIcon'
+        });
+        this.parent();
+
+      var self = this;
+      if (!Jx.Field.Color.ColorPalette) {
+          Jx.Field.Color.ColorPalette = new Jx.ColorPalette(this.options);
+      }
+      this.button = new Jx.Button.Flyout({
+          template: this.options.buttonTemplate,
+          imageClass: 'jxInputRevealerIcon',
+          positionElement: this.field,
+          onBeforeOpen: function() {
+            if (Jx.Field.Color.ColorPalette.currentButton) {
+                Jx.Field.Color.ColorPalette.currentButton.hide();
+            }
+            Jx.Field.Color.ColorPalette.currentButton = this;
+            Jx.Field.Color.ColorPalette.addEvent('change', self.changed);
+            Jx.Field.Color.ColorPalette.addEvent('click', self.hide);
+            this.content.appendChild(Jx.Field.Color.ColorPalette.domObj);
+            Jx.Field.Color.ColorPalette.domObj.setStyle('display', 'block');
+          },
+          onOpen: function() {
+            /* setting these before causes an update problem when clicking on
+             * a second color button when another one is open - the color
+             * wasn't updating properly
+             */
+            Jx.Field.Color.ColorPalette.options.color = self.options.color;
+            Jx.Field.Color.ColorPalette.updateSelected();
+          }
+        }).addTo(this.revealer);
+
+      this.validator = new Jx.Plugin.Field.Validator({
+        validators: [{
+            validatorClass: 'colorHex',
+            validator: {
+              name: 'colorValidator',
+              options: {
+                validateOnChange: false,
+                errorMsg: self.options.errorMsg,
+                test: function(field,props) {
+                  try {
+                    var c = field.get('value').hexToRgb(true);
+                    if(c == null) return false;
+                    for(var i = 0; i < 3; i++) {
+                      if(c[i].toString() == 'NaN') {
+                        return false;
+                      }
+                    }
+                  }catch(e) {
+                    return false;
+                  }
+                  c = c.rgbToHex().toUpperCase();
+                  self.setColor(c);
+                  return true;
+                }
+              }
+            }
+        }],
+        validateOnBlur: true,
+        validateOnChange: true
+      });
+      this.validator.attach(this);
+      this.field.addEvent('keyup', this.onKeyUp.bind(this));
+      if (this.options.showOnHover) {
+        this.field.addEvent('mouseenter', function(ev) {
+          self.button.clicked.delay(self.options.showDelay, self.button);
+        });
+      }
+      this.setValue(this.options.color);
+      this.icon.setStyle('background-color', this.options.color);
+      //this.addEvent('change', self.changed);
+    },
+    /*
+     * Method: onKeyUp
+     *
+     * listens to the keyup event and validates the input for a hex color
+     *
+     */
+    onKeyUp : function(ev) {
+      var color = this.getValue();
+      if (color.substring(0,1) == '#') {
+          color = color.substring(1);
+      }
+      if (color.toLowerCase().match(/^[0-9a-f]{6}$/)) {
+          this.options.color = '#' +color.toUpperCase();
+          this.setColor(this.options.color);
+      }
+    },
+    setColor: function(c) {
+        this.options.color = c;
+        this.setValue(c);
+        this.icon.setStyle('background-color', c);
+    },
+    changed: function() {
+        var c = Jx.Field.Color.ColorPalette.options.color;
+        this.setColor(c);
+    },
+    hide: function() {
+        this.button.setActive(false);
+        Jx.Field.Color.ColorPalette.removeEvent('change', this.changed);
+        Jx.Field.Color.ColorPalette.removeEvent('click', this.hide);
+
+        this.button.hide();
+        Jx.Field.Color.ColorPalette.currentButton = null;
+    },
+    changeText: function(lang) {
+      this.parent();
+    }
+  });

Added: trunk/lib/jxLib/themes/crispin/ie6.css
===================================================================
--- trunk/lib/jxLib/themes/crispin/ie6.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/crispin/ie6.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,216 @@
+/**
+ * @project         Jx
+ * @revision        $Id: ie6.css 935 2010-05-28 17:36:13Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* IE < 7 STYLES */
+/* ============= */
+
+/* 24 bit images do not appear correctly in IE versions below 7. Applying a 
+ * filter through the class below will make them appear correctly.
+ */
+.png24{filter:expression(Jx.applyPNGFilter(this))}
+
+/* Opacity needs to be set in IE6 and below using the following filters.
+ * Please note that IE8 changed how filters are written. 
+ */
+.jxChromeDrag {filter: Alpha(opacity=50);}
+.jxDisabled {filter:Alpha(opacity=40);}
+.jxDisabled * {filter:Alpha(opacity=40);}
+.jxMask {filter:Alpha(opacity=50);}
+.jxModalMask {filter: Alpha(opacity=20);}
+.jxSpinner {filter: alpha(opacity=50);}
+iframe.jxIframeShim {filter:Alpha(opacity:0);}
+
+/* List items do not render properly under several conditions.  
+ * Applying a height to the LI forces it to render properly.
+ * Content that is taller than the li simply forces the li to be taller 
+ */
+.jxTree li,
+.jxTreeRoot li {
+  height: 20px;
+}
+
+/* tree item focus style */
+.jxTree a:active,
+.jxTreeRoot a:active {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 14px;
+  background-position: left -72px;
+  outline: expression(hideFocus='true');
+}
+
+/* IE versions 7 and below do not recognize the focus pseudo-class, but instead
+ * use the active pseudo-class.  Other browsers use the active-pseudo-class
+ * while something is being pressed so IE specific definitions are needed. */
+/* focus button */
+ul.jxToolbar .jxButton:active,
+.jxButton:active {
+  background-position: left -96px;
+  outline: expression(hideFocus='true');
+}
+
+ul.jxToolbar .jxButton:active span.jxButtonContent,
+.jxButton:active span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:active,
+.jxButtonActive:active {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:active span.jxButtonContent,
+.jxButtonActive:active span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* clicking normal button */
+ul.jxToolbar .jxButtonPressed:active,
+.jxButtonPressed:active {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed:active span.jxButtonContent,
+.jxButtonPressed:active span.jxButtonContent {
+  background-position: right -120px;
+}
+
+.jxButtonDisclose:active {
+  background-position: right -96px;
+}
+
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+
+/* Focus tab */
+.jxBarTop a.jxTab:active,
+.jxBarBottom a.jxTab:active {
+  background-position: left -96px; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarTop a.jxTab:active span.jxTabContent,
+.jxBarBottom a.jxTab:active span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:active,
+.jxBarBottom a.jxTabActive:active {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:active span.jxTabContent,
+.jxBarBottom a.jxTabActive:active span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Click Focused Tab */
+.jxBarTop a.jxTabPressed:active,
+.jxBarBottom a.jxTabPressed:active {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed:active span.jxTabContent,
+.jxBarBottom a.jxTabPressed:active span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+
+/* Focus tab */
+.jxBarLeft a.jxTab:active,
+.jxBarRight a.jxTab:active {
+  background-position: -96px top; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarLeft a.jxTab:active span.jxTabContent,
+.jxBarRight a.jxTab:active span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:active,
+.jxBarRight a.jxTabActive:active {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:active span.jxTabContent,
+.jxBarRight a.jxTabActive:active span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Click Focused Tab */
+.jxBarLeft a.jxTabPressed:active,
+.jxBarRight a.jxTabPressed:active {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed:active span.jxTabContent,
+.jxBarRight a.jxTabPressed:active span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Menu Item icon position.
+   IE 6 seems to want to line up the image differently.  
+   This override just nudges it back up. */
+
+img.jxMenuItemIcon {
+  top: 3px;
+}
+
+/* Menu Item States */ 
+
+a.jxMenuItemActive {
+  background-position: 1px -97px;
+}
+
+a.jxMenuItem:active {
+  background-position: 1px -73px;
+  outline: expression(hideFocus='true');
+}
+
+a.jxMenuItemActive:active {
+  background-position: 1px -169px;
+}
+
+a.jxMenuItem:hover {
+  background-position: 1px -25px;
+}
+
+a.jxMenuItem:hover span.jxMenuItemContent {
+  border-top: 1px solid #C5E0FF;  /* forces IE to render properly */
+}
+
+a.jxMenuItemActive:hover {
+  background-position: 1px -121px;
+}
+
+a.jxMenuItemPressed,
+a.jxMenuItemPressed:hover {
+  background-position: 1px -49px;
+}
+
+span.jxMenuItemContent {
+  border-top: 1px solid #fff; /* forces IE to render properly */
+}
+
+span.jxMenuItemContent span {
+  font-size: 15px;
+  line-height: 15px;
+}
+
+
+/* chrome in dialogs doesn't resize properly when collapsing a dialog before
+ * moving or resizing it in IE 6 only, hiding overflow seems to do the trick
+ */
+.jxChrome {
+  overflow: hidden;
+}
+

Added: trunk/lib/jxLib/themes/crispin/ie7.css
===================================================================
--- trunk/lib/jxLib/themes/crispin/ie7.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/crispin/ie7.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,177 @@
+/**
+ * @project         Jx
+ * @revision        $Id: ie7.css 978 2010-09-08 18:37:27Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* IE 7 STYLES */
+/* =========== */
+
+/* force menus to have a size so they don't collapse */
+.jxMenuItemContainer {
+    filter:Alpha(opacity=100);
+}
+
+/* Opacity needs to be set in IE6 and below using the following filters.
+ * Please note that IE8 changed how filters are written. 
+ */
+.jxChromeDrag {filter: Alpha(opacity=50);}
+.jxDisabled {filter:Alpha(opacity=40);}
+.jxDisabled * {filter:Alpha(opacity=40);}
+.jxMask {filter:Alpha(opacity=50);}
+.jxModalMask {filter: Alpha(opacity=20);}
+.jxSpinner {filter: alpha(opacity=50);}
+iframe.jxIframeShim {filter:Alpha(opacity:0);}
+
+/* tree item focus style */
+.jxTree a:active,
+.jxTreeRoot a:active {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 14px;
+  background-position: left -72px;
+  outline: expression(hideFocus='true');
+}
+
+.jxTree,
+.jxTreeRoot,
+.jxTreeNest,
+li.jxTreeContainer {
+ /* zoom needed to fix alignment/sizing issues in IE 7 */
+ zoom: 1;
+}
+
+/* IE versions 7 and below do not recognize the focus pseudo-class, but instead
+ * use the active pseudo-class.  Other browsers use the active-pseudo-class
+ * while something is being pressed so IE specific definitions are needed. */
+/* focus button */
+ul.jxToolbar .jxButton:active,
+.jxButton:active {
+  background-position: left -96px;
+  outline: expression(hideFocus='true');
+}
+
+ul.jxToolbar .jxButton:active span.jxButtonContent,
+.jxButton:active span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:active,
+.jxButtonActive:active {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:active span.jxButtonContent,
+.jxButtonActive:active span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* clicking focused button */
+ul.jxToolbar .jxButtonPressed:active,
+.jxButtonPressed:active {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed:active span.jxButtonContent,
+.jxButtonPressed:active span.jxButtonContent {
+  background-position: right -120px;
+}
+
+a.jxButtonDisclose:active {
+  background-position: right -96px;
+  outline: expression(hideFocus='true');
+}
+
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+
+/* Focus tab */
+.jxBarTop a.jxTab:active,
+.jxBarBottom a.jxTab:active {
+  background-position: left -96px; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarTop a.jxTab:active span.jxTabContent,
+.jxBarBottom a.jxTab:active span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:active,
+.jxBarBottom a.jxTabActive:active {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:active span.jxTabContent,
+.jxBarBottom a.jxTabActive:active span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Click Focused Tab */
+.jxBarTop a.jxTabPressed:active,
+.jxBarBottom a.jxTabPressed:active {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed:active span.jxTabContent,
+.jxBarBottom a.jxTabPressed:active span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+
+/* Focus tab */
+.jxBarLeft a.jxTab:active,
+.jxBarRight a.jxTab:active {
+  background-position: -96px top; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarLeft a.jxTab:active span.jxTabContent,
+.jxBarRight a.jxTab:active span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:active,
+.jxBarRight a.jxTabActive:active {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:active span.jxTabContent,
+.jxBarRight a.jxTabActive:active span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Click Focused Tab */
+.jxBarLeft a.jxTabPressed:active,
+.jxBarRight a.jxTabPressed:active {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed:active span.jxTabContent,
+.jxBarRight a.jxTabPressed:active span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Menu Item iicon position.
+   IE 7 seems to want to line up the image by the top of the text of it's 
+   sibling.  This override just nudges it back up. */
+
+img.jxMenuItemIcon {
+  top: 1px;
+}
+
+/* Menu Item focus style */
+a.jxMenuItem:active {
+  background-position: 1px -74px;
+  outline: expression(hideFocus='true');
+}
+
+a.jxMenuItemActive:active {
+  background-position: 1px -170px;
+}
+

Added: trunk/lib/jxLib/themes/crispin/images/a_pixel.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/a_pixel.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/button.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/button.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/button_combo.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/button_combo.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/button_multi.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/button_multi.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/button_multi_disclose.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/button_multi_disclose.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/dialog_chrome.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/dialog_chrome.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/dialog_resize.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/dialog_resize.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/emblems.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/emblems.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/flyout_chrome.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/flyout_chrome.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/grid.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/grid.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/icons.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/icons.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/listitem.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/listitem.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/loading.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/loading.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/menuitem.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/menuitem.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/notice.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/notice.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/notice_error.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/notice_error.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/notice_success.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/notice_success.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/notice_warning.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/notice_warning.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/panel_controls.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/panel_controls.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/panelbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/panelbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/progressbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/progressbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/spinner_16.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/spinner_16.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/spinner_24.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/spinner_24.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tab_bottom.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tab_bottom.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tab_close.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tab_close.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tab_left.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tab_left.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tab_right.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tab_right.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tab_top.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tab_top.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tabbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tabbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tabbar_bottom.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tabbar_bottom.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tabbar_left.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tabbar_left.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tabbar_right.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tabbar_right.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/table_col.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/table_col.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/table_row.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/table_row.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/toolbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/toolbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/toolbar_separator_h.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/toolbar_separator_h.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/toolbar_separator_v.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/toolbar_separator_v.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tree.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tree.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tree_hover.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tree_hover.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/images/tree_vert_line.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/crispin/images/tree_vert_line.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/crispin/jxtheme.css
===================================================================
--- trunk/lib/jxLib/themes/crispin/jxtheme.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/crispin/jxtheme.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,24 @@
+/*
+ * reset.css - Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt
+ *
+ * Copyright (c) 2006-2008, DM Solutions Group Inc.  All rights reserved
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}.jxButtonContainer{display:-moz-inline-box;display:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:2px;border:none;}.jxButton{display:-moz-inline-box;display:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:0 0 0 4px;border:none;background-image:url(images/button.png);background-position:left -24px;background-repeat:no-repeat;text-decoration:none;outline:none;}a.jxButton{cursor:pointer;user-select:none;-moz-user-select:none;-khtml-user-select:none;}ul.jxToolbar .jxButton{background-position:left top;}span.jxButtonContent{display:-moz-inline-box;displ
 ay:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:4px 4px 4px 0;border:none;background-image:url(images/button.png);background-position:right -24px;background-repeat:no-repeat;}ul.jxToolbar span.jxButtonContent{background-position:right top;}ul.jxToolbar .jxButtonActive,.jxButtonActive{background-position:left -72px;}ul.jxToolbar .jxButtonActive span.jxButtonContent,.jxButtonActive span.jxButtonContent{background-position:right -72px;}ul.jxToolbar .jxButton:focus,.jxButton:focus{background-position:left -96px;}ul.jxToolbar .jxButton:focus span.jxButtonContent,.jxButton:focus span.jxButtonContent{background-position:right -96px;}ul.jxToolbar .jxButtonActive:focus,.jxButtonActive:focus{background-position:left -144px;}ul.jxToolbar .jxButtonActive:focus span.jxButtonContent,.jxButtonActive:focus span.jxButtonContent{background-position:right -144px;}ul.jxToolbar .jxButton:hover,ul.jxToolbar .jxButtonActive:hover,.jxButton:hover,.jxButtonActive:hover{b
 ackground-position:left -48px;}ul.jxToolbar .jxButton:hover span.jxButtonContent,ul.jxToolbar .jxButtonActive:hover span.jxButtonContent,.jxButton:hover span.jxButtonContent,.jxButtonActive:hover span.jxButtonContent{background-position:right -48px;}ul.jxToolbar .jxButtonPressed,ul.jxToolbar .jxButtonPressed:focus,.jxButtonPressed,.jxButtonPressed:focus{background-position:left -120px;}ul.jxToolbar .jxButtonPressed span.jxButtonContent,ul.jxToolbar .jxButtonPressed:focus span.jxButtonContent,.jxButtonPressed span.jxButtonContent,.jxButtonPressed:focus span.jxButtonContent{background-position:right -120px;}.jxDisabled .jxButton,.jxDisabled span.jxButtonContent span{cursor:default;}ul.jxToolbar .jxDisabled .jxButton:focus,ul.jxToolbar .jxDisabled .jxButton:active,ul.jxToolbar .jxDisabled .jxButton:hover,ul.jxToolbar .jxDisabled .jxButtonPressed{background-position:left top;}.jxDisabled .jxButton:focus,.jxDisabled .jxButton:active,.jxDisabled .jxButton:hover,.jxDisabled .jxButt
 onPressed{background-position:left -24px;}ul.jxToolbar .jxDisabled .jxButton:focus span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButton:active span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButton:hover span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButtonPressed span.jxButtonContent{background-position:right top;}.jxDisabled .jxButton:focus span.jxButtonContent,.jxDisabled .jxButton:active span.jxButtonContent,.jxDisabled .jxButton:hover span.jxButtonContent,.jxDisabled .jxButtonPressed span.jxButtonContent{background-position:right -24px;}ul.jxToolbar .jxDisabled .jxButtonActive:focus,ul.jxToolbar .jxDisabled .jxButtonActive:active,ul.jxToolbar .jxDisabled .jxButtonActive:hover,.jxDisabled .jxButtonActive:focus,.jxDisabled .jxButtonActive:active,.jxDisabled .jxButtonActive:hover{background-position:left -72px;}ul.jxToolbar .jxDisabled .jxButtonActive:focus span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButtonActive:active span.jxButtonContent,ul.jxToolbar .jxDisab
 led .jxButtonActive:hover span.jxButtonContent,.jxDisabled .jxButtonActive:focus span.jxButtonContent,.jxDisabled .jxButtonActive:active span.jxButtonContent,.jxDisabled .jxButtonActive:hover span.jxButtonContent{background-position:right -72px;}img.jxButtonIcon{display:-moz-inline-box;display:inline-block;position:relative;vertical-align:middle;width:16px;height:16px;background-position:center center;background-repeat:no-repeat;}span.jxButtonContent span{display:-moz-inline-box;display:inline-block;position:relative;vertical-align:middle;cursor:pointer;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;height:16px;white-space:nowrap;}span.jxButtonContent span.jxButtonLabel{margin:0;padding:0 4px 0 4px;color:#000;font-size:11px;}.jxDiscloser span.jxButtonContent{padding-right:0;}.jxDiscloser span.jxButtonContent span{padding-right:16px;background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}a.jxButtonDisclose{
 position:absolute;display:-moz-inline-box;display:inline-block;padding:4px 0;font-size:0;line-height:0;right:2px;top:2px;background-image:url(images/button_multi_disclose.png);background-position:right 0;background-repeat:no-repeat;outline:none;user-select:none;-moz-user-select:none;-khtml-user-select:none;}a.jxButtonDisclose img{width:16px;height:16px;margin:0;padding:0;border:0;background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}a.jxButtonDisclose:focus,a.jxButtonDisclose:active{background-position:right -96px;}a.jxButtonDisclose:hover{background-position:right -48px;}a.jxButtonDisclosePressed{background-position:right -120px;}.jxDisabled a.jxButtonDisclose,.jxDisabled a.jxButtonDisclose:focus,.jxDisabled a.jxButtonDisclose:active,.jxDisabled a.jxButtonDisclose:hover,.jxDisabled a.jxButtonDisclosePressed{cursor:default;background-position:right 0;}ul.jxToolbar .jxButtonHover{background-position:left -24px!important;}ul.jxTo
 olbar .jxButtonHover span.jxButtonContent{background-position:right -24px!important;}.jxFlyout .jxChrome{background-image:url(images/flyout_chrome.png);padding:5px 5px 7px 6px;}.jxFlyout{position:absolute;display:block;z-index:100;margin:0;padding:0;}.jxFlyoutContent{position:relative;display:block;overflow:auto;margin:6px 6px 8px 7px;background-color:#fff;border:1px solid #999;}.jxButtonMulti,.jxButtonMulti span.jxButtonContent{background-image:url(images/button_multi.png);}.jxButtonEditCombo,.jxButtonEditCombo span.jxButtonContent{background-image:url(images/button_combo.png);}.jxButtonMulti span.jxButtonContent span{padding-right:21px;}.jxButtonEditCombo span.jxButtonContent span{font-size:0;}.jxButtonComboDefault span.jxButtonContent span,.jxButtonComboDefault input{font-style:italic;color:#999;}.jxButtonEditCombo input{float:left;line-height:16px;height:16px;padding:0 4px;margin:0;border:none;font-size:11px;font-family:Arial,Helvetica,sans-serif;background-color:transpa
 rent;}.jxChrome{position:absolute;display:block;font-size:0;line-height:0;z-index:-1;width:100%;height:100%;top:0;left:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxChromeDrag{opacity:.5;-ms-filter:"Alpha(opacity=50)";}.jxChromeTL{position:absolute;overflow:hidden;left:0;top:0;width:50%;height:50%;}.jxChromeTR{position:absolute;overflow:hidden;left:50%;top:0;width:50%;height:50%;}.jxChromeBL{position:absolute;overflow:hidden;left:0;top:50%;width:50%;height:50%;}.jxChromeBR{position:absolute;overflow:hidden;left:50%;top:50%;width:50%;height:50%;}.jxChromeTL img{position:absolute;top:0;left:0;width:1000px;height:600px;}.jxChromeTR img{position:absolute;top:0;right:0;width:1000px;height:600px;}.jxChromeBL img{position:absolute;bottom:0;left:0;width:1000px;height:600px;}.jxChromeBR img{position:absolute;bottom:0;right:0;width:1000px;height:600px;}.jxColorBar{position:relative;overflow:hidden;}table.jxColorGrid{position:relative;border-collapse:collapse;emp
 ty-cells:show;clear:both;padding:0;margin:0;}.jxColorGrid tr{padding:0;margin:0;}.jxColorGrid td{border:1px solid #000;padding:0;margin:0;}.jxColorGrid td.emptyCell{border:0 solid #000;}.jxColorGrid td.emptyCell span{display:block;width:7px;height:7px;line-height:0;font-size:0;border:0 solid #000;padding:1px;margin:0;}.jxColorGrid a.colorSwatch{display:block;width:7px;height:7px;line-height:0;font-size:0;border:0 solid #000;margin:0;padding:1px;}.jxColorGrid a.borderWhite:hover{border:1px solid #fff;padding:0;}.jxColorGrid a.borderBlack:hover{border:1px solid #000;padding:0;}input.jxHexInput{width:55px;vertical-align:middle;}input.jxAlphaInput{width:30px;vertical-align:middle;}div.jxColorPreview{float:left;position:relative;width:20px;height:20px;border:1px solid #000;margin:2px;vertical-align:middle;background-image:url('images/grid.png');overflow:hidden;}.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch{display:block;float:left;width:14px;height:14px;border:1px soli
 d #000;background-image:url('images/grid.png');background-position:0 0;background-repeat:repeat;padding-right:0!important;}.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch span{display:block;width:14px;height:14px;position:absolute;padding-right:0;background:none;}div.jxColorPreview img{position:absolute;z-index:0;}div.jxColorPreview div{width:20px;height:10px;position:absolute;display:block;left:0;z-index:1;font-size:10px;line-height:0;}div.jxColorPreview div.jxColorSelected{top:0;}div.jxColorPreview div.jxColorHover{bottom:0;}label.jxColorLabel,label.jxAlphaLabel{width:auto;font-family:Arial,sans-serif;font-size:11px;line-height:24px;padding:2px;vertical-align:middle;}a.jxColorClose{position:absolute;top:0;right:0;width:16px;height:16px;}a.jxColorClose img{width:16px;height:16px;}.jxClearer{display:block;position:relative;float:none;clear:both;font-size:0;line-height:0;width:0;height:0;margin:0;padding:0;}.jxDisabled{opacity:.4;-ms-filter:"Alpha(opacity=40)";}.jxDi
 sabled *{-ms-filter:"Alpha(opacity=40)";}.jxMask{opacity:.5;-ms-filter:"Alpha(opacity=50)";background-color:#fff;}.jxModalMask{background-color:#000;opacity:.2;-ms-filter:"Alpha(opacity=20)";}.jxEventMask{background-image:url(images/a_pixel.png);}.jxSpinner{position:absolute;opacity:.5;-ms-filter:"Alpha(opacity=50)";z-index:999;background:#fff;}.jxSpinnerMessage{text-align:center;font-weight:bold;}.jxSpinnerSmall .jxSpinnerMessage{margin:0;padding:0;font-size:11px;line-height:24px;}.jxSpinnerLarge .jxSpinnerImage{background:url(images/spinner_24.gif) no-repeat;width:24px;height:24px;margin:0 auto;}.jxSpinnerSmall .jxSpinnerImage{background:url(images/spinner_16.gif) no-repeat;width:16px;height:16px;margin-right:4px;display:inline-block;vertical-align:middle;}.jxConfirmQuestion,.jxPrompt{text-align:center;padding:10px;margin-top:10px;}.jxDialog .jxChrome{background-image:url(images/dialog_chrome.png);}.jxDialog{display:block;z-index:1000;overflow:hidden;visibility:hidden;}.jx
 DialogContentContainer{z-index:1;margin:0 11px 13px 12px;border:1px solid #b7b7b7;background-color:#f0f0f0;}.jxDialogContent{display:block;position:relative;overflow:auto;padding:0;z-index:1;}.jxDialogTitle{display:block;position:relative;background-image:url(images/a_pixel.png);text-align:center;height:24px;line-height:24px;z-index:1;margin:6px 6px 0 7px;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxDialogMin .jxDialogTitle{margin-bottom:8px;}.jxDialogMoveable,.jxDialogMoveable .jxDialogLabel{cursor:move;}.jxDialogIcon{position:absolute;left:2px;top:3px;width:16px;height:16px;border:none;padding:0;margin:0;}.jxDialogLabel{font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:bold;line-height:21px;color:#000;white-space:nowrap;cursor:default;}.jxDialogResize{position:absolute;bottom:7px;right:6px;width:16px;height:16px;z-index:2;border:0;cursor:se-resize;background-image:url(images/dialog_resize.png);}.jxDialogControls{position:absolute;top:3
 px;right:2px;height:16px;width:80px;}.jxDialogControls img{background-image:url('images/panel_controls.png');background-repeat:no-repeat;border:0;margin:0;width:16px;height:16px;}.jxDialogClose img{background-position:0 -32px;}.jxDialogMenu img{background-position:0 -48px;}.jxDialogHelp img{background-position:0 -64px;}.jxDialogCollapse img{background-position:0 -16px;}.jxDialogMin .jxDialogCollapse img{background-position:0 0;}.jxDialogMax .jxDialogCollapse img{background-position:0 -16px;}.jxDialogMaximize img{background-position:0 -80px;}.jxDialogMaximized .jxDialogMaximize img{background-position:0 -96px;}.jxDialogLoading img{border:0;margin:0;width:16px;height:16px;visibility:hidden;position:absolute;top:1px;left:2px;}.jxDialogControls .jxButtonContainer,.jxDialogControls .jxButton,.jxDialogControls .jxButton:hover,.jxDialogControls .jxButton:active,.jxDialogControls .jxButtonActive,.jxDialogControls .jxButtonActive:hover,.jxDialogControls .jxButtonActive:active,.jxDial
 ogControls .jxDisabled .jxButton,.jxDialogControls .jxDisabled .jxButton:hover,.jxDialogControls .jxDisabled .jxButton:active{padding:0;margin:0;border:none;background-color:transparent;background-image:none;}.jxDialogControls .jxBarContainer{position:absolute;right:0;background-image:none;background-color:transparent;margin:0;padding:0;border:none;height:16px;}.jxDialogControls .jxBarScroller{left:auto;right:0;}.jxDialogControls ul.jxToolbar{float:right;}.jxDialogControls ul.jxToolbar,.jxDialogControls li.jxToolItem{background-image:none;background-color:transparent;margin:0;padding:0;border:none;}div.jxFileInputs{position:relative;}div.jxFileFake{position:absolute;top:0;left:0;z-index:1;}div.jxFileFake span{float:left;display:block;}div.jxFileFake .jxInputContainer{width:150px;}div.jxFileFake .jxInputText{width:135px;}div.jxFileFake .jxButtonContainer{margin-top:2px;float:left;}.jxInputFile{position:relative;text-align:right;-moz-opacity:0;filter:alpha(opacity:0);opacity:0
 ;z-index:2;margin-top:-5px;height:35px;}.jxForm{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxFieldset{display:block;position:relative;border:1px solid #ccc;margin:10px;padding:5px;}.jxFieldsetLegend,.jxFieldset legend{position:relative;margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:26px;color:#000;}.jxInputContainer{display:block;position:relative;padding:0;margin:2px;border:none;}.jxInputLabel,.jxInputTag{display:-moz-inline-box;display:inline-block;margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:26px;color:#000;}.jxInputLabel{vertical-align:top;}.jxInputTag{vertical-align:bottom;}.jxInputWrapper{display:inline-block;white-space:normal;position:relative;}.jxInputText,.jxInputPassword,.jxInputTextarea,.jxInputCombo,.jxInputColor{margin:0 4px;padding:4px;border:1px solid #bbb;width:232px;font-family:Arial,Helvetica,sans-serif;font
 -size:12px;line-height:16px;color:#000;}.jxInputCombo,.jxInputColor{padding:4px 20px 4px 20px;width:200px;}.jxInputIconHidden .jxInputCombo{padding-left:4px;width:216px;}.jxInputIcon{position:absolute;width:16px;height:16px;left:0;top:0;margin:4px 4px 4px 8px;}.jxInputContainerColor .jxInputIcon{border:1px solid #bbb;width:15px;height:15px;}.jxInputIconHidden .jxInputIcon{display:none;}.jxInputRevealer{position:absolute;width:16px;height:16px;right:0;top:0;margin:4px 8px 4px 4px;font-size:0;line-height:0;}img.jxInputRevealerIcon{background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}.jxInputRevealer .jxButtonContainer,.jxInputRevealer .jxButton{padding:0;margin:0;border:0;background-color:transparent;background-image:none;}.jxInputSelect{margin:0 4px;padding:3px 4px 3px 1px;border:1px solid #bbb;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxInputRadio,.jxInputCheck{margin:5px;font-family:A
 rial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxInputText:focus,.jxInputPassword:focus,.jxInputTextarea:focus,.jxInputSelect:focus,.jxInputCombo:focus,.jxInputColor:focus{border:1px solid #000;}.jxInputContainer .jxButtonContainer{padding:0;margin:0 4px;}.jxInputGroup{border:none;padding:0;margin:2px;}.jxInputGroup legend{font-size:0;line-height:0;padding:0;margin:0;border:none;}.jxInputGroup .jxFieldsetLegend{font-size:12px;}.jxInputGroup .jxInputLabel{width:auto;}.jxFieldError .jxInputText,.jxFieldError .jxInputPassword,.jxFieldError .jxInputTextarea,.jxFieldError .jxInputSelect,.jxFieldError .jxInputCombo,.jxFieldError .jxInputColor{background-color:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;}.jxFieldSuccess .jxInputText,.jxFieldSuccess .jxInputPassword,.jxFieldSuccess .jxInputTextarea,.jxFieldSuccess .jxInputSelect,.jxFieldSuccess .jxInputCombo,.jxFieldSuccess .jxInputColor{background-color:#E6EFC2;color:#264409;border-color:#C6D880;}.jxFieldErro
 r .jxInputText:focus,.jxFieldError .jxInputPassword:focus,.jxFieldError .jxInputTextarea:focus,.jxFieldError .jxInputSelect:focus,.jxFieldError .jxInputCombo:focus,.jxFieldError .jxInputColor:focus{border-color:#8a1f11;}.jxFieldSuccess .jxInputText:focus,.jxFieldSuccess .jxInputPassword:focus,.jxFieldSuccess .jxInputTextarea:focus,.jxFieldSuccess .jxInputSelect:focus,.jxFieldSuccess .jxInputCombo:focus,.jxFieldSuccess .jxInputColor:focus{border-color:#264409;}.jxFieldError .jxInputLabel,.jxFieldError .jxInputTag{color:#8a1f11;}.jxFieldSuccess .jxInputLabel,.jxFieldSuccess .jxInputTag{color:#264409;}.jxFormInline .jxInputContainer,form .jxFormInline .jxInputContainer,form .jxFieldset span.jxFormInline,form.jxForm span.jxFormInline{display:inline;}.jxFormInline .jxInputLabel,form .jxFormInline .jxInputLabel,form span.jxFormInline .jxInputLabel{display:inline;width:auto;}.jxFormInline .jxInputTag,form .jxFormInline .jxInputTag,form span.jxFormInline .jxInputTag{display:inline;}
 .jxFormInline .jxInputGroup,form .jxFormInline .jxInputGroup{padding-left:0;}.jxFormInline .jxInputGroup .jxFieldsetLegend,form .jxFormInline .jxInputGroup .jxFieldsetLegend{position:relative;left:auto;}.jxFormInline .jxInputGroup .jxInputLabel,form .jxFormInline .jxInputGroup .jxInputLabel{display:inline;width:auto;}.jxFormBlock .jxInputContainer,form .jxFormBlock .jxInputContainer,form .jxFieldset span.jxFormBlock,form.jxform span.jxFormBlock{display:block;}.jxFormBlock .jxInputLabel,form .jxFormBlock .jxInputLabel,form span.jxFormBlock .jxInputLabel{display:block;width:auto;margin-left:4px;}.jxFormBlock .jxInputContainerCheck .jxInputLabel,.jxFormBlock .jxInputContainerRadio .jxInputLabel,form .jxFormBlock .jxInputContainerCheck .jxInputLabel,form .jxFormBlock .jxInputContainerRadio .jxInputLabel,form span.jxFormBlock .jxInputContainerCheck .jxInputLabel,form span.jxFormBlock .jxInputContainerRadio .jxInputLabel{display:-moz-inline-box;display:inline-block;}.jxFormBlock .
 jxInputGroup,form .jxFormBlock .jxInputGroup{padding-left:0;}.jxFormBlock .jxInputGroup .jxFieldsetLegend,form .jxFormBlock .jxInputGroup .jxFieldsetLegend{position:relative;left:auto;}.jxFormBlock .jxInputGroup .jxInputLabel,form .jxFormBlock .jxInputGroup .jxInputLabel{display:-moz-inline-box;display:inline-block;width:auto;}.jxFormInlineblock .jxInputContainer,form .jxFormInlineblock .jxInputContainer,form .jxFieldset span.jxFormInlineblock,form.jxForm span.jxFormInlineblock{display:block;white-space:nowrap;}.jxFormInlineblock .jxInputLabel,form .jxFormInlineblock .jxInputLabel,form span.jxFormInlineblock .jxInputLabel{display:-moz-inline-box;display:inline-block;width:200px;}.jxFormInlineblock .jxInputGroup,form .jxFormInlineblock .jxInputGroup{padding-left:200px;}.jxFormInlineblock .jxInputGroup .jxFieldsetLegend,form .jxFormInlineblock .jxInputGroup .jxFieldsetLegend{position:absolute;left:-200px;width:200px;}.jxFormInlineblock .jxInputGroup .jxInputLabel,form .jxFormI
 nlineblock .jxInputGroup .jxInputLabel{display:-moz-inline-box;display:inline-block;width:auto;}.jxGridCellContent .jxInputContainer,.jxGridCellContent .jxInputRadio,.jxGridCellContent .jxInputCheck{display:inline;position:relative;border:none;margin:0;padding:0;font-size:0;line-height:0;color:#000;}.jxGridContainer{position:absolute;top:0;left:0;border-left:0 solid #d8d8d8;border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;overflow:hidden;}.jxGridTable{width:100%;position:relative;table-layout:fixed;border-collapse:collapse;border-style:none;cursor:default;}.jxGridTableBody{position:relative;table-layout:fixed;border-collapse:collapse;border-style:none;cursor:default;}.jxGridCell{border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;overflow:hidden;}.jxGridCellContent{position:relative;display:-moz-inline-box;display:inline-block;overflow:hidden;padding:0 3px;overflow:hidd
 en;vertical-align:middle;font-family:Arial,Verdana,sans-serif;font-size:11px;font-weight:normal;line-height:16px;white-space:nowrap;cursor:cell;text-overflow:ellipsis;}.jxGridColHead .jxGridCellContent{padding:0 3px;text-align:center;font-weight:bold;color:#333;height:100%;}.jxGridRowHead .jxGridCellContent{text-align:center;font-weight:bold;color:#333;}.jxGridColHead{height:100%;border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;background-color:#f2f2f2;background-image:url('images/table_col.png');background-position:0 0;background-repeat:repeat-x;text-align:center;cursor:default;padding:0;white-space:nowrap;overflow:hidden;}.jxGridRowHead{border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;background-color:#f2f2f2;background-image:url('images/table_row.png');background-position:0 0;background-repeat:repeat-y;text-align:center;cursor:default;o
 verflow:hidden;white-space:nowrap;}.jxGridRowAll{background-color:#fff;}.jxGridColumnHeaderSelected{background-color:#e1e1e1;background-position:0 -200px;}.jxGridRowHeaderSelected{background-color:#e1e1e1;background-position:-400px 0;}.jxGridColumnSelected{background-color:#f7f7f7;}.jxGridRowSelected td,.jxGridRowSelected th{background-color:#f7f7f7;}td.jxGridCellSelected,th.jxGridCellSelected{background-color:#ebebeb;}.jxGridColumnHeaderPrelight{background-color:#cee5ff;background-position:0 -300px;}.jxGridRowHeaderPrelight{background-color:#cee5ff;background-position:-600px 0;}.jxGridColumnPrelight{background-color:#e5f1ff;}.jxGridRowPrelight td,.jxGridRowPrelight th{background-color:#e5f1ff;}td.jxGridCellPrelight,th.jxGridCellPrelight{background-color:#cce3ff;}.jxGridHeader .jxColSortable img{vertical-align:top;width:16px;height:16px;background-image:url('images/emblems.png');background-repeat:no-repeat;background-position:right top;}.jxGridHeader .jxColSortable .jxGridCe
 llContent{margin-right:-16px;}.jxGridHeader .jxGridColumnSortedAsc img{background-position:right -162px;}.jxGridHeader .jxGridColumnSortedDesc img{background-position:right -18px;}.jxGridColumnResize,.jxGridRowResize{display:block;position:absolute;background-image:url(images/a_pixel.png);z-index:1;}.jxGridColumnResize{right:0;top:0;height:100%;width:4px;cursor:col-resize;}.jxColSortable .jxGridColumnResize{right:8px;}.jxGridRowResize{left:0;bottom:0;height:4px;width:100%;cursor:row-resize;}.jxGridEditorPopup{min-width:130px;margin:3px;font-size:10px;box-shadow:2px 2px 5px #888;-moz-box-shadow:2px 2px 5px #888;-webkit-box-shadow:2px 2px 5px #888;background-color:#ddd;border:1px solid #999;position:absolute;}.jxGridEditorPopupInnerWrapper{position:relative;height:100%;width:100%;}.jxListView{position:relative;display:block;list-style:none;margin:0;padding:0;}.jxListView .jxListItemContainer{position:relative;display:block;outline:none;overflow:hidden;border:none;margin:0 1px;
 padding:0;}.jxListItem{position:relative;display:block;cursor:pointer;outline:none;overflow:hidden;border:none;margin:0 1px;padding:0;z-index:0;font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#000;text-decoration:none;line-height:20px;height:20px;}.jxListView .jxHover{margin:0;border-left:1px solid #CDDFFD;border-right:1px solid #CDDFFD;background-image:url(images/listitem.png);background-repeat:repeat-x;background-color:#CDE5FF;background-position:left -24px;}.jxListItem:focus{margin:0;border-left:1px dotted #75ADFF;border-right:1px dotted #75ADFF;background-image:url(images/listitem.png);background-repeat:repeat-x;background-position:left -72px;}.jxListView .jxPressed,.jxListView .jxSelected{margin:0;border-left:1px solid #8AABFB;border-right:1px solid #8AABFB;background-color:#CDE5FF;background-image:url(images/listitem.png);background-repeat:repeat-x;background-position:left -48px;}.jxMenuContainer .jxChrome{background-image:url(images/flyout_chrome.png);padd
 ing:5px 5px 7px 6px;}.jxButtonMenu span.jxMenuItemSpan{padding-right:16px;}.jxMenuContainer{position:absolute;top:0;left:-10000px;display:none;z-index:2000;padding:0;}ul.jxMenu{display:block;position:relative;list-style-type:none;padding:1px;margin:6px 6px 8px 7px;background-color:#fff;border:1px solid #999;}li.jxMenuItemContainer{display:block;position:relative;font-size:0;line-height:0;margin:0;padding:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}a.jxMenuItem{display:block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;outline:none;border:1px solid #fff;background-image:url(images/menuitem.png);background-repeat:no-repeat;background-position:left top;font-family:Arial,Helvetica,sans-serif;font-size:11px;text-decoration:none;margin:0;padding:0;color:#000;}a.jxMenuItemActive{background-position:left -98px;}a.jxMenuItem:focus{background-position:left -74px;}a.jxMenuItem:focus span.jxMenuItemContent{border-right:1px dotted #75ADFF;}a
 .jxMenuItemActive:focus{background-position:left -170px;}a.jxMenuItem:hover{background-color:#CDE5FF;background-position:left -26px;}a.jxMenuItem:hover span.jxMenuItemContent{border-right:1px solid #C5E0FF;}a.jxMenuItemActive:hover{background-position:left -122px;}a.jxMenuItemPressed,a.jxMenuItemPressed:hover{background-color:#CDE5FF;background-position:left -50px;}.jxDisabled a.jxMenuItem,.jxDisabled span.jxMenuItemContent span{cursor:default;}.jxDisabled a.jxMenuItem:focus,.jxDisabled a.jxMenuItemPressed,.jxDisabled a.jxMenuItemPressed:hover,.jxDisabled a.jxMenuItem:hover{background-color:#fff;background-position:left top;border-right:1px solid #fff;}.jxDisabled a.jxMenuItem:hover span.jxMenuItemContent{border-right:1px solid #fff;}span.jxMenuItemContent{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:0;line-height:0;white-space:nowrap;padding:0 20px 0 0;margin:0;border-right:1px solid #fff;}.jxButtonSubMenu span.jxMenuItemContent,.jxButton
 SubMenu:hover span.jxMenuItemContent{background-image:url(images/emblems.png);background-position:right -30px;background-repeat:no-repeat;}img.jxMenuItemIcon{position:absolute;top:2px;left:2px;width:16px;height:16px;background-position:left center;background-repeat:no-repeat;}span.jxMenuItemContent span{display:block;position:relative;cursor:pointer;margin:0;padding:2px 0 2px 22px;font-size:16px;line-height:16px;color:#000;}span.jxMenuItemContent span.jxMenuItemLabel{color:#000;font-size:11px;}.jxMenuItemToggle img.jxMenuItemIcon,.jxMenuItemToggleSet img.jxMenuItemIcon{background-image:url(images/emblems.png);background-position:2px 0;background-repeat:no-repeat;}.jxMenuItemToggle a.jxMenuItemActive img.jxMenuItemIcon{background-position:2px -48px;}.jxMenuItemToggleSet a.jxMenuItemActive img.jxMenuItemIcon{background-position:2px -64px;}ul.jxMenu span.jxMenuSeparator{display:block;font-size:10px;line-height:10px;background-image:url(images/toolbar_separator_v.png);background
 -repeat:repeat-x;background-position:left center;}.jxMessage{text-align:center;padding:10px;margin-top:10px;}.jxNoticeListContainer{border:none;padding:0;margin:0;font-size:0;line-height:0;z-index:500;}.jxNoticeList{position:relative;}.jxNoticeItemContainer{position:relative;overflow:hidden;}.jxNoticeItemContainer .jxChrome{background-image:url(images/flyout_chrome.png);padding:5px 5px 7px 6px;}.jxHasChrome .jxNoticeItem{margin:6px 6px 8px 7px;}.jxNoticeItem{position:relative;border:2px solid #ccc;margin:0;padding:0;background-color:#f8f8f8;background-image:url(images/notice.png);background-repeat:repeat-x;background-position:left bottom;}.jxNoticeIcon{position:absolute;top:0;left:0;margin:6px;width:16px;height:16px;background-image:url(images/icons.png);background-repeat:no-repeat;background-position:0 0;}.jxNotice{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:18px;color:#666;margin:0;border:none;padding:6px 26px 6px 10px;
 }.jxNoticeError .jxNoticeItem{background-color:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;background-image:url(images/notice_error.png);}.jxNoticeWarning .jxNoticeItem{background-color:#FFF6BF;color:#514721;border-color:#FFD324;background-image:url(images/notice_warning.png);}.jxNoticeSuccess .jxNoticeItem{background-color:#E6EFC2;color:#264409;border-color:#C6D880;background-image:url(images/notice_success.png);}.jxNoticeInformation .jxNoticeItem{background-color:#F8F8F8;color:#666;border-color:#CCC;background-image:url(images/notice.png);}.jxNoticeError .jxNotice{color:#8a1f11;padding-left:28px;}.jxNoticeWarning .jxNotice{color:#514721;padding-left:28px;}.jxNoticeSuccess .jxNotice{color:#264409;padding-left:28px;}.jxNoticeInformation .jxNotice{color:#666;padding-left:28px;}.jxNoticeError .jxNoticeIcon{background-position:0 -32px;}.jxNoticeWarning .jxNoticeIcon{background-position:0 -16px;}.jxNoticeSuccess .jxNoticeIcon{background-position:0 -48px;}.jxNoticeInformation .jxN
 oticeIcon{background-position:0 -64px;}.jxNoticeClose{position:absolute;top:0;right:0;margin:6px;width:16px;height:16px;background-image:url(images/tab_close.png);background-position:0 0;background-repeat:no-repeat;}.jxPanel{display:block;position:relative;}.jxPanelContentContainer{overflow:hidden;background-color:#f0f0f0;}.jxPanelContent{position:relative;display:block;overflow:auto;background-color:#fff;margin:0;padding:0;}.jxPanelTitle{display:block;position:relative;background-image:url(images/panelbar.png);background-repeat:repeat-x;background-position:left top;height:22px;margin:0;padding:0;text-align:center;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxPanelBar{position:absolute;line-height:1px;width:100%;height:5px;cursor:row-resize;background-color:#f0f0f0;z-index:2;}.jxPanelIcon{position:absolute;left:2px;top:3px;width:16px;height:16px;border:none;padding:0;margin:0;}.jxPanelLabel{padding-left:25px;font-family:Arial,Helvetica,sans-serif;font-si
 ze:11px;font-weight:bold;line-height:21px;color:#000;white-space:nowrap;}.jxPanelControls{position:absolute;top:3px;right:2px;height:16px;width:80px;overflow:hidden;}.jxPanelControls img{background-image:url('images/panel_controls.png');background-repeat:no-repeat;border:0;margin:0;width:16px;height:16px;}.jxPanelClose img{background-position:0 -32px;}.jxPanelMenu img{background-position:0 -48px;}.jxPanelHelp img{background-position:0 -64px;}.jxPanelCollapse img{background-position:0 -16px;}.jxPanelMin .jxPanelCollapse img{background-position:0 0;}.jxPanelMax .jxPanelCollapse img{background-position:0 -16px;}.jxPanelMaximize img{background-position:0 0;}.jxPanelLoading img{border:0;margin:0;width:16px;height:16px;visibility:hidden;position:absolute;top:1px;left:2px;}.jxPanelControls .jxButtonContainer,.jxPanelControls .jxButton,.jxPanelControls .jxButton:hover,.jxPanelControls .jxButton:active,.jxPanelControls .jxButtonActive,.jxPanelControls .jxButtonActive:hover,.jxPanelCo
 ntrols .jxButtonActive:active,.jxPanelControls .jxDisabled .jxButton,.jxPanelControls .jxDisabled .jxButton:hover,.jxPanelControls .jxDisabled .jxButton:active{padding:0;margin:0;border:none;background-color:transparent;background-image:none;}.jxPanelControls div.jxBarTop{position:absolute;right:0;background-image:none;background-color:transparent;margin:0;padding:0;border:none;height:16px;}.jxPanelControls .jxBarScroller{left:auto;right:0;}.jxPanelControls ul.jxToolbar{float:right;}.jxPanelControls ul.jxToolbar,.jxPanelControls li.jxToolItem{background-image:none;background-color:transparent;margin:0;padding:0;border:none;}.jxProgressBar-container{position:relative;display:block;width:100%;}.jxProgressBar-message{position:relative;display:block;color:black;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:20px;}.jxProgressBar{position:relative;display:block;width:100%;height:20px;border:none;margin:0;padding:0;}.jxProgressBar-outline{position:absolute;displa
 y:block;top:0;left:0;z-index:10;height:20px;border-left:1px solid #cbc8c8;border-right:1px solid #cbc8c8;background-image:url(images/progressbar.png);background-position:0 -140px;width:100%;}.jxProgressBar-fill{position:absolute;display:block;top:0;left:0;z-index:20;height:20px;border:none;background-image:url(images/progressbar.png);background-position:0 0;}.jxProgressStarting .jxProgressBar-fill{border:none;}.jxProgressWorking .jxProgressBar-fill{border-left:1px solid #49afe8;}.jxProgressFinished .jxProgressBar-fill{border-left:1px solid #49afe8;border-right:1px solid #49afe8;}.jxProgressBar-text{position:absolute;display:block;overflow:visible;top:0;left:1px;width:auto;height:20px;z-index:30;border:none;margin:0;padding:0 0 0 4px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:20px;white-space:nowrap;}.jxHasVerticalScrollbar,.jxHasHorizontalScrollbar{position:relative;overflow:hidden;}.jxScrollbarChildWrapper{overflow:hidden;}.jxHasVerticalScrollbar{padd
 ing-right:25px;}.jxHasVerticalScrollbar .jxScrollbarContainer{position:absolute;right:0;top:0;width:20px;height:100%;border:none;border-left:1px solid black;}.jxHasVerticalScrollbar .jxScrollLeft,.jxHasVerticalScrollbar .jxScrollRight{width:20px;height:20px;display:block;background-color:blue;}.jxHasVerticalScrollbar .jxSliderContainer{height:100%;width:20px;border:none;}.jxHasHorizontalScrollbar{padding-bottom:25px;}.jxHasHorizontalScrollbar .jxScrollbarContainer{position:absolute;bottom:0;left:0;height:20px;width:100%;border:none;border-top:1px solid black;}.jxHasHorizontalScrollbar .jxScrollLeft,.jxHasHorizontalScrollbar .jxScrollRight{width:20px;height:20px;display:block;background-color:blue;}.jxHasHorizontalScrollbar .jxScrollLeft{float:left;}.jxHasHorizontalScrollbar .jxScrollRight{float:right;}.jxHasHorizontalScrollbar .jxSlider{float:left;}.jxHasHorizontalScrollbar .jxSliderContainer{height:20px;width:100%;border:none;}.jxHasVerticalScrollbar .jxSliderKnob,.jxHasHor
 izontalScrollbar .jxSliderKnob{width:20px;height:20px;background-color:black;cursor:pointer;}.jxSliderContainer{width:100%;height:10px;border:1px solid black;}.jxSliderKnob{width:10px;height:10px;background-color:black;cursor:pointer;}.jxSplitterMask{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;background-image:url(images/a_pixel.png);z-index:1;}.jxSplitBarHorizontal{display:block;position:absolute;font-size:0;line-height:0;margin:0;padding:0;border:none;width:5px;height:100%;cursor:col-resize;background-color:#f0f0f0;z-index:2;}.jxSplitBarVertical{display:block;position:absolute;font-size:0;line-height:0;margin:0;padding:0;border:none;width:100%;height:5px;cursor:row-resize;background-color:#f0f0f0;z-index:2;}.jxSplitContainer{display:block;position:relative;margin:0;padding:0;border:none;overflow:hidden;}.jxSplitArea{display:block;position:absolute;margin:0;padding:0;border:none;z-index:0;}.jxSplitBarDrag{background-color:#eee;}.jxSnapHorizontalBef
 ore{width:5px;height:5px;position:absolute;top:0;left:0;background-color:#aaa;}.jxSnapHorizontalAfter{width:5px;height:5px;position:absolute;top:0;left:0;background-color:#aaa;}.jxTabSetContainer{position:relative;display:block;overflow:hidden;width:200px;height:200px;margin:0;padding:0;background-color:#fff;}.jxTabSetContainer .jxBarContainer{z-index:auto;}.tabContent{display:none;position:relative;width:100%;height:100%;overflow:auto;}.tabContentActive{display:block;}span.jxTabContainer{display:block;position:relative;margin:0;padding:2px;border:none;}a.jxTab{display:-moz-inline-box;display:inline-block;position:relative;cursor:pointer;user-select:none;-moz-user-select:none;-khtml-user-select:none;margin:0;padding:0;border:none;background-repeat:no-repeat;text-decoration:none;outline:none;}span.jxTabContent{display:-moz-inline-box;display:inline-block;font-size:0;line-height:0;margin:0;padding:0;border:none;background-repeat:no-repeat;}img.jxTabIcon{display:-moz-inline-box
 ;display:inline-block;position:relative;width:16px;height:16px;background-position:center center;background-repeat:no-repeat;}span.jxTabLabel{display:-moz-inline-box;display:inline-block;position:relative;cursor:pointer;margin:0;padding:0;color:#000;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;}a.jxTabClose{display:block;position:absolute;cursor:pointer;outline:none;user-select:none;-moz-user-select:none;-khtml-user-select:none;width:16px;height:16px;background-image:url(images/tab_close.png);background-position:0 0;background-repeat:no-repeat;}.jxDisabled a.jxTab,.jxDisabled span.jxTabContent span,.jxDisabled a.jxTabClose{cursor:default;}.jxTabBarTop .jxBarWrapper,.jxTabBarBottom .jxBarWrapper{padding-left:2px;}.jxBarTop span.jxTabContainer,.jxBarBottom span.jxTabContainer{margin-right:-1px;padding:2px 0;}.jxBarTop a.jxTab,.jxBarTop span.jxTabContent,.jxTabBarTop .jxBarControls a.jxButton,.jxTabBarTop .jxBarControls span.jxButtonContent{background-
 image:url(images/tab_top.png);}.jxBarBottom a.jxTab,.jxBarBottom span.jxTabContent,.jxTabBarBottom .jxBarControls a.jxButton,.jxTabBarBottom .jxBarControls span.jxButtonContent{background-image:url(images/tab_bottom.png);}.jxBarTop a.jxTabClose,.jxBarBottom a.jxTabClose{top:5px;right:5px;}.jxBarTop .jxTabClose span.jxTabContent,.jxBarBottom .jxTabClose span.jxTabContent{padding-right:16px;}.jxBarTop a.jxTab,.jxBarBottom a.jxTab{padding-left:4px;background-position:left -24px;}.jxBarTop span.jxTabContent,.jxBarBottom span.jxTabContent{padding:4px 4px 4px 0;background-position:right -24px;}.jxBarTop a.jxTabActive,.jxBarBottom a.jxTabActive{background-position:left -72px;}.jxBarTop a.jxTabActive span.jxTabContent,.jxBarBottom a.jxTabActive span.jxTabContent{background-position:right -72px;}.jxBarTop a.jxTab:focus,.jxBarBottom a.jxTab:focus{background-position:left -96px;}.jxBarTop a.jxTab:focus span.jxTabContent,.jxBarBottom a.jxTab:focus span.jxTabContent{background-position:r
 ight -96px;}.jxBarTop a.jxTabActive:focus,.jxBarBottom a.jxTabActive:focus{background-position:left -144px;}.jxBarTop a.jxTabActive:focus span.jxTabContent,.jxBarBottom a.jxTabActive:focus span.jxTabContent{background-position:right -144px;}.jxBarTop a.jxTab:hover,.jxBarTop a.jxTabActive:hover,.jxBarBottom a.jxTab:hover,.jxBarBottom a.jxTabActive:hover{background-position:left -48px;}.jxBarTop a.jxTab:hover span.jxTabContent,.jxBarTop a.jxTabActive:hover span.jxTabContent,.jxBarBottom a.jxTab:hover span.jxTabContent,.jxBarBottom a.jxTabActive:hover span.jxTabContent{background-position:right -48px;}.jxBarTop a.jxTabPressed,.jxBarTop a.jxTabPressed:focus,.jxBarBottom a.jxTabPressed,.jxBarBottom a.jxTabPressed:focus{background-position:left -120px;}.jxBarTop a.jxTabPressed span.jxTabContent,.jxBarTop a.jxTabPressed:focus span.jxTabContent,.jxBarBottom a.jxTabPressed span.jxTabContent,.jxBarBottom a.jxTabPressed:focus span.jxTabContent{background-position:right -120px;}.jxBarTo
 p .jxDisabled a.jxTab:focus,.jxBarTop .jxDisabled a.jxTab:active,.jxBarTop .jxDisabled a.jxTab:hover,.jxBarTop .jxDisabled a.jxTabPressed,.jxBarBottom .jxDisabled a.jxTab:focus,.jxBarBottom .jxDisabled a.jxTab:active,.jxBarBottom .jxDisabled a.jxTab:hover,.jxBarBottom .jxDisabled a.jxTabPressed{background-position:left -24px;}.jxBarTop .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarTop .jxDisabled a.jxTab:active span.jxTabContent,.jxBarTop .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarTop .jxDisabled a.jxTabPressed span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:active span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabPressed span.jxTabContent{background-position:right -24px;}.jxBarTop .jxDisabled a.jxTabActive:focus,.jxBarTop .jxDisabled a.jxTabActive:active,.jxBarTop .jxDisabled a.jxTabActive:hover,.jxBarBottom .jxDisabled a.jxTabActive:focus,.jxBarBo
 ttom .jxDisabled a.jxTabActive:active,.jxBarBottom .jxDisabled a.jxTabActive:hover{background-position:left -72px;}.jxBarTop .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarTop .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarTop .jxDisabled a.jxTabActive:hover span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:hover span.jxTabContent{background-position:right -72px;}.jxBarTop img.jxTabIcon,.jxBarBottom img.jxTabIcon{vertical-align:middle;}.jxBarTop span.jxTabLabel,.jxBarBottom span.jxTabLabel{vertical-align:middle;height:16px;padding:0 4px 0 4px;}.jxTabBarLeft .jxBarWrapper,.jxTabBarRight .jxBarWrapper{padding-top:2px;}.jxBarLeft span.jxTabContainer,.jxBarRight span.jxTabContainer{margin-bottom:-1px;padding:0 2px;}.jxBarLeft a.jxTab,.jxBarLeft span.jxTabContent{background-image:url(images/tab_left.png);}.jxBarRight a.jxTab,.jxB
 arRight span.jxTabContent{background-image:url(images/tab_right.png);}.jxBarLeft a.jxTabClose,.jxBarRight a.jxTabClose{top:5px;left:5px;}.jxBarLeft .jxTabClose span.jxTabContent,.jxBarRight .jxTabClose span.jxTabContent{padding-top:16px;}.jxBarLeft a.jxTab,.jxBarRight a.jxTab{padding-top:4px;background-position:-24px top;}.jxBarLeft span.jxTabContent,.jxBarRight span.jxTabContent{padding:0 4px 4px 4px;background-position:-24px bottom;}.jxBarLeft a.jxTabActive,.jxBarRight a.jxTabActive{background-position:-72px top;}.jxBarLeft a.jxTabActive span.jxTabContent,.jxBarRight a.jxTabActive span.jxTabContent{background-position:-72px bottom;}.jxBarLeft a.jxTab:focus,.jxBarRight a.jxTab:focus{background-position:-96px top;}.jxBarLeft a.jxTab:focus span.jxTabContent,.jxBarRight a.jxTab:focus span.jxTabContent{background-position:-96px bottom;}.jxBarLeft a.jxTabActive:focus,.jxBarRight a.jxTabActive:focus{background-position:-144px top;}.jxBarLeft a.jxTabActive:focus span.jxTabContent,
 .jxBarRight a.jxTabActive:focus span.jxTabContent{background-position:-144px bottom;}.jxBarLeft a.jxTab:hover,.jxBarLeft a.jxTabActive:hover,.jxBarRight a.jxTab:hover,.jxBarRight a.jxTabActive:hover{background-position:-48px top;}.jxBarLeft a.jxTab:hover span.jxTabContent,.jxBarLeft a.jxTabActive:hover span.jxTabContent,.jxBarRight a.jxTab:hover span.jxTabContent,.jxBarRight a.jxTabActive:hover span.jxTabContent{background-position:-48px bottom;}.jxBarLeft a.jxTabPressed,.jxBarLeft a.jxTabPressed:focus,.jxBarRight a.jxTabPressed,.jxBarRight a.jxTabPressed:focus{background-position:-120px top;}.jxBarLeft a.jxTabPressed span.jxTabContent,.jxBarLeft a.jxTabPressed:focus span.jxTabContent,.jxBarRight a.jxTabPressed span.jxTabContent,.jxBarRight a.jxTabPressed:focus span.jxTabContent{background-position:-120px bottom;}.jxBarLeft .jxDisabled a.jxTab:focus,.jxBarLeft .jxDisabled a.jxTab:active,.jxBarLeft .jxDisabled a.jxTab:hover,.jxBarLeft .jxDisabled a.jxTabPressed,.jxBarRight .j
 xDisabled a.jxTab:focus,.jxBarRight .jxDisabled a.jxTab:active,.jxBarRight .jxDisabled a.jxTab:hover,.jxBarRight .jxDisabled a.jxTabPressed{background-position:-24px top;}.jxBarLeft .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarLeft .jxDisabled a.jxTab:active span.jxTabContent,.jxBarLeft .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabPressed span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:active span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarRight .jxDisabled a.jxTabPressed span.jxTabContent{background-position:-24px bottom;}.jxBarLeft .jxDisabled a.jxTabActive:focus,.jxBarLeft .jxDisabled a.jxTabActive:active,.jxBarLeft .jxDisabled a.jxTabActive:hover,.jxBarRight .jxDisabled a.jxTabActive:focus,.jxBarRight .jxDisabled a.jxTabActive:active,.jxBarRight .jxDisabled a.jxTabActive:hover{background-position:-72px top;}.jxBarLeft .jxDisabled a.jxTabActive:focus
  span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabActive:hover span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:hover span.jxTabContent{background-position:-72px bottom;}.jxBarLeft span.jxTabLabel,.jxBarRight span.jxTabLabel{padding:4px 0 4px 0;}.jxBarContainer{display:block;position:relative;z-index:1;overflow:hidden;margin:0;padding:0;border:0;background-color:#f0f0f0;}.jxBarTop,.jxBarBottom{width:100%;height:28px;background-image:url(images/toolbar.png);background-repeat:repeat-x;background-position:0 0;overflow:hidden;}.jxTabBox .jxTabBarTop{background-image:url(images/tabbar.png);background-position:0 bottom;}.jxTabBox .jxTabBarBottom{background-image:url(images/tabbar_bottom.png);background-position:0 top;}.jxBarLeft,.jxBarRight{width:auto;height:100%;background-image:url(images/
 toolbar.png);background-repeat:repeat-x;background-position:0 0;float:left;overflow:hidden;}.jxTabBox .jxTabBarLeft{background-image:url(images/tabbar_left.png);background-repeat:repeat-y;background-position:right 0;}.jxTabBox .jxTabBarRight{background-image:url(images/tabbar_right.png);background-repeat:repeat-y;background-position:left 0;}.jxBarTop .jxBarScroller,.jxBarBottom .jxBarScroller{float:left;height:28px;overflow:hidden;z-index:0;}.jxBarTop .jxBarScroller .jxBarWrapper,.jxBarBottom .jxBarScroller .jxBarWrapper{float:left;height:28px;overflow:hidden;width:10000%;}.jxBarTop .jxBarControls .jxButtonContainer,.jxBarBottom .jxBarControls .jxButtonContainer{z-index:1;padding:2px 0;margin-left:-1px;}.jxBarTop .jxBarScrollLeft img.jxButtonIcon,.jxBarBottom .jxBarScrollLeft img.jxButtonIcon{background-image:url(images/emblems.png);background-position:0 -80px;}.jxBarTop .jxBarScrollRight img.jxButtonIcon,.jxBarBottom .jxBarScrollRight img.jxButtonIcon{background-image:url(i
 mages/emblems.png);background-position:0 -96px;}.jxBarControls{float:right;position:relative;font-size:0;line-height:0;}ul.jxToolbar,ul.jxTabBar{display:block;position:relative;float:left;clear:none;list-style-type:none;margin:0;padding:0;border:none;}li.jxToolItem{display:block;position:relative;float:left;font-size:0;line-height:0;white-space:nowrap;padding:0;margin:0;border:none;}li.jxToolItem .jxInputWrapper{white-space:nowrap;}li.jxToolItem span.jxBarSeparator{display:block;position:relative;float:left;font-size:0;line-height:0;border:0;margin:0;padding:4px;background-repeat:no-repeat;background-position:center center;}.jxBarTop li.jxToolItem span.jxBarSeparator,.jxBarBottom li.jxToolItem span.jxBarSeparator{width:8px;height:20px;background-image:url(images/toolbar_separator_h.png);}.jxBarLeft li.jxToolItem span.jxBarSeparator,.jxBarRight li.jxToolItem span.jxBarSeparator{width:20px;height:8px;background-image:url(images/toolbar_separator_v.png);}.jxBarLeft ul.jxToolbar
 ,.jxBarLeft ul.jxTabBar,.jxBarLeft li.jxToolItem,.jxBarRight ul.jxToolbar,.jxBarRight ul.jxTabBar,.jxBarRight li.jxToolItem{clear:both;}.jxToolbarAlignLeft ul{float:left;}.jxToolbarAlignRight ul{float:right;}.jxToolbarAlignCenter{text-align:center;}.jxToolbarAlignCenter ul{float:none;}.jxToolbarAlignCenter ul li{float:none;display:inline;}.jxTooltip{width:auto;height:auto;background-color:black;color:white;padding:5px;z-index:65536;}.jxTree,.jxTreeRoot{position:relative;display:block;list-style:none;margin:0;padding:0;}.jxTreeNest{list-style:none;margin:0;padding:0;background-repeat:repeat-y;background-position:left top;}li.jxTreeContainer{position:relative;display:block;margin:0;padding:0;background-repeat:no-repeat;background-position:left top;white-space:nowrap;font-size:0;line-height:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxTree li.jxTreeContainer{margin-left:16px;}a.jxTreeItem{position:relative;display:block;cursor:pointer;outline:none;overfl
 ow:hidden;background-image:url(images/tree_hover.png);background-repeat:repeat-x;background-position:left top;border:none;margin:0 1px 0 17px;padding:0 0 0 20px;z-index:0;font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#000;text-decoration:none;line-height:20px;height:20px;}a.jxTreeItem:focus{border-left:1px dotted #75ADFF;border-right:1px dotted #75ADFF;margin:0 0 0 16px;background-position:left -72px;}a.jxTreeItem:hover,li.jxTreeContainer a.jxHover{border-left:1px solid #CDDFFD;border-right:1px solid #CDDFFD;margin:0 0 0 16px;background-color:#CDE5FF;background-position:left -24px;}li.jxTreeContainer a.jxSelected,li.jxTreeContainer a.jxSelected:hover,li.jxTreeContainer a.jxPressed,li.jxTreeContainer a.jxPressed:hover{border-left:1px solid #8AABFB;border-right:1px solid #8AABFB;margin:0 0 0 16px;background-color:#CDE5FF;background-position:left -48px;}li.jxDisabled a.jxTreeItem{cursor:default;}li.jxDisabled a.jxTreeItem:focus,li.jxDisabled a.jxTreeItem:hover{bac
 kground-position:left top;background-color:transparent;border:none;margin:0 1px 0 17px;}.jxTreeNest{background-image:url(images/tree_vert_line.png);}img.jxTreeImage,img.jxTreeIcon{position:absolute;display:inline;left:0;top:0;width:16px;height:20px;z-index:1;background-image:url(images/tree.png);background-repeat:no-repeat;border:0;margin:0;}img.jxTreeIcon{height:16px;top:2px;left:1px;}.jxTreeBranchOpen .jxTreeIcon,.jxTreeBranchLastOpen .jxTreeIcon{background-position:left -40px;}.jxTreeBranchOpen .jxTreeImage{background-position:left -100px;}.jxTreeBranchLastOpen .jxTreeImage{background-position:left -160px;}.jxTreeBranchClosed .jxTreeIcon,.jxTreeBranchLastClosed .jxTreeIcon{background-position:left -20px;}.jxTreeBranchClosed .jxTreeImage{background-position:left -80px;}.jxTreeBranchLastClosed .jxTreeImage{background-position:left -140px;}.jxTreeLeaf .jxTreeIcon,.jxTreeLeafLast .jxTreeIcon{background-position:left 0;}.jxTreeLeaf .jxTreeImage{background-position:left -60px;}
 .jxTreeLeafLast .jxTreeImage{background-position:left -120px;}a.jxTreeItem,img.jxTreeImage,img.jxTreeIcon,span.jxTreeLabel,.jxTreeItemContainer input{vertical-align:middle;}img.jxTreeImage.jxBusy{background-image:url(images/spinner_16.gif);background-position:left top;}.jxFileUploadPanel{padding:5px;}.jxUploadQueue li{display:block;position:relative;overflow:hidden;padding:2px;}.jxUploadQueue div span{display:block;}.jxUploadQueue li span.jxUploadFileName{font-size:12px;line-height:16px;padding-left:2px;}.jxUploadQueue li span.jxUploadFileDelete,.jxUploadQueue li span.jxUploadFileProgress,.jxUploadQueue li span.jxUploadFileComplete,.jxUploadQueue li span.jxUploadFileError{position:absolute;top:2px;right:2px;width:16px;height:16px;background-repeat:no-repeat;cursor:pointer;}.jxUploadQueue li span.jxUploadFileDelete{background-image:url('images/icons.png');background-position:0 -128px;}.jxUploadQueue li span.jxUploadFileProgress{background-image:url('images/spinner_16.gif');ba
 ckground-position:top left;}.jxUploadQueue li span.jxUploadFileComplete{background-image:url('images/icons.png');background-position:0 -48px;}.jxUploadQueue li span.jxUploadFileError{background-image:url('images/icons.png');background-position:0 -32px;}.jxUploadFileErrorTip{padding:4px 4px 4px 20px;border:2px solid #ddd;background:url("images/icons.png") no-repeat 0 -32px;color:black;width:100px;}
\ No newline at end of file

Added: trunk/lib/jxLib/themes/crispin/jxtheme.uncompressed.css
===================================================================
--- trunk/lib/jxLib/themes/crispin/jxtheme.uncompressed.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/crispin/jxtheme.uncompressed.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,3658 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.11.0
+*/
+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}
+table{border-collapse:collapse;border-spacing:0;}
+fieldset,img{border:0;}
+address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}
+ol,ul {list-style:none;}
+caption,th {text-align:left;}
+h1,h2,h3,h4,h5,h6{font-size:100%;}
+q:before,q:after{content:'';}/**
+ * @project         Jx
+ * @revision        $Id: button.css 593 2009-11-09 20:29:54Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* BUTTON STYLES */
+/* ============= */
+/* jxButtons consist of an A, containing a SPAN, which contains an image.
+   Buttons can use the sliding door technique with background images to horizontally
+   accomodate icons with labels. */
+
+.jxButtonContainer {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+  /* float: left; */
+
+  margin: 0px;
+  padding: 2px;
+  border: none;
+}
+
+/* normal button */
+.jxButton {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+  /* float: left; */
+
+  /* Using background images, the A contains the left side of the background */
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 0px 0px 0px 4px; /* makes room for the left of the button bg */
+  border: none;
+  background-image: url(images/button.png);
+  background-position: left -24px;
+  background-repeat: no-repeat;
+  text-decoration: none;
+  outline: none;
+}
+
+/* normal button */
+a.jxButton {
+  /* Base setup */
+  cursor: pointer;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+ul.jxToolbar .jxButton {
+  background-position: left top;
+}
+
+span.jxButtonContent {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  /* float: left; */
+  font-size: 0px;
+  line-height: 0px;
+
+  /* Using background images, the SPAN contains the right side of the background */
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 4px 4px 4px 0px; /* makes room for the right of the button bg */
+  border: none;
+  background-image: url(images/button.png);
+  background-position: right -24px;
+  background-repeat: no-repeat;
+}
+
+ul.jxToolbar span.jxButtonContent {
+  background-position: right top;
+}
+
+/* active button */
+ul.jxToolbar .jxButtonActive,
+.jxButtonActive {
+  background-position: left -72px;
+}
+
+ul.jxToolbar .jxButtonActive span.jxButtonContent,
+.jxButtonActive span.jxButtonContent {
+  background-position: right -72px;
+}
+
+/* focus button */
+ul.jxToolbar .jxButton:focus,
+.jxButton:focus {
+  background-position: left -96px;
+}
+
+ul.jxToolbar .jxButton:focus span.jxButtonContent,
+.jxButton:focus span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:focus,
+.jxButtonActive:focus {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:focus span.jxButtonContent,
+.jxButtonActive:focus span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* hover normal and active button */
+ul.jxToolbar .jxButton:hover,
+ul.jxToolbar .jxButtonActive:hover,
+.jxButton:hover,
+.jxButtonActive:hover {
+  background-position: left -48px;
+}
+
+ul.jxToolbar .jxButton:hover span.jxButtonContent,
+ul.jxToolbar .jxButtonActive:hover span.jxButtonContent,
+.jxButton:hover span.jxButtonContent,
+.jxButtonActive:hover span.jxButtonContent {
+  background-position: right -48px;
+}
+
+/* clicking normal and focused button */
+ul.jxToolbar .jxButtonPressed,
+ul.jxToolbar .jxButtonPressed:focus,
+.jxButtonPressed,
+.jxButtonPressed:focus {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed span.jxButtonContent,
+ul.jxToolbar .jxButtonPressed:focus span.jxButtonContent,
+.jxButtonPressed span.jxButtonContent,
+.jxButtonPressed:focus span.jxButtonContent {
+  background-position: right -120px;
+}
+
+/* disabled buttons */
+.jxDisabled .jxButton,
+.jxDisabled span.jxButtonContent span {
+ cursor: default; 
+}
+
+/* hover, focus and pressing disabled button */
+ul.jxToolbar .jxDisabled .jxButton:focus,
+ul.jxToolbar .jxDisabled .jxButton:active,
+ul.jxToolbar .jxDisabled .jxButton:hover,
+ul.jxToolbar .jxDisabled .jxButtonPressed {
+  background-position: left top;
+}
+
+.jxDisabled .jxButton:focus,
+.jxDisabled .jxButton:active,
+.jxDisabled .jxButton:hover,
+.jxDisabled .jxButtonPressed {
+  background-position: left -24px;
+}
+
+ul.jxToolbar .jxDisabled .jxButton:focus span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButton:active span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButton:hover span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonPressed span.jxButtonContent {
+  background-position: right top;
+}
+  
+.jxDisabled .jxButton:focus span.jxButtonContent,
+.jxDisabled .jxButton:active span.jxButtonContent,
+.jxDisabled .jxButton:hover span.jxButtonContent,
+.jxDisabled .jxButtonPressed span.jxButtonContent {
+  background-position: right -24px;
+}
+
+/* hover and focus disabled active button */
+ul.jxToolbar .jxDisabled .jxButtonActive:focus,
+ul.jxToolbar .jxDisabled .jxButtonActive:active,
+ul.jxToolbar .jxDisabled .jxButtonActive:hover,
+.jxDisabled .jxButtonActive:focus,
+.jxDisabled .jxButtonActive:active,
+.jxDisabled .jxButtonActive:hover {
+  background-position: left -72px;
+}
+
+ul.jxToolbar .jxDisabled .jxButtonActive:focus span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonActive:active span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonActive:hover span.jxButtonContent,
+.jxDisabled .jxButtonActive:focus span.jxButtonContent,
+.jxDisabled .jxButtonActive:active span.jxButtonContent,
+.jxDisabled .jxButtonActive:hover span.jxButtonContent {
+  background-position: right -72px;
+}
+
+img.jxButtonIcon {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  vertical-align: middle;
+  /* float: left; */
+
+  width: 16px;
+  height: 16px;
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+span.jxButtonContent span {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  vertical-align: middle;
+  /* float: left; */
+  cursor: pointer;
+
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  line-height: 16px;
+  height: 16px;
+  white-space: nowrap;
+}
+
+span.jxButtonContent span.jxButtonLabel {
+  margin: 0px;
+  padding: 0 4px 0 4px;
+  color: #000;
+  font-size: 11px;
+}
+
+/* ========================== */
+/* JX BUTTON EXTENSION STYLES */
+/* ========================== */
+
+.jxDiscloser span.jxButtonContent {
+  padding-right: 0px;
+}
+
+.jxDiscloser span.jxButtonContent span {
+  padding-right: 16px;
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+a.jxButtonDisclose {
+  position: absolute;
+  display: -moz-inline-box;
+  display: inline-block;
+  padding: 4px 0px;
+  font-size: 0px;
+  line-height: 0px;
+  right: 2px;
+  top: 2px;
+  background-image: url(images/button_multi_disclose.png);
+  background-position: right 0px;
+  background-repeat: no-repeat;
+  outline: none;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+a.jxButtonDisclose img {
+  width: 16px;
+  height: 16px;
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+a.jxButtonDisclose:focus,
+a.jxButtonDisclose:active {
+  background-position: right -96px;
+}
+
+a.jxButtonDisclose:hover {
+  background-position: right -48px;
+}
+
+a.jxButtonDisclosePressed {
+  background-position: right -120px;
+}
+
+.jxDisabled a.jxButtonDisclose,
+.jxDisabled a.jxButtonDisclose:focus, 
+.jxDisabled a.jxButtonDisclose:active, 
+.jxDisabled a.jxButtonDisclose:hover, 
+.jxDisabled a.jxButtonDisclosePressed {
+  cursor: default; 
+  background-position: right 0px;
+}
+
+/* note, jxButtonHover is set by Multi button JS */
+ul.jxToolbar .jxButtonHover {
+  background-position: left -24px !important;
+}
+
+ul.jxToolbar .jxButtonHover span.jxButtonContent {
+  background-position: right -24px !important;
+}
+
+
+/* Jx Flyout Styles */
+
+.jxFlyout .jxChrome {
+  background-image: url(images/flyout_chrome.png);
+  padding: 5px 5px 7px 6px;
+}
+
+.jxFlyout {
+  /* Base setup */
+  position: absolute;
+  display: block;
+  z-index: 100;
+
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxFlyoutContent {
+  position: relative;
+  display: block;
+  overflow: auto;
+  margin: 6px 6px 8px 7px;
+  background-color: #fff;
+  border: 1px solid #999;
+}
+
+/* Jx Combo and Multi Button Styles */
+
+.jxButtonMulti,
+.jxButtonMulti span.jxButtonContent {
+  background-image: url(images/button_multi.png);
+}
+
+.jxButtonEditCombo,
+.jxButtonEditCombo span.jxButtonContent {
+  background-image: url(images/button_combo.png);
+}
+
+/*a.jxButtonEditCombo {
+  user-select: text;
+  -moz-user-select: text;
+  -khtml-user-select: text;
+}*/
+
+.jxButtonMulti span.jxButtonContent span {
+  padding-right: 21px;
+}
+
+.jxButtonEditCombo span.jxButtonContent span {
+  font-size: 0px;
+}
+
+.jxButtonComboDefault span.jxButtonContent span,
+.jxButtonComboDefault input {
+  font-style: italic;
+  color: #999;
+}
+
+.jxButtonEditCombo input {
+  float: left;
+  line-height: 16px;
+  height: 16px;
+  padding: 0px 4px;
+  margin: 0px;
+  border: none;
+  font-size: 11px;
+  font-family: Arial, Helvetica, sans-serif;
+  background-color: transparent;
+}/**
+ * @project         Jx
+ * @revision        $Id: chrome.css 454 2009-06-03 14:50:22Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* CHROME STYLES */
+/* ============= */
+/* Chrome uses four absolutely positioned DIVs containing an image for each of 
+   four quadrants.  The chrome image is used as a sprite map. */
+
+.jxChrome {
+  /* Base setup */
+  position:absolute;
+  display: block;
+  font-size: 0px;
+  line-height: 0px;
+  z-index: -1;
+  width: 100%;
+  height: 100%;
+  top: 0px;
+  left: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxChromeDrag {
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+}
+
+.jxChromeTL { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 0%; 
+  top: 0%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeTR { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 50%; 
+  top: 0%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeBL { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 0%; 
+  top: 50%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeBR { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 50%; 
+  top: 50%;  
+  width: 50%; 
+  height: 50%; 
+}
+
+
+.jxChromeTL img { 
+  position: absolute; 
+  top: 0px; 
+  left: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeTR img { 
+  position: absolute; 
+  top: 0px; 
+  right: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeBL img { 
+  position: absolute; 
+  bottom: 0px; 
+  left: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeBR img { 
+  position: absolute; 
+  bottom: 0px; 
+  right: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: color.css 423 2009-05-12 12:37:56Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =================== */
+/* COLOR PICKER STYLES */
+/* =================== */
+
+/*.jxColorPicker {
+    position: absolute;
+    display: none;
+    top: 100%;
+    width: 212px;
+    left: 0px;
+    border: 1px solid #000;
+    padding: 2px;
+    background-color: #eee;
+}*/
+
+.jxColorBar {
+    position: relative;
+    overflow: hidden;
+}
+
+table.jxColorGrid {
+    position: relative;
+    border-collapse: collapse;
+    empty-cells: show;
+    clear:both;
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid tr {
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid td {
+    border: 1px solid #000;
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid td.emptyCell {
+    border: 0px solid #000;
+}
+
+.jxColorGrid td.emptyCell span {
+    display: block;
+    width: 7px;
+    height: 7px;
+    line-height: 0px;
+    font-size: 0px;
+    border: 0px solid #000;
+    padding: 1px;
+    margin: 0px;
+}
+
+.jxColorGrid a.colorSwatch {
+    display: block;
+    width: 7px;
+    height: 7px;
+    line-height: 0px;
+    font-size: 0px;
+    border: 0px solid #000;
+    margin: 0px;
+    padding: 1px;
+}
+
+.jxColorGrid a.borderWhite:hover {
+    border: 1px solid #fff;
+    padding: 0px;
+}
+
+.jxColorGrid a.borderBlack:hover {
+    border: 1px solid #000;
+    padding: 0px;
+}
+
+input.jxHexInput {
+    width: 55px;
+    vertical-align: middle;
+}
+
+input.jxAlphaInput {
+    width: 30px;
+    vertical-align: middle;
+}
+
+div.jxColorPreview {
+    float: left;
+    position: relative;
+    width: 20px;
+    height: 20px;
+    border: 1px solid #000;
+    margin: 2px;
+    vertical-align: middle;
+    background-image: url('images/grid.png');
+    overflow: hidden;
+}
+
+.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch {
+    display: block;
+    float: left;
+    width: 14px;
+    height: 14px;
+    border: 1px solid #000;
+    background-image: url('images/grid.png');
+    background-position: 0px 0px;
+    background-repeat: repeat;
+    padding-right: 0px !important;
+}
+
+.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch span {
+    display: block;
+    width: 14px;
+    height: 14px;
+    position: absolute;
+    padding-right: 0px;
+    background: none;
+}
+
+div.jxColorPreview img {
+    position: absolute;
+    z-index: 0;
+}
+
+div.jxColorPreview div {
+    width: 20px;
+    height: 10px;
+    position: absolute;
+    display: block;
+    left: 0px;
+    z-index: 1;
+    font-size: 10px;
+    line-height: 0px;
+}
+
+div.jxColorPreview div.jxColorSelected {
+    top: 0px;
+}
+
+div.jxColorPreview div.jxColorHover {
+    bottom: 0px;
+}
+
+label.jxColorLabel,
+label.jxAlphaLabel {
+    width: auto;
+    font-family: Arial, sans-serif;
+    font-size: 11px;
+    line-height: 24px;
+    padding: 2px;
+    vertical-align: middle;
+}
+
+a.jxColorClose {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    width: 16px;
+    height: 16px;
+}
+
+a.jxColorClose img {
+    width: 16px;
+    height: 16px;
+}/**
+ * @project         Jx
+ * @revision        $Id: common.css 736 2010-03-05 16:04:56Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* COMMON STYLES */
+/* ============= */
+
+.jxClearer {
+  display: block;
+  position: relative;
+  float: none;
+  clear: both;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+  margin: 0;
+  padding: 0;
+}
+
+.jxDisabled {
+  opacity: 0.4;
+  -ms-filter: "Alpha(opacity=40)";
+}
+
+.jxDisabled * {
+  -ms-filter: "Alpha(opacity=40)";
+}
+
+/* ============= */
+/*  MASK STYLES  */
+/* ============= */
+
+.jxMask {
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+  background-color: #fff;
+}
+
+.jxModalMask {
+  background-color: #000;
+  opacity: 0.2;
+  -ms-filter: "Alpha(opacity=20)";
+}
+
+.jxEventMask {
+  background-image: url(images/a_pixel.png);
+}
+
+
+.jxSpinner {
+  position: absolute;
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+  z-index: 999;
+  background: #fff;
+}
+
+/* .jxSpinnerContent { } */
+
+.jxSpinnerMessage {
+  text-align: center;
+  font-weight: bold;
+}
+
+.jxSpinnerSmall .jxSpinnerMessage {
+  margin: 0px;
+  padding: 0px;
+  font-size: 11px;
+  line-height: 24px;
+}
+
+.jxSpinnerLarge .jxSpinnerImage {
+  background: url(images/spinner_24.gif) no-repeat;
+  width: 24px;
+  height: 24px;
+  margin: 0 auto;
+}
+
+.jxSpinnerSmall .jxSpinnerImage {
+  background: url(images/spinner_16.gif) no-repeat;
+  width: 16px;
+  height: 16px;
+  margin-right: 4px;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+/*iframe.jxIframeShim { }*/
+/**
+ * Confirm and Prompt dialog classes
+ */
+.jxConfirmQuestion, .jxPrompt {
+    text-align: center;
+    padding: 10px;
+    margin-top: 10px;
+}/**
+ * @project         Jx
+ * @revision        $Id: dialog.css 732 2010-03-05 14:38:36Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* DIALOG STYLES */
+/* ============= */
+
+.jxDialog .jxChrome {
+  background-image: url(images/dialog_chrome.png);
+}
+
+.jxDialog {
+  /* Base setup */
+  display: block;
+  z-index: 1000;
+  overflow: hidden;
+
+  /* initial state is hidden */
+  visibility: hidden;
+}
+
+.jxDialogContentContainer {
+  z-index: 1;
+  margin: 0px 11px 13px 12px;
+  border: 1px solid #b7b7b7;
+  background-color: #f0f0f0;
+}
+
+.jxDialogContent {
+  /* Base setup */
+  display: block;
+  position:relative;
+  overflow: auto;
+
+  padding: 0px;
+  z-index: 1;
+}
+
+.jxDialogTitle {
+   /* Base setup */
+  display: block;
+  position: relative;
+
+  /* this makes the dialog draggable by the title bar in IE
+   * Without it, only the label is draggable
+   */
+  background-image: url(images/a_pixel.png);
+
+  text-align: center;
+  height: 24px;
+  line-height: 24px;
+  z-index: 1;
+
+  margin: 6px 6px 0px 7px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxDialogMin .jxDialogTitle {
+  margin-bottom: 8px;
+}
+
+.jxDialogMoveable,
+.jxDialogMoveable .jxDialogLabel {
+  cursor: move;
+}
+
+.jxDialogIcon {
+  position: absolute;
+  left: 2px;
+  top: 3px;
+  width: 16px;
+  height: 16px;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+.jxDialogLabel {
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  font-weight: bold;
+  /* line-height vertically aligns the label in the containing div. */
+  line-height:21px;
+  color: #000;
+  white-space: nowrap;
+  cursor: default;
+}
+
+.jxDialogResize {
+  /* Base setup */
+  position: absolute;
+
+  bottom: 7px;
+  right: 6px;
+  width: 16px;
+  height: 16px;
+  z-index: 2;
+  border: 0px;
+  cursor: se-resize;
+  background-image: url(images/dialog_resize.png);
+}
+
+.jxDialogControls {
+  position: absolute;
+  top: 3px;
+  right: 2px;
+  height: 16px;
+  width: 80px;
+}
+
+.jxDialogControls img {
+  background-image: url('images/panel_controls.png');
+  background-repeat: no-repeat;
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+}
+
+.jxDialogClose img {
+  background-position: 0px -32px;
+}
+
+.jxDialogMenu img {
+  background-position: 0px -48px;
+}
+
+.jxDialogHelp img {
+  background-position: 0px -64px;
+}
+
+.jxDialogCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxDialogMin .jxDialogCollapse img {
+  background-position: 0px 0px;
+}
+
+.jxDialogMax .jxDialogCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxDialogMaximize img {
+  background-position: 0px -80px;
+}
+
+.jxDialogMaximized .jxDialogMaximize img {
+  background-position: 0px -96px;
+}
+
+.jxDialogLoading img {
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+  visibility:hidden;
+  position: absolute;
+  top: 1px;
+  left: 2px;
+}
+
+
+/* ========================= */
+/* JX BUTTON STYLES OVERIDES */
+/* ========================= */
+
+.jxDialogControls .jxButtonContainer,
+.jxDialogControls .jxButton,
+.jxDialogControls .jxButton:hover,
+.jxDialogControls .jxButton:active ,
+.jxDialogControls .jxButtonActive,
+.jxDialogControls .jxButtonActive:hover,
+.jxDialogControls .jxButtonActive:active,
+.jxDialogControls .jxDisabled .jxButton,
+.jxDialogControls .jxDisabled .jxButton:hover,
+.jxDialogControls .jxDisabled .jxButton:active {
+  padding: 0px;
+  margin: 0px;
+  border: none;
+  background-color: transparent;
+  background-image: none;
+}
+
+
+/* ========================== */
+/* JX TOOLBAR STYLES OVERIDES */
+/* ========================== */
+
+/* Multiple toolbars can be housed in  the toolbar container the container will expand vertically to accomodate wrapped toolbars */
+
+.jxDialogControls .jxBarContainer {
+  /* Base setup */
+  position: absolute;
+  right: 0px;
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  height: 16px;
+}
+
+.jxDialogControls .jxBarScroller {
+  left: auto;
+  right: 0px;
+}
+
+.jxDialogControls ul.jxToolbar {
+  float: right;
+}
+
+.jxDialogControls ul.jxToolbar,
+.jxDialogControls li.jxToolItem {
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+}/**
+ * File Input classes
+ */
+div.jxFileInputs {
+    position: relative;
+}
+
+div.jxFileFake {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    z-index: 1;
+}
+
+div.jxFileFake span {
+    float: left;
+    display: block;
+}
+
+div.jxFileFake .jxInputContainer {
+    width: 150px;
+}
+
+div.jxFileFake .jxInputText {
+    width: 135px;
+} 
+
+div.jxFileFake .jxButtonContainer {
+    margin-top: 2px;
+    float: left;
+}
+
+.jxInputFile {
+    position: relative;
+    text-align: right;
+    -moz-opacity: 0;
+    filter: alpha(opacity:0);
+    opacity: 0;
+    z-index: 2;
+    margin-top: -5px;
+    height: 35px;
+}/**
+ * @project         Jx
+ * @revision        $Id: form.css 827 2010-04-01 14:22:49Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* FORM STYLES */
+/* =========== */
+/* JxForm classes are a set of styles that can be used for laying out forms 
+ * There are three different types of layouts: Inline, Inlineblock and Block.
+ * Each can be used to layout an entire form, a fieldset or an individual input.
+ */
+
+/* debuggiong styles */
+/*
+.jxForm           { background-color: yellow; }
+.jxFieldset       { background-color: khaki; }
+.jxInputGroup     { background-color: tan; }
+.jxFieldsetLegend { background-color: orange; }
+.jxInputContainer { background-color: pink; }
+.jxInputLabel     { background-color: plum; }
+.jxInputTag       { background-color: lime; }
+*/
+
+ /* Base and Typography Styles */
+ 
+.jxForm {
+  display: block;
+  position: relative;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxFieldset {
+  display: block;
+  position: relative;
+  border: 1px solid #ccc;
+  margin: 10px;
+  padding: 5px;
+}
+
+.jxFieldsetLegend,
+.jxFieldset legend {
+  position: relative;
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  line-height: 26px;
+  color: #000;
+}
+
+.jxInputContainer {
+  display: block;
+  position: relative;
+  padding: 0px;
+  margin: 2px;
+  border: none;
+}
+
+.jxInputLabel,
+.jxInputTag {
+  display: -moz-inline-box;
+  display: inline-block;
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 26px;
+  color: #000;
+}
+
+.jxInputLabel {
+  vertical-align: top;
+}
+
+.jxInputTag {
+  vertical-align: bottom;
+}
+
+.jxInputWrapper {
+  display: inline-block;
+  white-space: normal;
+  position: relative;
+}
+
+.jxInputText,
+.jxInputPassword,
+.jxInputTextarea,
+.jxInputCombo,
+.jxInputColor {
+  margin: 0px 4px;
+  padding: 4px;
+  border: 1px solid #bbb;
+  /* overall width is 250px, margins+padding+border is 18px */
+  width: 232px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputCombo,
+.jxInputColor {
+  padding: 4px 20px 4px 20px;
+  /* overall width is 250px, margins+padding+border is 50px */
+  width: 200px;
+}
+
+.jxInputIconHidden .jxInputCombo {
+  /* overall width is 250px, margins+padding+border is 34px */
+  padding-left: 4px;
+  width: 216px;
+}
+
+.jxInputIcon {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  left: 0px;
+  top: 0px;
+  margin: 4px 4px 4px 8px;
+}
+
+.jxInputContainerColor .jxInputIcon {
+  border: 1px solid #bbb;
+  width: 15px;
+  height: 15px;
+}
+
+.jxInputIconHidden .jxInputIcon {
+  display: none;
+}
+
+.jxInputRevealer {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  right: 0px;
+  top: 0px;
+  margin: 4px 8px 4px 4px;
+  font-size: 0px;
+  line-height: 0px;
+}
+
+img.jxInputRevealerIcon {
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+.jxInputRevealer .jxButtonContainer,
+.jxInputRevealer .jxButton {
+  padding: 0px;
+  margin: 0px;
+  border: 0px;
+  background-color: transparent;
+  background-image: none;
+}
+
+.jxInputSelect {
+  margin: 0px 4px;
+  padding: 3px 4px 3px 1px;
+  border: 1px solid #bbb;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputRadio,
+.jxInputCheck {
+  margin: 5px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputText:focus,
+.jxInputPassword:focus,
+.jxInputTextarea:focus,
+.jxInputSelect:focus,
+.jxInputCombo:focus,
+.jxInputColor:focus {
+  border: 1px solid #000;
+}
+
+.jxInputContainer .jxButtonContainer {
+  padding: 0px;
+  margin: 0px 4px;
+}
+
+/* Input Group */
+
+.jxInputGroup {
+  border: none;
+  padding: 0px;
+  margin: 2px;
+}
+
+.jxInputGroup legend {
+  font-size: 0px;
+  line-height: 0px;
+  padding: 0px;
+  margin: 0px;
+  border: none;
+}
+
+.jxInputGroup .jxFieldsetLegend {
+  font-size: 12px;
+}
+
+.jxInputGroup .jxInputLabel {
+  width: auto;
+}
+
+/* Field Validation */
+
+.jxFieldError .jxInputText,
+.jxFieldError .jxInputPassword,
+.jxFieldError .jxInputTextarea,
+.jxFieldError .jxInputSelect,
+.jxFieldError .jxInputCombo,
+.jxFieldError .jxInputColor {
+  background-color: #FBE3E4; 
+  color: #8a1f11; 
+  border-color: #FBC2C4;
+}
+
+.jxFieldSuccess .jxInputText,
+.jxFieldSuccess .jxInputPassword,
+.jxFieldSuccess .jxInputTextarea,
+.jxFieldSuccess .jxInputSelect,
+.jxFieldSuccess .jxInputCombo,
+.jxFieldSuccess .jxInputColor {
+  background-color: #E6EFC2; 
+  color: #264409; 
+  border-color: #C6D880;
+}
+
+.jxFieldError .jxInputText:focus,
+.jxFieldError .jxInputPassword:focus,
+.jxFieldError .jxInputTextarea:focus,
+.jxFieldError .jxInputSelect:focus,
+.jxFieldError .jxInputCombo:focus,
+.jxFieldError .jxInputColor:focus {
+  border-color: #8a1f11;
+}
+
+.jxFieldSuccess .jxInputText:focus,
+.jxFieldSuccess .jxInputPassword:focus,
+.jxFieldSuccess .jxInputTextarea:focus,
+.jxFieldSuccess .jxInputSelect:focus,
+.jxFieldSuccess .jxInputCombo:focus,
+.jxFieldSuccess .jxInputColor:focus {
+  border-color: #264409;
+}
+
+.jxFieldError .jxInputLabel,
+.jxFieldError .jxInputTag {
+  color: #8a1f11; 
+}
+
+.jxFieldSuccess .jxInputLabel,
+.jxFieldSuccess .jxInputTag {
+  color: #264409; 
+}
+
+
+/* For Reference
+   Success, notice and error boxes from Blueprint */
+
+/* 
+.error      { background: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; }
+.notice     { background: #FFF6BF; color: #514721; border-color: #FFD324; }
+.success    { background: #E6EFC2; color: #264409; border-color: #C6D880; }
+.error a    { color: #8a1f11; }
+.notice a   { color: #514721; }
+.success a  { color: #264409; }
+*/
+
+
+
+/* INLINE FORM 
+ * Sets up form elements to work as inline objects like they do by default
+ * These styles rely on increasing specificity to provide overrides */
+
+/* Inline Input Container */
+.jxFormInline .jxInputContainer,
+form .jxFormInline .jxInputContainer,
+form .jxFieldset span.jxFormInline,
+form.jxForm span.jxFormInline {
+  display: inline;
+}
+
+/* Inline Label */
+.jxFormInline .jxInputLabel,
+form .jxFormInline .jxInputLabel,
+form span.jxFormInline .jxInputLabel {
+  display: inline;
+  width: auto;
+}
+
+/* Inline Tag */
+.jxFormInline .jxInputTag,
+form .jxFormInline .jxInputTag,
+form span.jxFormInline .jxInputTag {
+  display: inline;
+}
+
+/* Inline Input Group */
+.jxFormInline .jxInputGroup,
+form .jxFormInline .jxInputGroup {
+  padding-left: 0px;
+}
+
+.jxFormInline .jxInputGroup .jxFieldsetLegend,
+form .jxFormInline .jxInputGroup .jxFieldsetLegend {
+  position: relative;
+  left: auto;
+}
+
+.jxFormInline .jxInputGroup .jxInputLabel,
+form .jxFormInline .jxInputGroup .jxInputLabel {
+  display: inline;
+  width: auto;
+}
+
+
+/* BLOCK FORM 
+ *  Sets up form elements to work as block objects so they can appear stacked */
+
+/* Block Input Container */
+.jxFormBlock .jxInputContainer,
+form .jxFormBlock .jxInputContainer,
+form .jxFieldset span.jxFormBlock,
+form.jxform span.jxFormBlock {
+  display: block;
+}
+ 
+/* Block Label */
+.jxFormBlock .jxInputLabel,
+form .jxFormBlock .jxInputLabel,
+form span.jxFormBlock .jxInputLabel {
+  display: block;
+  width: auto;
+  margin-left: 4px;
+}
+
+/* Checks and Radios Label
+ * Most inputs are preceeded by their labels, but in the case of radio buttons
+ * and checkboxes, the inputs are followed by their labels */
+.jxFormBlock .jxInputContainerCheck .jxInputLabel,
+.jxFormBlock .jxInputContainerRadio .jxInputLabel,
+form .jxFormBlock .jxInputContainerCheck .jxInputLabel,
+form .jxFormBlock .jxInputContainerRadio .jxInputLabel,
+form span.jxFormBlock .jxInputContainerCheck .jxInputLabel,
+form span.jxFormBlock .jxInputContainerRadio .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+}
+
+/* Block Input Group */
+.jxFormBlock .jxInputGroup,
+form .jxFormBlock .jxInputGroup {
+  padding-left: 0px;
+}
+
+.jxFormBlock .jxInputGroup .jxFieldsetLegend,
+form .jxFormBlock .jxInputGroup .jxFieldsetLegend {
+  position: relative;
+  left: auto;
+}
+
+.jxFormBlock .jxInputGroup .jxInputLabel,
+form .jxFormBlock .jxInputGroup .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: auto;
+}
+
+
+/* INLINE-BLOCK FORM 
+ * Sets up form elements to work as inline-block objects so labels can have set 
+ * widths to simulate a 2 column display for label / input pairs. */
+
+/* Inline-Block Input Container */
+.jxFormInlineblock .jxInputContainer,
+form .jxFormInlineblock .jxInputContainer,
+form .jxFieldset span.jxFormInlineblock,
+form.jxForm span.jxFormInlineblock {
+  display: block;
+  white-space: nowrap;
+}
+
+/* Inline-Block Label */
+.jxFormInlineblock .jxInputLabel,
+form .jxFormInlineblock .jxInputLabel,
+form span.jxFormInlineblock .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: 200px;
+}
+
+/* Inline-Block Input Group */
+.jxFormInlineblock .jxInputGroup,
+form .jxFormInlineblock .jxInputGroup {
+  padding-left: 200px;
+}
+
+.jxFormInlineblock .jxInputGroup .jxFieldsetLegend,
+form .jxFormInlineblock .jxInputGroup .jxFieldsetLegend {
+  position: absolute;
+  left: -200px; /* for ie? */
+  width: 200px;
+}
+
+.jxFormInlineblock .jxInputGroup .jxInputLabel,
+form .jxFormInlineblock .jxInputGroup .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: auto;
+}
+
+/** Jx.Grid Overrides **/
+
+.jxGridCellContent .jxInputContainer,
+.jxGridCellContent .jxInputRadio,
+.jxGridCellContent .jxInputCheck {
+  display: inline;
+  position: relative;
+  border: none;
+  margin: 0px;
+  padding: 0px;
+  font-size: 0px;
+  line-height: 0px;
+  color: #000;
+}/**
+ * @project         Jx
+ * @revision        $Id: grid.css 796 2010-03-26 19:56:43Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* GRID STYLES */
+/* ============= */
+
+.jxGridContainer {
+    position: absolute;
+    top: 0;
+    left: 0;
+    border-left: 0px solid #d8d8d8;
+    border-top: 0px solid #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    overflow: hidden;
+}
+
+.jxGridTable {
+    width: 100%;
+    position: relative;
+    table-layout: fixed;
+    border-collapse: collapse;
+    border-style: none;
+
+    cursor: default;
+}
+
+.jxGridTableBody {
+    position: relative;
+    table-layout: fixed;
+    border-collapse: collapse;
+    border-style: none;
+   /* width: 100%;*/
+    cursor: default;
+}
+
+.jxGridCell {
+    border-top: 0px solid #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid #d8d8d8;
+    overflow: hidden;
+}
+
+.jxGridCellContent {
+    position: relative;
+    display: -moz-inline-box;
+    display: inline-block;
+    overflow: hidden;
+    padding: 0px 3px;
+    overflow: hidden;
+    vertical-align: middle;
+    
+    font-family: Arial, Verdana, sans-serif;
+    font-size: 11px;
+    font-weight: normal;
+    line-height: 16px;
+    
+    /* can change this to normal */
+    white-space: nowrap;
+    cursor: cell;
+    /* only applies in IE and Safari right now */
+    text-overflow: ellipsis;
+}
+
+.jxGridColHead .jxGridCellContent {
+    padding: 0px 3px;
+    text-align: center;
+    font-weight: bold;
+    color: #333;
+    height: 100%;
+}
+
+.jxGridRowHead .jxGridCellContent {
+  text-align: center;
+  font-weight: bold;
+  color: #333;
+}
+/* Normal Styles */
+
+.jxGridColHead {
+    height: 100%;
+  
+    border-top: 0px solid  #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid  #d8d8d8;
+    background-color: #f2f2f2;
+    background-image: url('images/table_col.png');
+    background-position: 0px 0px;
+    background-repeat: repeat-x;
+
+    text-align: center;
+    cursor: default;
+    padding: 0px;
+    white-space: nowrap;
+    
+    overflow: hidden;
+}
+
+.jxGridRowHead {
+    border-top: 0px solid  #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid  #d8d8d8;
+    background-color:  #f2f2f2;
+    background-image: url('images/table_row.png');
+    background-position: 0px 0px;
+    background-repeat: repeat-y;
+
+    text-align: center;
+    cursor: default;
+    overflow: hidden;
+    white-space: nowrap;
+}
+
+/* Alternating Row Styles */
+
+.jxGridRowAll {
+    background-color: #fff;
+}
+
+.jxGridRowOdd {}
+.jxGridRowEven {}
+.jxGridRowOdd td {}
+.jxGridRowEven td {}
+
+/* Selected Styles */
+
+.jxGridColumnHeaderSelected {
+    background-color: #e1e1e1;
+    background-position: 0px -200px;
+}
+
+.jxGridRowHeaderSelected {
+    background-color: #e1e1e1;
+    background-position: -400px 0px;
+}
+
+.jxGridColumnSelected {
+    background-color: #f7f7f7;
+}
+
+.jxGridRowSelected td,
+.jxGridRowSelected th {
+    background-color: #f7f7f7;
+}
+
+td.jxGridCellSelected,
+th.jxGridCellSelected {
+    background-color: #ebebeb;
+}
+
+/* Prelight Styles */
+
+.jxGridColumnHeaderPrelight {
+    background-color: #cee5ff;
+    background-position: 0px -300px;
+}
+
+.jxGridRowHeaderPrelight {
+    background-color: #cee5ff;
+    background-position: -600px 0px;
+}
+
+.jxGridColumnPrelight {
+    background-color: #e5f1ff;
+}
+
+.jxGridRowPrelight td,
+.jxGridRowPrelight th {
+    background-color: #e5f1ff;
+}
+
+td.jxGridCellPrelight,
+th.jxGridCellPrelight {
+  background-color: #cce3ff;
+}
+
+.jxGridHeader .jxColSortable img {
+    vertical-align: top;
+    width: 16px;
+    height: 16px;
+    background-image: url('images/emblems.png');
+    background-repeat: no-repeat;
+    background-position: right top;
+}
+
+.jxGridHeader .jxColSortable .jxGridCellContent {
+  margin-right: -16px; /* recenter the column heading text */
+}
+ 
+.jxGridHeader .jxGridColumnSortedAsc img {
+    background-position: right -162px;
+}
+ 
+.jxGridHeader .jxGridColumnSortedDesc img {
+    background-position: right -18px;
+}
+ 
+.jxGridColumnResize,
+.jxGridRowResize {
+    display: block;
+    position: absolute;
+    background-image: url(images/a_pixel.png);
+    z-index: 1;
+    
+}
+.jxGridColumnResize {
+    right: 0px;
+    top: 0px;
+    height: 100%;
+    width: 4px;
+    cursor: col-resize;
+}
+
+.jxColSortable .jxGridColumnResize {
+  right: 8px;
+}
+
+.jxGridRowResize {
+    left: 0px;
+    bottom: 0px;
+    height: 4px;
+    width: 100%;
+    cursor: row-resize;
+}
+
+/* Editor Styles */
+.jxGridEditorPopup {
+  min-width: 130px;
+  margin: 3px;
+  font-size: 10px;
+  box-shadow: 2px 2px 5px #888; /* i really like the boxshadow */
+  -moz-box-shadow : 2px 2px 5px #888;
+  -webkit-box-shadow : 2px 2px 5px #888;
+  /*
+  border-radius: 5px;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  */
+  background-color: #dddddd;
+  border: 1px solid #999999;
+  position: absolute;
+}
+
+.jxGridEditorPopupInnerWrapper {
+  position: relative;
+  height: 100%;
+  width: 100%;
+}.jxListView {
+  position:relative;
+  display: block;
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxListView .jxListItemContainer {
+    position: relative;
+    display: block;
+    outline: none;
+    overflow: hidden;
+    
+    border: none;
+    margin: 0px 1px;
+    padding: 0px;
+}
+
+.jxListItem {
+    position: relative;
+    display: block;
+    cursor: pointer;
+    outline: none;
+    overflow: hidden;
+
+    border: none;
+    margin: 0px 1px;
+    padding: 0px;
+    z-index: 0;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 11px;
+    color: #000;
+    text-decoration: none;
+    /* Line Height needs to be an even number so branches line up properly */
+    line-height: 20px;
+    height: 20px;
+}
+
+.jxListView .jxHover {
+    margin: 0px;
+    border-left: 1px solid #CDDFFD;
+    border-right: 1px solid #CDDFFD;
+    background-image: url(images/listitem.png);
+    background-repeat: repeat-x;
+    background-color: #CDE5FF;
+    background-position: left -24px;
+    
+}
+
+.jxListItem:focus {
+    margin: 0px;
+    border-left: 1px dotted #75ADFF;
+    border-right: 1px dotted #75ADFF;
+    background-image: url(images/listitem.png);
+    background-repeat: repeat-x;
+    background-position: left -72px;
+}
+
+.jxListView .jxPressed,
+.jxListView .jxSelected {
+  margin: 0px;
+  border-left: 1px solid #8AABFB;
+  border-right: 1px solid #8AABFB;
+  background-color: #CDE5FF;
+  background-image: url(images/listitem.png);
+  background-repeat: repeat-x;
+  background-position: left -48px;
+}
+/**
+ * @project         Jx
+ * @revision        $Id: menu.css 601 2009-11-10 18:44:35Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============== */
+/* JX MENU STYLES */
+/* ============== */
+
+.jxMenuContainer .jxChrome {
+  /* the background image gets used by Jx.Chrome to create a stretchable chrome */
+  background-image: url(images/flyout_chrome.png);
+  /* the padding reflects the amount of space to leave around the content area
+   * for the chrome, typically to leave space for a shadow
+   */
+  padding: 5px 5px 7px 6px;
+}
+
+.jxButtonMenu span.jxMenuItemSpan {
+  padding-right: 16px;
+}
+
+/* Jx Menus and Sub-menus are all built out of nested ULs
+   For this to work visually, the margins and padding need to be flattened
+   out, and the list marker needs to be hidden
+*/
+
+.jxMenuContainer {
+  position: absolute;
+  top: 0;
+  left: -10000px;
+  display: none;
+  z-index: 2000;
+  padding: 0px;
+}
+
+ul.jxMenu {
+  /* Base setup */
+  display: block;
+  position: relative;
+
+  list-style-type: none;
+  padding: 1px;
+  margin: 6px 6px 8px 7px;
+  background-color: #fff;
+  border: 1px solid #999;
+}
+
+li.jxMenuItemContainer {
+  /* This is needed for IE to make sure submenus don't open space in the parent menu */
+  /* Base setup */
+  display: block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+
+  margin: 0px;
+  padding: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+a.jxMenuItem {
+  /* Base setup */
+  display: block;
+  position: relative;
+  overflow: hidden;
+  text-decoration: none;
+  cursor: pointer;
+  outline: none;
+
+  border: 1px solid #fff;
+  background-image: url(images/menuitem.png);
+  background-repeat: no-repeat;
+  background-position: left top;
+
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  text-decoration: none;
+  margin: 0px;
+  padding: 0px;
+  color: #000;
+}
+
+a.jxMenuItemActive {
+  background-position: left -98px;
+}
+
+a.jxMenuItem:focus {
+  background-position: left -74px;
+}
+
+a.jxMenuItem:focus span.jxMenuItemContent {
+  border-right: 1px dotted #75ADFF;
+}
+
+a.jxMenuItemActive:focus {
+  background-position: left -170px;
+}
+
+a.jxMenuItem:hover {
+  background-color: #CDE5FF;
+  background-position: left -26px;
+}
+
+a.jxMenuItem:hover span.jxMenuItemContent {
+  border-right: 1px solid #C5E0FF;
+}
+
+a.jxMenuItemActive:hover {
+  background-position: left -122px;
+}
+
+a.jxMenuItemPressed,
+a.jxMenuItemPressed:hover {
+  background-color: #CDE5FF;
+  background-position: left -50px;
+}
+
+.jxDisabled a.jxMenuItem,
+.jxDisabled span.jxMenuItemContent span {
+  cursor: default;
+}
+
+.jxDisabled a.jxMenuItem:focus,
+.jxDisabled a.jxMenuItemPressed,
+.jxDisabled a.jxMenuItemPressed:hover,
+.jxDisabled a.jxMenuItem:hover {
+  background-color: #fff;
+  background-position: left top;
+  border-right: 1px solid #fff;
+}
+
+.jxDisabled a.jxMenuItem:hover span.jxMenuItemContent {
+  border-right: 1px solid #fff;
+}
+
+span.jxMenuItemContent {
+  /* If using background images, the SPAN contains the right side of the background */
+  /* use padding to make space between the icon and button edge */
+  /* padding-left: 0px;*/ /* butts up to the left of the button bg image */
+  /* Base setup */
+  display: block;
+  position: relative;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 0px;
+  line-height: 0px;
+  white-space: nowrap;
+  padding: 0px 20px 0px 0px; /* space for the arrow */
+  margin: 0px;
+  border-right: 1px solid #fff; /* forces IE to render properly */
+  /*overflow: hidden;*/
+}
+
+.jxButtonSubMenu span.jxMenuItemContent,
+.jxButtonSubMenu:hover span.jxMenuItemContent {
+  background-image: url(images/emblems.png);
+  background-position: right -30px;
+  background-repeat: no-repeat;
+}
+
+img.jxMenuItemIcon {
+  /* Base setup */
+  position: absolute;
+  top: 2px;
+  left: 2px;
+
+  width: 16px;
+  height: 16px;
+  background-position: left center;
+  background-repeat: no-repeat;
+}
+
+span.jxMenuItemContent span {
+  /* Base setup for empty labels */
+  display: block;
+  position: relative;
+  cursor: pointer;
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 2px 0px 2px 22px; /* space for the icon */
+  font-size: 16px;
+  line-height: 16px;
+  /*height: 20px;*/
+  color: #000;
+}
+
+span.jxMenuItemContent span.jxMenuItemLabel {
+  /* Base setup, overrides empty labels */
+  color: #000;
+  font-size: 11px;
+}
+
+.jxMenuItemToggle img.jxMenuItemIcon,
+.jxMenuItemToggleSet img.jxMenuItemIcon {
+  background-image: url(images/emblems.png);
+  background-position: 2px 0px;
+  background-repeat: no-repeat;
+}
+
+.jxMenuItemToggle a.jxMenuItemActive img.jxMenuItemIcon {
+  background-position: 2px -48px;
+}
+
+.jxMenuItemToggleSet a.jxMenuItemActive img.jxMenuItemIcon {
+  background-position: 2px -64px;
+}
+
+ul.jxMenu span.jxMenuSeparator {
+  /* Base setup */
+  display: block;
+
+  font-size: 10px;
+  line-height: 10px;
+  background-image: url(images/toolbar_separator_v.png);
+  background-repeat: repeat-x;
+  background-position: left center;
+}
+/**
+ * Confirm dialog classes
+ */
+.jxMessage {
+    text-align: center;
+    padding: 10px;
+    margin-top: 10px;
+}/* For Reference
+   Success, notice and error boxes from Blueprint */
+
+/* 
+.error      { background: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; }
+.notice     { background: #FFF6BF; color: #514721; border-color: #FFD324; }
+.success    { background: #E6EFC2; color: #264409; border-color: #C6D880; }
+.error a    { color: #8a1f11; }
+.notice a   { color: #514721; }
+.success a  { color: #264409; }
+*/
+
+
+.jxNoticeListContainer {
+  border: none;
+  padding: 0px;
+  margin: 0px;
+  font-size: 0px;
+  line-height: 0px;
+  z-index: 500;
+}
+
+.jxNoticeList {
+    position: relative;
+}
+
+.jxNoticeItemContainer {
+    position: relative;
+    overflow: hidden;
+}
+
+.jxNoticeItemContainer .jxChrome {
+  background-image: url(images/flyout_chrome.png);
+  padding: 5px 5px 7px 6px;
+}
+
+.jxHasChrome .jxNoticeItem  {
+  margin: 6px 6px 8px 7px;
+}
+
+.jxNoticeItem {
+    position: relative;
+    border: 2px solid #ccc;
+    margin: 0px;
+    padding: 0px;
+    background-color: #f8f8f8;
+    background-image: url(images/notice.png);
+    background-repeat: repeat-x;
+    background-position: left bottom;
+}
+
+.jxNoticeIcon {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  margin: 6px;
+  width: 16px;
+  height: 16px;
+  background-image: url(images/icons.png);
+  background-repeat: no-repeat;
+  background-position: 0px 0px;
+}
+
+.jxNotice {
+    display: block;
+    position: relative;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 18px;
+    color: #666;
+    margin: 0px;
+    border: none;
+    padding: 6px 26px 6px 10px; 
+}                          
+
+.jxNoticeError .jxNoticeItem { background-color: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; background-image: url(images/notice_error.png);}
+.jxNoticeWarning .jxNoticeItem { background-color: #FFF6BF; color: #514721; border-color: #FFD324; background-image: url(images/notice_warning.png);}
+.jxNoticeSuccess .jxNoticeItem { background-color: #E6EFC2; color: #264409; border-color: #C6D880; background-image: url(images/notice_success.png);}
+.jxNoticeInformation .jxNoticeItem { background-color: #F8F8F8; color: #666666; border-color: #CCCCCC; background-image: url(images/notice.png);}
+
+.jxNoticeError .jxNotice { color: #8a1f11; padding-left: 28px; }
+.jxNoticeWarning .jxNotice { color: #514721; padding-left: 28px; }
+.jxNoticeSuccess .jxNotice { color: #264409; padding-left: 28px; }
+.jxNoticeInformation .jxNotice { color: #666666; padding-left: 28px; }
+
+.jxNoticeError .jxNoticeIcon { background-position: 0px -32px; }
+.jxNoticeWarning .jxNoticeIcon { background-position: 0px -16px; }
+.jxNoticeSuccess .jxNoticeIcon { background-position: 0px -48px; }            
+.jxNoticeInformation .jxNoticeIcon { background-position: 0px -64px; }
+
+.jxNoticeClose {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    margin: 6px;
+    width: 16px;
+    height: 16px;
+    background-image: url(images/tab_close.png);
+    background-position: 0px 0px;
+    background-repeat: no-repeat;
+}/**
+ * @project         Jx
+ * @revision        $Id: panel.css 716 2010-03-02 16:07:19Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =============== */
+/* JX PANEL STYLES */
+/* =============== */
+
+.jxPanel {
+  /* Base setup */
+  display: block;
+  position:relative;
+}
+
+.jxPanelContentContainer {
+  /* Base setup */
+  /* need to test various scenarios to see if this is limiting */
+  overflow: hidden;
+  /*margin: 5px;*/
+  background-color: #f0f0f0;
+}
+
+/* the content panel inside a panel */
+.jxPanelContent {
+  /* Base setup */
+  /* position relative is required for panels to work correctly in safari */
+  position: relative;
+  display: block;
+  overflow: auto;
+
+  /*border: 1px solid #d8d8d8;*/
+  background-color: #fff;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxPanelTitle {
+  /* Base setup */
+  /* position relative is required for panel dragging to work correctly in safari */
+  display: block;
+  position: relative;
+
+  background-image: url(images/panelbar.png);
+  background-repeat: repeat-x;
+  background-position: left top;
+  /* note this is hard coded into jx.js JxPanel initialize function - change there as well as here */
+  height: 22px;
+  margin: 0;
+  padding: 0;
+
+  text-align: center;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+/* JX PANELSET STYLE FOR TITLE BAR */
+.jxPanelBar {
+  position: absolute;
+  line-height: 1px;
+  width: 100%;
+  height: 5px;
+  cursor: row-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxPanelIcon {
+  position: absolute;
+  left: 2px;
+  top: 3px;
+  width: 16px;
+  height: 16px;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+.jxPanelLabel {
+  /* make room for the loading spinner */
+  padding-left: 25px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  font-weight: bold;
+  /* line-height vertically aligns the label in the containing div. */
+  line-height:21px;
+  color: #000;
+  white-space: nowrap;
+}
+
+.jxPanelControls {
+  position: absolute;
+  top: 3px;
+  right: 2px;
+  height: 16px;
+  width: 80px;
+  overflow: hidden;
+}
+
+.jxPanelControls img {
+  background-image: url('images/panel_controls.png');
+  background-repeat: no-repeat;
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+}
+
+.jxPanelClose img {
+  background-position: 0px -32px;
+}
+
+.jxPanelMenu img {
+  background-position: 0px -48px;
+}
+
+.jxPanelHelp img {
+  background-position: 0px -64px;
+}
+
+.jxPanelCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxPanelMin .jxPanelCollapse img {
+  background-position: 0px 0px;
+}
+
+.jxPanelMax .jxPanelCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxPanelMaximize img {
+  background-position: 0px 0px;
+}
+
+.jxPanelLoading img {
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+  visibility:hidden;
+  position: absolute;
+  top: 1px;
+  left: 2px;
+}
+
+
+
+/* ========================= */
+/* JX BUTTON STYLES OVERIDES */
+/* ========================= */
+
+.jxPanelControls .jxButtonContainer,
+.jxPanelControls .jxButton,
+.jxPanelControls .jxButton:hover,
+.jxPanelControls .jxButton:active ,
+.jxPanelControls .jxButtonActive,
+.jxPanelControls .jxButtonActive:hover,
+.jxPanelControls .jxButtonActive:active,
+.jxPanelControls .jxDisabled .jxButton,
+.jxPanelControls .jxDisabled .jxButton:hover,
+.jxPanelControls .jxDisabled .jxButton:active {
+  padding: 0px;
+  margin: 0px;
+  border: none;
+  background-color: transparent;
+  background-image: none;
+}
+
+
+/* ========================== */
+/* JX TOOLBAR STYLES OVERIDES */
+/* ========================== */
+
+/* Multiple toolbars can be housed in  the toolbar container the container will expand vertically to accomodate wrapped toolbars */
+
+.jxPanelControls div.jxBarTop {
+  /* Base setup */
+  position: absolute;
+  right: 0px;
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  height: 16px;
+}
+
+.jxPanelControls .jxBarScroller {
+  left: auto;
+  right: 0px;
+}
+
+.jxPanelControls ul.jxToolbar {
+  float: right; 
+}
+
+.jxPanelControls ul.jxToolbar,
+.jxPanelControls li.jxToolItem {
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+}/**
+ * progress bar classes
+ */
+.jxProgressBar-container {
+    position: relative;
+    display: block;
+    width: 100%;
+}
+
+.jxProgressBar-message {
+    position: relative;
+    display: block;
+    color: black;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 20px;
+}
+
+.jxProgressBar {
+    position: relative;
+    display: block;
+    width: 100%;
+    height: 20px;
+    border: none;
+    margin: 0px;
+    padding: 0px;
+}
+
+.jxProgressBar-outline {
+    position: absolute;
+    display: block;
+    top: 0px;
+    left: 0px;
+    z-index: 10;
+    height: 20px;
+    border-left: 1px solid #cbc8c8;
+    border-right: 1px solid #cbc8c8;
+    background-image: url(images/progressbar.png);
+    background-position: 0px -140px;
+    width: 100%;
+}
+
+.jxProgressBar-fill {
+    position: absolute;
+    display: block;
+    top: 0px;
+    left: 0px;
+    z-index: 20;
+    height: 20px;
+    border: none;
+    background-image: url(images/progressbar.png);
+    background-position: 0px 0px;
+}
+
+.jxProgressStarting .jxProgressBar-fill {
+    border: none;
+}
+
+.jxProgressWorking .jxProgressBar-fill {
+    border-left: 1px solid #49afe8;
+}
+
+.jxProgressFinished .jxProgressBar-fill {
+    border-left: 1px solid #49afe8;
+    border-right: 1px solid #49afe8;
+}
+
+.jxProgressBar-text {
+    position: absolute;
+    display: block;
+    overflow: visible;
+    top: 0px;
+    left: 1px;
+    width: auto;
+    height: 20px;
+    z-index: 30;
+    border: none;
+    margin: 0px;
+    padding: 0px 0px 0px 4px;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 20px;
+    white-space: nowrap;
+}
+.jxHasVerticalScrollbar,
+.jxHasHorizontalScrollbar {
+    position: relative;
+    overflow: hidden;
+}
+
+.jxScrollbarChildWrapper {
+    overflow: hidden;
+}
+
+.jxHasVerticalScrollbar {
+    padding-right: 25px;
+}
+
+.jxHasVerticalScrollbar .jxScrollbarContainer {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 20px;
+    height: 100%;
+    border: none;
+    border-left: 1px solid black;
+}
+
+.jxHasVerticalScrollbar .jxScrollLeft,
+.jxHasVerticalScrollbar .jxScrollRight {
+    width: 20px;
+    height: 20px;
+    display: block;
+    background-color: blue;
+}
+
+.jxHasVerticalScrollbar .jxSliderContainer {
+    height: 100%;
+    width: 20px;
+    border: none;
+}
+
+.jxHasHorizontalScrollbar {
+    padding-bottom: 25px;
+}
+
+.jxHasHorizontalScrollbar .jxScrollbarContainer {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 20px;
+    width: 100%;
+    border: none;
+    border-top: 1px solid black;
+}
+
+.jxHasHorizontalScrollbar .jxScrollLeft,
+.jxHasHorizontalScrollbar .jxScrollRight {
+    width: 20px;
+    height: 20px;
+    display: block;
+    background-color: blue;
+}
+ 
+.jxHasHorizontalScrollbar .jxScrollLeft {
+    float: left;
+}
+
+.jxHasHorizontalScrollbar .jxScrollRight {
+    float: right;
+}
+
+.jxHasHorizontalScrollbar .jxSlider {
+    float: left;
+}
+
+.jxHasHorizontalScrollbar .jxSliderContainer {
+    height: 20px;
+    width: 100%;
+    border: none;
+}
+
+.jxHasVerticalScrollbar .jxSliderKnob,
+.jxHasHorizontalScrollbar .jxSliderKnob {
+    width: 20px;
+    height: 20px;
+    background-color: black;
+    cursor: pointer;
+}/**
+ * Slider classes
+ */
+.jxSliderContainer {
+    width: 100%;
+    height: 10px;
+    border: 1px solid black;
+}
+
+.jxSliderKnob {
+    width: 10px;
+    height: 10px;
+    background-color: black;
+    cursor: pointer;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: splitter.css 294 2009-04-02 12:26:26Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =============== */
+/* SPLITTER STYLES */
+/* =============== */
+.jxSplitterMask{
+    position:absolute;
+    top:0;
+    left:0;
+    width:100%;
+    height:100%;
+    overflow:hidden;
+    background-image: url(images/a_pixel.png);
+    z-index:1;
+}
+
+.jxSplitBarHorizontal {
+  display: block;
+  position: absolute;
+  font-size: 0px;
+  line-height: 0px;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  width: 5px;
+  height: 100%;
+  cursor: col-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxSplitBarVertical {
+  display: block;
+  position: absolute;
+  font-size: 0px;
+  line-height: 0px;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  width: 100%;
+  height: 5px;
+  cursor: row-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxSplitContainer {
+  display: block;
+  position: relative;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  overflow: hidden;
+}
+
+.jxSplitArea {
+  display: block;
+  position: absolute;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  /*overflow: hidden;*/
+  z-index: 0;
+}
+
+.jxSplitBarDrag {
+  background-color: #eee;
+}
+
+.jxSnapHorizontalBefore {
+  width: 5px;
+  height: 5px;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  background-color: #aaa;
+}
+
+.jxSnapHorizontalAfter {
+  width: 5px;
+  height: 5px;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  background-color: #aaa;
+}/**
+ * @project         Jx
+ * @revision        $Id: tab.css 875 2010-04-24 06:10:53Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ================== */
+/* TAB CONTENT STYLES */
+/* ================== */
+
+/* The tabBox consists of a box containing a tabbar and the tab content areas.
+   It can be used within the body or nested within another object.
+*/
+
+.jxTabSetContainer {
+  /* This is an example of a container that can be used to hold a tabBox
+     the position need to be explicitly set, as well as the width and height. */
+  /* Base setup */
+  position: relative;
+  display: block;
+  overflow: hidden;
+
+  width: 200px;
+  height: 200px;
+  margin: 0px;
+  padding: 0px;
+  background-color: #fff;
+}
+
+.jxTabSetContainer  .jxBarContainer {
+  /* Base setup */
+  z-index: auto;
+}
+
+.tabContent {
+  /* the width and height need to be set to 100% to:
+     1. fill the tab box area
+     2. allow a scrolling content area in IE */
+  /* Base setup */
+  display: none;
+  position: relative;
+  width:100%;
+  height: 100%;
+  overflow: auto;
+}
+
+.tabContentActive {
+  /* Base setup */
+  display: block;
+}
+
+/* ======================== */
+/* BASE TAB (BUTTON) STYLES */
+/* ======================== */
+
+span.jxTabContainer {
+  /* Base setup */
+  display: block;
+  position: relative;
+
+  margin: 0px;
+  padding: 2px;
+  border: none;
+}
+
+a.jxTab {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+
+  /* Using background images, the A contains the left side of the background */
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  background-repeat: no-repeat;
+  text-decoration: none;
+  outline: none;
+}
+
+span.jxTabContent {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  font-size: 0px;
+  line-height: 0px;
+
+  /* The SPAN contains the other side of the tab background image
+     and the tab label */
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  background-repeat: no-repeat;
+}
+
+img.jxTabIcon {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+
+  width: 16px;
+  height: 16px;
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+span.jxTabLabel {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  margin: 0px;
+  padding: 0px;
+  color: #000;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  line-height: 16px;
+}
+
+a.jxTabClose {
+  /* Base setup */
+  display: block;
+  position: absolute;
+  cursor: pointer;
+  outline: none;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+  width: 16px;
+  height: 16px;
+  background-image: url(images/tab_close.png);
+  background-position: 0px 0px;
+  background-repeat: no-repeat;
+}
+
+.jxDisabled a.jxTab,
+.jxDisabled span.jxTabContent span,
+.jxDisabled a.jxTabClose {
+  cursor: default;
+}
+
+.jxTabBox {
+}
+
+/* ======================================= */
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+/* ======================================= */
+
+.jxTabBarTop .jxBarWrapper, 
+.jxTabBarBottom .jxBarWrapper {
+  padding-left: 2px;
+}
+
+.jxBarTop span.jxTabContainer,
+.jxBarBottom span.jxTabContainer {
+  /* Base setup */
+  margin-right: -1px;
+  padding: 2px 0px;
+}
+
+.jxBarTop a.jxTab,
+.jxBarTop span.jxTabContent,
+.jxTabBarTop .jxBarControls a.jxButton,
+.jxTabBarTop .jxBarControls span.jxButtonContent{
+  background-image: url(images/tab_top.png);
+}
+
+.jxBarBottom a.jxTab,
+.jxBarBottom span.jxTabContent,
+.jxTabBarBottom .jxBarControls a.jxButton,
+.jxTabBarBottom .jxBarControls span.jxButtonContent {
+  background-image: url(images/tab_bottom.png);
+}
+
+/* Closeable Tab */
+.jxBarTop a.jxTabClose,
+.jxBarBottom a.jxTabClose {
+  top: 5px;
+  right: 5px;
+}
+
+.jxBarTop .jxTabClose span.jxTabContent,
+.jxBarBottom .jxTabClose span.jxTabContent {
+  padding-right: 16px;
+}
+
+/* Normal Tab */
+.jxBarTop a.jxTab,
+.jxBarBottom a.jxTab {
+  /* Base setup */
+
+  padding-left: 4px; /* makes room for the left of the tab bg */
+  background-position: left -24px; 
+}
+
+.jxBarTop span.jxTabContent,
+.jxBarBottom span.jxTabContent {
+  /* Base setup */
+
+  padding: 4px 4px 4px 0px; /* makes space around the label */
+  background-position: right -24px; 
+}
+
+/* Active tab */
+.jxBarTop a.jxTabActive,
+.jxBarBottom a.jxTabActive {
+  background-position: left -72px; 
+}
+
+.jxBarTop a.jxTabActive span.jxTabContent,
+.jxBarBottom a.jxTabActive span.jxTabContent {
+  background-position: right -72px; 
+}
+
+/* Focus tab */
+.jxBarTop a.jxTab:focus,
+.jxBarBottom a.jxTab:focus {
+  background-position: left -96px; 
+}
+
+.jxBarTop a.jxTab:focus span.jxTabContent,
+.jxBarBottom a.jxTab:focus span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:focus,
+.jxBarBottom a.jxTabActive:focus {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:focus span.jxTabContent,
+.jxBarBottom a.jxTabActive:focus span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Hover Normal and Active  Tab */
+.jxBarTop a.jxTab:hover,
+.jxBarTop a.jxTabActive:hover,
+.jxBarBottom a.jxTab:hover,
+.jxBarBottom a.jxTabActive:hover {
+  background-position: left -48px; 
+}
+
+.jxBarTop a.jxTab:hover span.jxTabContent,
+.jxBarTop a.jxTabActive:hover span.jxTabContent,
+.jxBarBottom a.jxTab:hover span.jxTabContent,
+.jxBarBottom a.jxTabActive:hover span.jxTabContent {
+  background-position: right -48px; 
+}
+
+/* Click Normal and Focused Tab */
+.jxBarTop a.jxTabPressed,
+.jxBarTop a.jxTabPressed:focus,
+.jxBarBottom a.jxTabPressed,
+.jxBarBottom a.jxTabPressed:focus {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed span.jxTabContent,
+.jxBarTop a.jxTabPressed:focus span.jxTabContent,
+.jxBarBottom a.jxTabPressed span.jxTabContent,
+.jxBarBottom a.jxTabPressed:focus span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* Hover, Focus and Pressing Disabled Tab */
+.jxBarTop .jxDisabled a.jxTab:focus,
+.jxBarTop .jxDisabled a.jxTab:active,
+.jxBarTop .jxDisabled a.jxTab:hover,
+.jxBarTop .jxDisabled a.jxTabPressed,
+.jxBarBottom .jxDisabled a.jxTab:focus,
+.jxBarBottom .jxDisabled a.jxTab:active,
+.jxBarBottom .jxDisabled a.jxTab:hover,
+.jxBarBottom .jxDisabled a.jxTabPressed {
+  background-position: left -24px; /* do not switch the left BG */
+}
+
+  
+.jxBarTop .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabPressed span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabPressed  span.jxTabContent {
+  background-position: right -24px; /* do not switch the right BG */
+}
+
+/* Hover, Focus Disabled Active Tab */
+.jxBarTop .jxDisabled a.jxTabActive:focus,
+.jxBarTop .jxDisabled a.jxTabActive:active,
+.jxBarTop .jxDisabled a.jxTabActive:hover,
+.jxBarBottom .jxDisabled a.jxTabActive:focus,
+.jxBarBottom .jxDisabled a.jxTabActive:active,
+.jxBarBottom .jxDisabled a.jxTabActive:hover {
+  background-position: left -72px; /* do not switch the left BG */
+}
+
+  
+.jxBarTop .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabActive:hover span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:hover span.jxTabContent {
+  background-position: right -72px; /* do not switch the right BG */
+}
+
+.jxBarTop img.jxTabIcon,
+.jxBarBottom img.jxTabIcon {
+  vertical-align: middle;
+  /* Base setup */
+}
+
+.jxBarTop span.jxTabLabel,
+.jxBarBottom span.jxTabLabel {
+  /* Base setup */
+  vertical-align: middle;
+  height: 16px;
+
+  padding: 0px 4px 0px 4px;
+}
+
+
+/* ================================= */
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+/* ================================= */
+
+.jxTabBarLeft .jxBarWrapper, 
+.jxTabBarRight .jxBarWrapper {
+  padding-top: 2px;
+}
+
+.jxBarLeft span.jxTabContainer,
+.jxBarRight span.jxTabContainer {
+  /* Base setup */
+  margin-bottom: -1px;
+  padding: 0px 2px;
+}
+
+
+.jxBarLeft a.jxTab,
+.jxBarLeft span.jxTabContent {
+  background-image: url(images/tab_left.png);
+}
+
+.jxBarRight a.jxTab,
+.jxBarRight span.jxTabContent {
+  background-image: url(images/tab_right.png);
+}
+
+/* Closeable Tab */
+.jxBarLeft a.jxTabClose,
+.jxBarRight a.jxTabClose {
+  top: 5px;
+  left: 5px;
+}
+
+.jxBarLeft .jxTabClose span.jxTabContent,
+.jxBarRight .jxTabClose span.jxTabContent {
+  padding-top: 16px;
+}
+
+/* Normal Tab */
+.jxBarLeft a.jxTab,
+.jxBarRight a.jxTab {
+  padding-top: 4px; /* makes room for the top of the tab bg */
+  background-position: -24px top; 
+}
+
+.jxBarLeft span.jxTabContent,
+.jxBarRight span.jxTabContent {
+  padding: 0px 4px 4px 4px; /* makes space around the label */
+  background-position: -24px bottom; 
+}
+
+/* Active Tab */
+.jxBarLeft a.jxTabActive,
+.jxBarRight a.jxTabActive {
+  background-position: -72px top; 
+}
+
+.jxBarLeft a.jxTabActive span.jxTabContent,
+.jxBarRight a.jxTabActive span.jxTabContent {
+  background-position: -72px bottom; 
+}
+
+/* Focus tab */
+.jxBarLeft a.jxTab:focus,
+.jxBarRight a.jxTab:focus {
+  background-position: -96px top; 
+}
+
+.jxBarLeft a.jxTab:focus span.jxTabContent,
+.jxBarRight a.jxTab:focus span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:focus,
+.jxBarRight a.jxTabActive:focus {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:focus span.jxTabContent,
+.jxBarRight a.jxTabActive:focus span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Hover Normal and Active tab */
+.jxBarLeft a.jxTab:hover,
+.jxBarLeft a.jxTabActive:hover,
+.jxBarRight a.jxTab:hover,
+.jxBarRight a.jxTabActive:hover {
+  background-position: -48px top; 
+}
+
+.jxBarLeft a.jxTab:hover span.jxTabContent,
+.jxBarLeft a.jxTabActive:hover span.jxTabContent,
+.jxBarRight a.jxTab:hover span.jxTabContent,
+.jxBarRight a.jxTabActive:hover span.jxTabContent {
+  background-position: -48px bottom; 
+}
+
+/* Click Normal and Focused Tab */
+.jxBarLeft a.jxTabPressed,
+.jxBarLeft a.jxTabPressed:focus,
+.jxBarRight a.jxTabPressed,
+.jxBarRight a.jxTabPressed:focus {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed span.jxTabContent,
+.jxBarLeft a.jxTabPressed:focus span.jxTabContent,
+.jxBarRight a.jxTabPressed span.jxTabContent,
+.jxBarRight a.jxTabPressed:focus span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Hover, Focus and Pressing Disabled Tab */
+.jxBarLeft .jxDisabled a.jxTab:focus,
+.jxBarLeft .jxDisabled a.jxTab:active,
+.jxBarLeft .jxDisabled a.jxTab:hover,
+.jxBarLeft .jxDisabled a.jxTabPressed,
+.jxBarRight .jxDisabled a.jxTab:focus,
+.jxBarRight .jxDisabled a.jxTab:active,
+.jxBarRight .jxDisabled a.jxTab:hover,
+.jxBarRight .jxDisabled a.jxTabPressed {
+  background-position: -24px top; /* do not switch the left BG */
+}
+
+  
+.jxBarLeft .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabPressed span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabPressed  span.jxTabContent {
+  background-position: -24px bottom; /* do not switch the right BG */
+}
+
+/* Hover, Focus Disabled Active Tab */
+.jxBarLeft .jxDisabled a.jxTabActive:focus,
+.jxBarLeft .jxDisabled a.jxTabActive:active,
+.jxBarLeft .jxDisabled a.jxTabActive:hover,
+.jxBarRight .jxDisabled a.jxTabActive:focus,
+.jxBarRight .jxDisabled a.jxTabActive:active,
+.jxBarRight .jxDisabled a.jxTabActive:hover {
+  background-position: -72px top; /* do not switch the left BG */
+}
+
+.jxBarLeft .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabActive:hover span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:hover span.jxTabContent {
+  background-position: -72px bottom; /* do not switch the right BG */
+}
+
+.jxBarLeft span.jxTabLabel,
+.jxBarRight span.jxTabLabel {
+  padding: 4px 0px 4px 0px;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: toolbar.css 912 2010-05-21 21:33:08Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============== */
+/* TOOLBAR STYLES */
+/* ============== */
+
+/* Multiple toolbars can be housed in  the toolbar container.
+   The container will expand vertically to accomodate wrapped toolbars */
+
+.jxBarContainer {
+  /* Base setup */
+  display: block;
+  position: relative;
+  z-index: 1;
+  overflow: hidden;
+
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+
+  background-color: #f0f0f0;
+}
+
+.jxBarTop,
+.jxBarBottom {
+  /* Horizontally oriented toolbars */
+  /* Base setup */
+  width: 100%; /* fills the width, may be needed for JS style sniffing */
+  height: 28px;
+  background-image: url(images/toolbar.png);
+  background-repeat: repeat-x;
+  background-position: 0px 0px;
+  overflow: hidden;
+}
+
+.jxTabBox .jxTabBarTop {
+  background-image: url(images/tabbar.png);
+  background-position: 0px bottom;
+}
+
+.jxTabBox .jxTabBarBottom {
+  background-image: url(images/tabbar_bottom.png);
+  background-position: 0px top;
+}
+
+.jxBarLeft,
+.jxBarRight {
+  /* Vertically oriented toolbars */
+  /* Base setup */
+  width: auto;
+  height: 100%;
+  background-image: url(images/toolbar.png);
+  background-repeat: repeat-x;
+  background-position: 0px 0px;
+  float: left;
+  overflow: hidden;
+}
+
+.jxTabBox .jxTabBarLeft {
+  background-image: url(images/tabbar_left.png);
+  background-repeat: repeat-y;
+  background-position: right 0px;
+}
+
+.jxTabBox .jxTabBarRight {
+  background-image: url(images/tabbar_right.png);
+  background-repeat: repeat-y;
+  background-position: left 0px;
+}
+
+
+.jxBarTop .jxBarScroller,
+.jxBarBottom .jxBarScroller {
+  float: left;
+  height: 28px;
+  overflow: hidden;
+  z-index: 0;
+}
+
+.jxBarTop .jxBarScroller .jxBarWrapper,
+.jxBarBottom .jxBarScroller .jxBarWrapper {
+  float: left;
+  height: 28px;
+  overflow: hidden;
+  width: 10000%;
+}
+
+.jxBarTop .jxBarControls .jxButtonContainer,
+.jxBarBottom .jxBarControls .jxButtonContainer {
+  /* float: right; */
+  z-index: 1;
+  padding: 2px 0px;
+  margin-left: -1px;
+}
+
+.jxBarTop .jxBarScrollLeft img.jxButtonIcon,
+.jxBarBottom .jxBarScrollLeft img.jxButtonIcon {
+  background-image: url(images/emblems.png);
+  background-position: 0px -80px;
+}
+
+.jxBarTop .jxBarScrollRight img.jxButtonIcon,
+.jxBarBottom .jxBarScrollRight img.jxButtonIcon {
+  background-image: url(images/emblems.png);
+  background-position: 0px -96px;
+}
+
+.jxBarControls {
+    float: right;
+    position: relative;
+    font-size: 0px;
+    line-height: 0px;
+}
+
+/* The jx toolbar and tabbar are both built out of a UL
+   The margins/padding are flattened out, and the list markers are hidden
+   UL's are floated left so multiple toolbars can be in the samae row.
+   In IE, the UL needs to have a specified width to prevent button wrapping.
+
+   The tab background uses the sliding door technique so tabs can expand to
+   accomodate content up to 200 px wide (top/bottom tabs) or 200px high
+   (left/right tabs).  All parts and states of the tab BG graphics are in the
+   same image so they can be treated like sprites.
+
+   Horizontal tabs can contain text or an image label.  Vertical tabs need an
+   image label.
+*/
+
+ul.jxToolbar,
+ul.jxTabBar {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  clear: none;
+  list-style-type: none;
+
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  padding: 0px;
+  border: none;
+}
+
+.jxBarTop ul.jxToolbar,
+.jxBarBottom ul.jxToolbar,
+.jxBarTop ul.jxTabBar,
+.jxBarBottom ul.jxTabBar {
+}
+
+/* LI's are floated to the left, to make a horizontal row of buttons*/
+
+li.jxToolItem {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  font-size: 0px;
+  line-height: 0px;
+  white-space: nowrap;
+  padding: 0px;
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  border: none;
+}
+
+/* inputs in toolbars wrap in IE */
+li.jxToolItem .jxInputWrapper {
+  white-space: nowrap;
+}
+
+/* Seperator height should match that of button images
+   and the margins+padding+border should add up to the same total too. */
+
+li.jxToolItem  span.jxBarSeparator {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  font-size: 0px;
+  line-height: 0px;
+
+  border: 0px;
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  padding: 4px;
+  background-repeat: no-repeat;
+  background-position: center center;
+}
+
+.jxBarTop  li.jxToolItem  span.jxBarSeparator,
+.jxBarBottom  li.jxToolItem  span.jxBarSeparator {
+  /* width/height should be defined */
+  width: 8px;
+  height: 20px;
+  background-image: url(images/toolbar_separator_h.png);
+}
+
+.jxBarLeft  li.jxToolItem  span.jxBarSeparator,
+.jxBarRight  li.jxToolItem  span.jxBarSeparator {
+  /* width/height should be defined */
+  width: 20px;
+  height: 8px;
+  background-image: url(images/toolbar_separator_v.png);
+}
+
+/* Vertically oriented toolbars need floats cleared */
+
+.jxBarLeft ul.jxToolbar,
+.jxBarLeft ul.jxTabBar,
+.jxBarLeft li.jxToolItem,
+.jxBarRight ul.jxToolbar,
+.jxBarRight ul.jxTabBar,
+.jxBarRight li.jxToolItem
+{
+  /* Base setup */
+  clear: both;
+}
+
+
+/* Aligning options */
+.jxToolbarAlignLeft ul {
+    float: left;
+}
+
+.jxToolbarAlignRight ul {
+    float: right;
+}
+
+.jxToolbarAlignCenter {
+    text-align: center;
+}
+
+.jxToolbarAlignCenter ul {
+    float: none;
+}
+
+.jxToolbarAlignCenter ul li {
+    float: none;
+    display: inline;
+}/*
+ * Tooltip classes
+ */
+.jxTooltip {
+	width: auto;
+	height: auto;
+	background-color: black;
+	color: white;
+	padding: 5px;
+	z-index: 65536;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: tree.css 755 2010-03-15 03:09:37Z jonlb at comcast.net $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* TREE STYLES */
+/* =========== */
+
+/* The jx tree built out of nested ULs
+   For this to work visually, the margins and padding need to be flattened
+   out, and the list marker needs to be hidden
+*/
+
+
+.jxTree,
+.jxTreeRoot {
+  /* relative positioning is required for IE to fix the peek-a-boo bug */
+  position:relative;
+  display: block;
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxTreeNest {
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+  background-repeat: repeat-y;
+  background-position: left top;
+}
+
+/* Node Classes */
+
+li.jxTreeContainer {
+  /* relative positioning is required for IE to fix the peek-a-boo bug */
+  position:relative;
+  display: block;
+  margin: 0px;
+  padding: 0px;
+  background-repeat: no-repeat;
+  /* background branches may need to shift up/down according to height of the node */
+  background-position: left top;
+  white-space: nowrap;
+  font-size: 0px;
+  line-height: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxTree li.jxTreeContainer {
+  margin-left: 16px;
+}
+
+a.jxTreeItem {
+  position: relative;
+  display: block;
+  cursor: pointer;
+  outline: none;
+  overflow: hidden;
+
+  background-image: url(images/tree_hover.png);
+  background-repeat: repeat-x;
+  background-position: left top;
+  border: none;
+
+  margin: 0px 1px 0px 17px;
+  padding: 0px 0px 0px 20px;
+  z-index: 0;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  color: #000;
+  text-decoration: none;
+  /* Line Height needs to be an even number so branches line up properly */
+  line-height: 20px;
+  height: 20px;
+}
+
+
+a.jxTreeItem:focus {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 16px;
+  background-position: left -72px;
+}
+
+a.jxTreeItem:hover,
+li.jxTreeContainer a.jxHover {
+  /*border: 1px solid #C5E0FF;*/
+  border-left: 1px solid #CDDFFD;
+  border-right: 1px solid #CDDFFD;
+  margin: 0px 0px 0px 16px;
+  background-color: #CDE5FF;
+  background-position: left -24px;
+}
+
+li.jxTreeContainer a.jxSelected,
+li.jxTreeContainer a.jxSelected:hover,
+li.jxTreeContainer a.jxPressed,
+li.jxTreeContainer a.jxPressed:hover {
+  border-left: 1px solid #8AABFB;
+  border-right: 1px solid #8AABFB;
+  margin: 0px 0px 0px 16px;
+  background-color: #CDE5FF;
+  background-position: left -48px;
+}
+
+li.jxDisabled a.jxTreeItem {
+  cursor: default;
+}
+            
+li.jxDisabled a.jxTreeItem:focus,
+li.jxDisabled a.jxTreeItem:hover {
+  background-position: left top;
+  background-color: transparent;
+  border: none;
+  margin: 0px 1px 0px 17px;
+}
+
+.jxTreeNest {
+  background-image: url(images/tree_vert_line.png);
+}
+
+img.jxTreeImage,
+img.jxTreeIcon {
+  position: absolute;
+  display: inline;
+
+  left: 0px;
+  top: 0px;
+  width: 16px;
+  height: 20px;
+
+  z-index: 1;
+
+  background-image: url(images/tree.png);
+  background-repeat: no-repeat;
+
+  border: 0px;
+  margin: 0px;
+}
+
+img.jxTreeIcon { 
+  height: 16px;
+  top: 2px;
+  left: 1px;
+}
+
+.jxTreeBranchOpen .jxTreeIcon,
+.jxTreeBranchLastOpen .jxTreeIcon {
+  background-position: left -40px; /* open folder image */
+}
+
+
+.jxTreeBranchOpen .jxTreeImage {
+  background-position: left -100px; /* minus image */
+}
+
+.jxTreeBranchLastOpen .jxTreeImage {
+  background-position: left -160px; /* minus last image */
+}
+
+.jxTreeBranchClosed .jxTreeIcon,
+.jxTreeBranchLastClosed .jxTreeIcon {
+  background-position: left -20px; /* closed folder image */
+}
+
+
+.jxTreeBranchClosed .jxTreeImage {
+  background-position: left -80px; /* plus image */
+}
+
+.jxTreeBranchLastClosed .jxTreeImage {
+  background-position: left -140px; /* plus last image */
+}
+
+.jxTreeLeaf .jxTreeIcon,
+.jxTreeLeafLast .jxTreeIcon {
+  background-position: left 0px; /* page image */
+}
+
+.jxTreeLeaf .jxTreeImage {
+  background-position: left -60px; /* node image */
+}
+
+.jxTreeLeafLast .jxTreeImage {
+  background-position: left -120px; /* last node image */
+}
+
+
+a.jxTreeItem,
+img.jxTreeImage,
+img.jxTreeIcon,
+span.jxTreeLabel,
+.jxTreeItemContainer input {
+    vertical-align: middle;
+}
+
+img.jxTreeImage.jxBusy {
+	background-image: url(images/spinner_16.gif);
+    background-position: left top;
+}
+
+/* FileUploadPanel */
+.jxFileUploadPanel {
+    padding: 5px;
+}
+
+.jxFileUploadPanel .jxInputContainer {
+    /*width: 300px;*/
+}
+ 
+.jxUploadQueue {
+    /*width: 100%;*/  
+    /*margin-top: 10px;*/  
+}
+
+.jxUploadQueue li {
+    display: block;
+    position: relative;
+    overflow: hidden;
+    /*width: 95%;*/
+    /*clear: both;*/
+    /*border-top: 1px solid black;*/
+    padding: 2px;
+}
+
+.jxUploadQueue div span {
+    display: block;
+}
+
+.jxUploadQueue li span.jxUploadFileName {
+    /*float: left;*/
+    font-size: 12px;
+    line-height: 16px;
+    padding-left: 2px;
+}
+
+.jxUploadQueue li span.jxUploadFileDelete,
+.jxUploadQueue li span.jxUploadFileProgress,
+.jxUploadQueue li span.jxUploadFileComplete,
+.jxUploadQueue li span.jxUploadFileError {
+    /*float: right;*/
+    position: absolute;
+    top: 2px;
+    right: 2px;
+    width: 16px;
+    height: 16px;
+    background-repeat: no-repeat;
+    cursor: pointer;
+}
+
+.jxUploadQueue li span.jxUploadFileDelete {
+    background-image: url('images/icons.png');
+    background-position: 0px -128px;
+}
+
+.jxUploadQueue li span.jxUploadFileProgress {
+    background-image: url('images/spinner_16.gif');
+    background-position: top left;
+}
+
+.jxUploadQueue li span.jxUploadFileComplete {
+    background-image: url('images/icons.png');
+    background-position: 0px -48px;
+}
+
+.jxUploadQueue li span.jxUploadFileError {
+    background-image: url('images/icons.png');
+    background-position: 0px -32px;
+}
+
+.jxUploadFileErrorTip {
+  padding: 4px 4px 4px 20px; 
+  border: 2px solid #ddd;
+  background: url("images/icons.png") no-repeat 0px -32px;
+  color: black;
+  width: 100px;
+}

Added: trunk/lib/jxLib/themes/delicious/ie6.css
===================================================================
--- trunk/lib/jxLib/themes/delicious/ie6.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/delicious/ie6.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,217 @@
+/**
+ * @project         Jx
+ * @revision        $Id: ie6.css 935 2010-05-28 17:36:13Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* IE < 7 STYLES */
+/* ============= */
+
+/* 24 bit images do not appear correctly in IE versions below 7. Applying a 
+ * filter through the class below will make them appear correctly.
+ */
+.png24{filter:expression(Jx.applyPNGFilter(this))}
+
+/* Opacity needs to be set in IE6 and below using the following filters.
+ * Please note that IE8 changed how filters are written. 
+ */
+.jxChromeDrag {filter: Alpha(opacity=50);}
+.jxDisabled {filter:Alpha(opacity=40);}
+.jxDisabled * {filter:Alpha(opacity=40);}
+.jxMask {filter:Alpha(opacity=50);}
+.jxModalMask {filter: Alpha(opacity=20);}
+.jxSpinner {filter: alpha(opacity=50);}
+
+iframe.jxIframeShim {filter:Alpha(opacity:0);}
+
+/* List items do not render properly under several conditions.  
+ * Applying a height to the LI forces it to render properly.
+ * Content that is taller than the li simply forces the li to be taller 
+ */
+.jxTree li,
+.jxTreeRoot li {
+  height: 20px;
+}
+
+/* tree item focus style */
+.jxTree a:active,
+.jxTreeRoot a:active {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 14px;
+  background-position: left -72px;
+  outline: expression(hideFocus='true');
+}
+
+/* IE versions 7 and below do not recognize the focus pseudo-class, but instead
+ * use the active pseudo-class.  Other browsers use the active-pseudo-class
+ * while something is being pressed so IE specific definitions are needed. */
+/* focus button */
+ul.jxToolbar .jxButton:active,
+.jxButton:active {
+  background-position: left -96px;
+  outline: expression(hideFocus='true');
+}
+
+ul.jxToolbar .jxButton:active span.jxButtonContent,
+.jxButton:active span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:active,
+.jxButtonActive:active {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:active span.jxButtonContent,
+.jxButtonActive:active span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* clicking normal button */
+ul.jxToolbar .jxButtonPressed:active,
+.jxButtonPressed:active {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed:active span.jxButtonContent,
+.jxButtonPressed:active span.jxButtonContent {
+  background-position: right -120px;
+}
+
+.jxButtonDisclose:active {
+  background-position: right -96px;
+}
+
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+
+/* Focus tab */
+.jxBarTop a.jxTab:active,
+.jxBarBottom a.jxTab:active {
+  background-position: left -96px; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarTop a.jxTab:active span.jxTabContent,
+.jxBarBottom a.jxTab:active span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:active,
+.jxBarBottom a.jxTabActive:active {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:active span.jxTabContent,
+.jxBarBottom a.jxTabActive:active span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Click Focused Tab */
+.jxBarTop a.jxTabPressed:active,
+.jxBarBottom a.jxTabPressed:active {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed:active span.jxTabContent,
+.jxBarBottom a.jxTabPressed:active span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+
+/* Focus tab */
+.jxBarLeft a.jxTab:active,
+.jxBarRight a.jxTab:active {
+  background-position: -96px top; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarLeft a.jxTab:active span.jxTabContent,
+.jxBarRight a.jxTab:active span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:active,
+.jxBarRight a.jxTabActive:active {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:active span.jxTabContent,
+.jxBarRight a.jxTabActive:active span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Click Focused Tab */
+.jxBarLeft a.jxTabPressed:active,
+.jxBarRight a.jxTabPressed:active {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed:active span.jxTabContent,
+.jxBarRight a.jxTabPressed:active span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Menu Item icon position.
+   IE 6 seems to want to line up the image differently.  
+   This override just nudges it back up. */
+
+img.jxMenuItemIcon {
+  top: 3px;
+}
+
+/* Menu Item States */ 
+
+a.jxMenuItemActive {
+  background-position: 1px -97px;
+}
+
+a.jxMenuItem:active {
+  background-position: 1px -73px;
+  outline: expression(hideFocus='true');
+}
+
+a.jxMenuItemActive:active {
+  background-position: 1px -169px;
+}
+
+a.jxMenuItem:hover {
+  background-position: 1px -25px;
+}
+
+a.jxMenuItem:hover span.jxMenuItemContent {
+  border-top: 1px solid #C5E0FF;  /* forces IE to render properly */
+}
+
+a.jxMenuItemActive:hover {
+  background-position: 1px -121px;
+}
+
+a.jxMenuItemPressed,
+a.jxMenuItemPressed:hover {
+  background-position: 1px -49px;
+}
+
+span.jxMenuItemContent {
+  border-top: 1px solid #fff; /* forces IE to render properly */
+}
+
+span.jxMenuItemContent span {
+  font-size: 15px;
+  line-height: 15px;
+}
+
+
+/* chrome in dialogs doesn't resize properly when collapsing a dialog before
+ * moving or resizing it in IE 6 only, hiding overflow seems to do the trick
+ */
+.jxChrome {
+  overflow: hidden;
+}
+

Added: trunk/lib/jxLib/themes/delicious/ie7.css
===================================================================
--- trunk/lib/jxLib/themes/delicious/ie7.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/delicious/ie7.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,176 @@
+/**
+ * @project         Jx
+ * @revision        $Id: ie7.css 979 2010-09-08 19:34:36Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* IE 7 STYLES */
+/* =========== */
+
+/* force menus to have a size so they don't collapse */
+.jxMenuItemContainer {
+    filter:Alpha(opacity=100);
+}
+
+/* Opacity needs to be set in IE6 and below using the following filters.
+ * Please note that IE8 changed how filters are written. 
+ */
+.jxChromeDrag {filter: Alpha(opacity=50);}
+.jxDisabled {filter:Alpha(opacity=40);}
+.jxDisabled * {filter:Alpha(opacity=40);}
+.jxMask {filter:Alpha(opacity=50);}
+.jxModalMask {filter: Alpha(opacity=20);}
+.jxSpinner {filter: alpha(opacity=50);}
+iframe.jxIframeShim {filter:Alpha(opacity:0);}
+
+/* tree item focus style */
+.jxTree a:active,
+.jxTreeRoot a:active {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 14px;
+  background-position: left -72px;
+  outline: expression(hideFocus='true');
+}
+
+.jxTree,
+.jxTreeRoot,
+.jxTreeNest,
+li.jxTreeContainer {
+ /* zoom needed to fix alignment/sizing issues in IE 7 */
+ zoom: 1;
+}
+
+/* IE versions 7 and below do not recognize the focus pseudo-class, but instead
+ * use the active pseudo-class.  Other browsers use the active-pseudo-class
+ * while something is being pressed so IE specific definitions are needed. */
+/* focus button */
+ul.jxToolbar .jxButton:active,
+.jxButton:active {
+  background-position: left -96px;
+  outline: expression(hideFocus='true');
+}
+
+ul.jxToolbar .jxButton:active span.jxButtonContent,
+.jxButton:active span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:active,
+.jxButtonActive:active {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:active span.jxButtonContent,
+.jxButtonActive:active span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* clicking focused button */
+ul.jxToolbar .jxButtonPressed:active,
+.jxButtonPressed:active {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed:active span.jxButtonContent,
+.jxButtonPressed:active span.jxButtonContent {
+  background-position: right -120px;
+}
+
+a.jxButtonDisclose:active {
+  background-position: right -96px;
+  outline: expression(hideFocus='true');
+}
+
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+
+/* Focus tab */
+.jxBarTop a.jxTab:active,
+.jxBarBottom a.jxTab:active {
+  background-position: left -96px; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarTop a.jxTab:active span.jxTabContent,
+.jxBarBottom a.jxTab:active span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:active,
+.jxBarBottom a.jxTabActive:active {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:active span.jxTabContent,
+.jxBarBottom a.jxTabActive:active span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Click Focused Tab */
+.jxBarTop a.jxTabPressed:active,
+.jxBarBottom a.jxTabPressed:active {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed:active span.jxTabContent,
+.jxBarBottom a.jxTabPressed:active span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+
+/* Focus tab */
+.jxBarLeft a.jxTab:active,
+.jxBarRight a.jxTab:active {
+  background-position: -96px top; 
+  outline: expression(hideFocus='true');
+}
+
+.jxBarLeft a.jxTab:active span.jxTabContent,
+.jxBarRight a.jxTab:active span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:active,
+.jxBarRight a.jxTabActive:active {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:active span.jxTabContent,
+.jxBarRight a.jxTabActive:active span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Click Focused Tab */
+.jxBarLeft a.jxTabPressed:active,
+.jxBarRight a.jxTabPressed:active {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed:active span.jxTabContent,
+.jxBarRight a.jxTabPressed:active span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Menu Item iicon position.
+   IE 7 seems to want to line up the image by the top of the text of it's 
+   sibling.  This override just nudges it back up. */
+
+img.jxMenuItemIcon {
+  top: 1px;
+}
+
+/* Menu Item focus style */
+a.jxMenuItem:active {
+  background-position: 1px -74px;
+  outline: expression(hideFocus='true');
+}
+
+a.jxMenuItemActive:active {
+  background-position: 1px -170px;
+}

Added: trunk/lib/jxLib/themes/delicious/images/a_pixel.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/a_pixel.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/button.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/button.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/button_combo.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/button_combo.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/button_multi.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/button_multi.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/button_multi_disclose.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/button_multi_disclose.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/dialog_chrome.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/dialog_chrome.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/dialog_resize.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/dialog_resize.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/emblems.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/emblems.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/flyout_chrome.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/flyout_chrome.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/grid.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/grid.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/icons.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/icons.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/listitem.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/listitem.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/loading.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/loading.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/menuitem.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/menuitem.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/notice.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/notice.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/notice_error.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/notice_error.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/notice_success.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/notice_success.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/notice_warning.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/notice_warning.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/panel_controls.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/panel_controls.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/panelbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/panelbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/progressbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/progressbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/spinner_16.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/spinner_16.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/spinner_24.gif
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/spinner_24.gif
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tab_bottom.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tab_bottom.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tab_close.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tab_close.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tab_left.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tab_left.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tab_right.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tab_right.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tab_top.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tab_top.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tabbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tabbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tabbar_bottom.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tabbar_bottom.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tabbar_left.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tabbar_left.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tabbar_right.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tabbar_right.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/table_col.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/table_col.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/table_row.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/table_row.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/toolbar.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/toolbar.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/toolbar_separator_h.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/toolbar_separator_h.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/toolbar_separator_v.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/toolbar_separator_v.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tree.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tree.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tree_hover.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tree_hover.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/images/tree_vert_line.png
===================================================================
(Binary files differ)


Property changes on: trunk/lib/jxLib/themes/delicious/images/tree_vert_line.png
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: trunk/lib/jxLib/themes/delicious/jxtheme.css
===================================================================
--- trunk/lib/jxLib/themes/delicious/jxtheme.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/delicious/jxtheme.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,24 @@
+/*
+ * reset.css - Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License: http://developer.yahoo.net/yui/license.txt
+ *
+ * Copyright (c) 2006-2008, DM Solutions Group Inc.  All rights reserved
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}.jxButtonContainer{display:-moz-inline-box;display:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:2px;border:none;}.jxButton{display:-moz-inline-box;display:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:0 0 0 4px;border:none;background-image:url(images/button.png);background-position:left -24px;background-repeat:no-repeat;text-decoration:none;outline:none;}a.jxButton{cursor:pointer;user-select:none;-moz-user-select:none;-khtml-user-select:none;}ul.jxToolbar .jxButton{background-position:left top;}span.jxButtonContent{display:-moz-inline-box;displ
 ay:inline-block;position:relative;font-size:0;line-height:0;margin:0;padding:4px 4px 4px 0;border:none;background-image:url(images/button.png);background-position:right -24px;background-repeat:no-repeat;}ul.jxToolbar span.jxButtonContent{background-position:right top;}ul.jxToolbar .jxButtonActive,.jxButtonActive{background-position:left -72px;}ul.jxToolbar .jxButtonActive span.jxButtonContent,.jxButtonActive span.jxButtonContent{background-position:right -72px;}ul.jxToolbar .jxButton:focus,.jxButton:focus{background-position:left -96px;}ul.jxToolbar .jxButton:focus span.jxButtonContent,.jxButton:focus span.jxButtonContent{background-position:right -96px;}ul.jxToolbar .jxButtonActive:focus,.jxButtonActive:focus{background-position:left -144px;}ul.jxToolbar .jxButtonActive:focus span.jxButtonContent,.jxButtonActive:focus span.jxButtonContent{background-position:right -144px;}ul.jxToolbar .jxButton:hover,ul.jxToolbar .jxButtonActive:hover,.jxButton:hover,.jxButtonActive:hover{b
 ackground-position:left -48px;}ul.jxToolbar .jxButton:hover span.jxButtonContent,ul.jxToolbar .jxButtonActive:hover span.jxButtonContent,.jxButton:hover span.jxButtonContent,.jxButtonActive:hover span.jxButtonContent{background-position:right -48px;}ul.jxToolbar .jxButtonPressed,ul.jxToolbar .jxButtonPressed:focus,.jxButtonPressed,.jxButtonPressed:focus{background-position:left -120px;}ul.jxToolbar .jxButtonPressed span.jxButtonContent,ul.jxToolbar .jxButtonPressed:focus span.jxButtonContent,.jxButtonPressed span.jxButtonContent,.jxButtonPressed:focus span.jxButtonContent{background-position:right -120px;}.jxDisabled .jxButton,.jxDisabled span.jxButtonContent span{cursor:default;}ul.jxToolbar .jxDisabled .jxButton:focus,ul.jxToolbar .jxDisabled .jxButton:active,ul.jxToolbar .jxDisabled .jxButton:hover,ul.jxToolbar .jxDisabled .jxButtonPressed{background-position:left top;}.jxDisabled .jxButton:focus,.jxDisabled .jxButton:active,.jxDisabled .jxButton:hover,.jxDisabled .jxButt
 onPressed{background-position:left -24px;}ul.jxToolbar .jxDisabled .jxButton:focus span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButton:active span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButton:hover span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButtonPressed span.jxButtonContent{background-position:right top;}.jxDisabled .jxButton:focus span.jxButtonContent,.jxDisabled .jxButton:active span.jxButtonContent,.jxDisabled .jxButton:hover span.jxButtonContent,.jxDisabled .jxButtonPressed span.jxButtonContent{background-position:right -24px;}ul.jxToolbar .jxDisabled .jxButtonActive:focus,ul.jxToolbar .jxDisabled .jxButtonActive:active,ul.jxToolbar .jxDisabled .jxButtonActive:hover,.jxDisabled .jxButtonActive:focus,.jxDisabled .jxButtonActive:active,.jxDisabled .jxButtonActive:hover{background-position:left -72px;}ul.jxToolbar .jxDisabled .jxButtonActive:focus span.jxButtonContent,ul.jxToolbar .jxDisabled .jxButtonActive:active span.jxButtonContent,ul.jxToolbar .jxDisab
 led .jxButtonActive:hover span.jxButtonContent,.jxDisabled .jxButtonActive:focus span.jxButtonContent,.jxDisabled .jxButtonActive:active span.jxButtonContent,.jxDisabled .jxButtonActive:hover span.jxButtonContent{background-position:right -72px;}img.jxButtonIcon{display:-moz-inline-box;display:inline-block;position:relative;vertical-align:middle;width:16px;height:16px;background-position:center center;background-repeat:no-repeat;}span.jxButtonContent span{display:-moz-inline-box;display:inline-block;position:relative;vertical-align:middle;cursor:pointer;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;height:16px;white-space:nowrap;}span.jxButtonContent span.jxButtonLabel{margin:0;padding:0 4px 0 4px;color:#000;font-size:11px;}.jxDiscloser span.jxButtonContent{padding-right:0;}.jxDiscloser span.jxButtonContent span{padding-right:16px;background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}a.jxButtonDisclose{
 position:absolute;display:-moz-inline-box;display:inline-block;padding:4px 0;font-size:0;line-height:0;right:2px;top:2px;background-image:url(images/button_multi_disclose.png);background-position:right 0;background-repeat:no-repeat;outline:none;user-select:none;-moz-user-select:none;-khtml-user-select:none;}a.jxButtonDisclose img{width:16px;height:16px;margin:0;padding:0;border:0;background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}a.jxButtonDisclose:focus,a.jxButtonDisclose:active{background-position:right -96px;}a.jxButtonDisclose:hover{background-position:right -48px;}a.jxButtonDisclosePressed{background-position:right -120px;}.jxDisabled a.jxButtonDisclose,.jxDisabled a.jxButtonDisclose:focus,.jxDisabled a.jxButtonDisclose:active,.jxDisabled a.jxButtonDisclose:hover,.jxDisabled a.jxButtonDisclosePressed{cursor:default;background-position:right 0;}ul.jxToolbar .jxButtonHover{background-position:left -24px!important;}ul.jxTo
 olbar .jxButtonHover span.jxButtonContent{background-position:right -24px!important;}.jxFlyout .jxChrome{background-image:url(images/flyout_chrome.png);padding:5px 5px 7px 6px;}.jxFlyout{position:absolute;display:block;z-index:100;margin:0;padding:0;}.jxFlyoutContent{position:relative;display:block;overflow:auto;margin:6px 6px 8px 7px;background-color:#fff;border:1px solid #999;}.jxButtonMulti,.jxButtonMulti span.jxButtonContent{background-image:url(images/button_multi.png);}.jxButtonEditCombo,.jxButtonEditCombo span.jxButtonContent{background-image:url(images/button_combo.png);}.jxButtonMulti span.jxButtonContent span{padding-right:21px;}.jxButtonEditCombo span.jxButtonContent span{font-size:0;}.jxButtonComboDefault span.jxButtonContent span,.jxButtonComboDefault input{font-style:italic;color:#999;}.jxButtonEditCombo input{float:left;line-height:16px;height:16px;padding:0 4px;margin:0;border:none;font-size:11px;font-family:Arial,Helvetica,sans-serif;background-color:transpa
 rent;}.jxChrome{position:absolute;display:block;font-size:0;line-height:0;z-index:-1;width:100%;height:100%;top:0;left:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxChromeDrag{opacity:.5;-ms-filter:"Alpha(opacity=50)";}.jxChromeTL{position:absolute;overflow:hidden;left:0;top:0;width:50%;height:50%;}.jxChromeTR{position:absolute;overflow:hidden;left:50%;top:0;width:50%;height:50%;}.jxChromeBL{position:absolute;overflow:hidden;left:0;top:50%;width:50%;height:50%;}.jxChromeBR{position:absolute;overflow:hidden;left:50%;top:50%;width:50%;height:50%;}.jxChromeTL img{position:absolute;top:0;left:0;width:1000px;height:600px;}.jxChromeTR img{position:absolute;top:0;right:0;width:1000px;height:600px;}.jxChromeBL img{position:absolute;bottom:0;left:0;width:1000px;height:600px;}.jxChromeBR img{position:absolute;bottom:0;right:0;width:1000px;height:600px;}.jxColorBar{position:relative;overflow:hidden;}table.jxColorGrid{position:relative;border-collapse:collapse;emp
 ty-cells:show;clear:both;padding:0;margin:0;}.jxColorGrid tr{padding:0;margin:0;}.jxColorGrid td{border:1px solid #000;padding:0;margin:0;}.jxColorGrid td.emptyCell{border:0 solid #000;}.jxColorGrid td.emptyCell span{display:block;width:7px;height:7px;line-height:0;font-size:0;border:0 solid #000;padding:1px;margin:0;}.jxColorGrid a.colorSwatch{display:block;width:7px;height:7px;line-height:0;font-size:0;border:0 solid #000;margin:0;padding:1px;}.jxColorGrid a.borderWhite:hover{border:1px solid #fff;padding:0;}.jxColorGrid a.borderBlack:hover{border:1px solid #000;padding:0;}input.jxHexInput{width:55px;vertical-align:middle;}input.jxAlphaInput{width:30px;vertical-align:middle;}div.jxColorPreview{float:left;position:relative;width:20px;height:20px;border:1px solid #000;margin:2px;vertical-align:middle;background-image:url('images/grid.png');overflow:hidden;}.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch{display:block;float:left;width:14px;height:14px;border:1px soli
 d #000;background-image:url('images/grid.png');background-position:0 0;background-repeat:repeat;padding-right:0!important;}.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch span{display:block;width:14px;height:14px;position:absolute;padding-right:0;background:none;}div.jxColorPreview img{position:absolute;z-index:0;}div.jxColorPreview div{width:20px;height:10px;position:absolute;display:block;left:0;z-index:1;font-size:10px;line-height:0;}div.jxColorPreview div.jxColorSelected{top:0;}div.jxColorPreview div.jxColorHover{bottom:0;}label.jxColorLabel,label.jxAlphaLabel{width:auto;font-family:Arial,sans-serif;font-size:11px;line-height:24px;padding:2px;vertical-align:middle;}a.jxColorClose{position:absolute;top:0;right:0;width:16px;height:16px;}a.jxColorClose img{width:16px;height:16px;}.jxClearer{display:block;position:relative;float:none;clear:both;font-size:0;line-height:0;width:0;height:0;margin:0;padding:0;}.jxDisabled{opacity:.4;-ms-filter:"Alpha(opacity=40)";}.jxDi
 sabled *{-ms-filter:"Alpha(opacity=40)";}.jxMask{opacity:.5;-ms-filter:"Alpha(opacity=50)";background-color:#fff;}.jxModalMask{background-color:#000;opacity:.2;-ms-filter:"Alpha(opacity=20)";}.jxEventMask{background-image:url(images/a_pixel.png);}.jxSpinner{position:absolute;opacity:.5;-ms-filter:"Alpha(opacity=50)";z-index:999;background:#fff;}.jxSpinnerMessage{text-align:center;font-weight:bold;}.jxSpinnerSmall .jxSpinnerMessage{margin:0;padding:0;font-size:11px;line-height:24px;}.jxSpinnerLarge .jxSpinnerImage{background:url(images/spinner_24.gif) no-repeat;width:24px;height:24px;margin:0 auto;}.jxSpinnerSmall .jxSpinnerImage{background:url(images/spinner_16.gif) no-repeat;width:16px;height:16px;margin-right:4px;display:inline-block;vertical-align:middle;}.jxConfirmQuestion,.jxPrompt{text-align:center;padding:10px;margin-top:10px;}.jxDialog .jxChrome{background-image:url(images/dialog_chrome.png);}.jxDialog{display:block;z-index:1000;overflow:hidden;visibility:hidden;}.jx
 DialogContentContainer{z-index:1;margin:0 11px 13px 12px;border:1px solid #b7b7b7;background-color:#f0f0f0;}.jxDialogContent{display:block;position:relative;overflow:auto;padding:0;z-index:1;}.jxDialogTitle{display:block;position:relative;background-image:url(images/a_pixel.png);text-align:center;height:24px;line-height:24px;z-index:1;margin:6px 6px 0 7px;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxDialogMin .jxDialogTitle{margin-bottom:8px;}.jxDialogMoveable,.jxDialogMoveable .jxDialogLabel{cursor:move;}.jxDialogIcon{position:absolute;left:2px;top:3px;width:16px;height:16px;border:none;padding:0;margin:0;}.jxDialogLabel{font-family:Arial,Helvetica,sans-serif;font-size:11px;font-weight:bold;line-height:21px;color:#000;white-space:nowrap;cursor:default;}.jxDialogResize{position:absolute;bottom:7px;right:6px;width:16px;height:16px;z-index:2;border:0;cursor:se-resize;background-image:url(images/dialog_resize.png);}.jxDialogControls{position:absolute;top:3
 px;right:2px;height:16px;width:80px;}.jxDialogControls img{background-image:url('images/panel_controls.png');background-repeat:no-repeat;border:0;margin:0;width:16px;height:16px;}.jxDialogClose img{background-position:0 -32px;}.jxDialogMenu img{background-position:0 -48px;}.jxDialogHelp img{background-position:0 -64px;}.jxDialogCollapse img{background-position:0 -16px;}.jxDialogMin .jxDialogCollapse img{background-position:0 0;}.jxDialogMax .jxDialogCollapse img{background-position:0 -16px;}.jxDialogMaximize img{background-position:0 -80px;}.jxDialogMaximized .jxDialogMaximize img{background-position:0 -96px;}.jxDialogLoading img{border:0;margin:0;width:16px;height:16px;visibility:hidden;position:absolute;top:1px;left:2px;}.jxDialogControls .jxButtonContainer,.jxDialogControls .jxButton,.jxDialogControls .jxButton:hover,.jxDialogControls .jxButton:active,.jxDialogControls .jxButtonActive,.jxDialogControls .jxButtonActive:hover,.jxDialogControls .jxButtonActive:active,.jxDial
 ogControls .jxDisabled .jxButton,.jxDialogControls .jxDisabled .jxButton:hover,.jxDialogControls .jxDisabled .jxButton:active{padding:0;margin:0;border:none;background-color:transparent;background-image:none;}.jxDialogControls .jxBarContainer{position:absolute;right:0;background-image:none;background-color:transparent;margin:0;padding:0;border:none;height:16px;}.jxDialogControls .jxBarScroller{left:auto;right:0;}.jxDialogControls ul.jxToolbar{float:right;}.jxDialogControls ul.jxToolbar,.jxDialogControls li.jxToolItem{background-image:none;background-color:transparent;margin:0;padding:0;border:none;}div.jxFileInputs{position:relative;}div.jxFileFake{position:absolute;top:0;left:0;z-index:1;}div.jxFileFake span{float:left;display:block;}div.jxFileFake .jxInputContainer{width:150px;}div.jxFileFake .jxInputText{width:135px;}div.jxFileFake .jxButtonContainer{margin-top:2px;float:left;}.jxInputFile{position:relative;text-align:right;-moz-opacity:0;filter:alpha(opacity:0);opacity:0
 ;z-index:2;margin-top:-5px;height:35px;}.jxForm{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxFieldset{display:block;position:relative;border:1px solid #ccc;margin:10px;padding:5px;}.jxFieldsetLegend,.jxFieldset legend{position:relative;margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;font-size:14px;line-height:26px;color:#000;}.jxInputContainer{display:block;position:relative;padding:0;margin:2px;border:none;}.jxInputLabel,.jxInputTag{display:-moz-inline-box;display:inline-block;margin:0;padding:0;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:26px;color:#000;}.jxInputLabel{vertical-align:top;}.jxInputTag{vertical-align:bottom;}.jxInputWrapper{display:inline-block;white-space:normal;position:relative;}.jxInputText,.jxInputPassword,.jxInputTextarea,.jxInputCombo,.jxInputColor{margin:0 4px;padding:4px;border:1px solid #bbb;width:232px;font-family:Arial,Helvetica,sans-serif;font
 -size:12px;line-height:16px;color:#000;}.jxInputCombo,.jxInputColor{padding:4px 20px 4px 20px;width:200px;}.jxInputIconHidden .jxInputCombo{padding-left:4px;width:216px;}.jxInputIcon{position:absolute;width:16px;height:16px;left:0;top:0;margin:4px 4px 4px 8px;}.jxInputContainerColor .jxInputIcon{border:1px solid #bbb;width:15px;height:15px;}.jxInputIconHidden .jxInputIcon{display:none;}.jxInputRevealer{position:absolute;width:16px;height:16px;right:0;top:0;margin:4px 8px 4px 4px;font-size:0;line-height:0;}img.jxInputRevealerIcon{background-image:url(images/emblems.png);background-position:right -16px;background-repeat:no-repeat;}.jxInputRevealer .jxButtonContainer,.jxInputRevealer .jxButton{padding:0;margin:0;border:0;background-color:transparent;background-image:none;}.jxInputSelect{margin:0 4px;padding:3px 4px 3px 1px;border:1px solid #bbb;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxInputRadio,.jxInputCheck{margin:5px;font-family:A
 rial,Helvetica,sans-serif;font-size:12px;line-height:16px;color:#000;}.jxInputText:focus,.jxInputPassword:focus,.jxInputTextarea:focus,.jxInputSelect:focus,.jxInputCombo:focus,.jxInputColor:focus{border:1px solid #000;}.jxInputContainer .jxButtonContainer{padding:0;margin:0 4px;}.jxInputGroup{border:none;padding:0;margin:2px;}.jxInputGroup legend{font-size:0;line-height:0;padding:0;margin:0;border:none;}.jxInputGroup .jxFieldsetLegend{font-size:12px;}.jxInputGroup .jxInputLabel{width:auto;}.jxFieldError .jxInputText,.jxFieldError .jxInputPassword,.jxFieldError .jxInputTextarea,.jxFieldError .jxInputSelect,.jxFieldError .jxInputCombo,.jxFieldError .jxInputColor{background-color:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;}.jxFieldSuccess .jxInputText,.jxFieldSuccess .jxInputPassword,.jxFieldSuccess .jxInputTextarea,.jxFieldSuccess .jxInputSelect,.jxFieldSuccess .jxInputCombo,.jxFieldSuccess .jxInputColor{background-color:#E6EFC2;color:#264409;border-color:#C6D880;}.jxFieldErro
 r .jxInputText:focus,.jxFieldError .jxInputPassword:focus,.jxFieldError .jxInputTextarea:focus,.jxFieldError .jxInputSelect:focus,.jxFieldError .jxInputCombo:focus,.jxFieldError .jxInputColor:focus{border-color:#8a1f11;}.jxFieldSuccess .jxInputText:focus,.jxFieldSuccess .jxInputPassword:focus,.jxFieldSuccess .jxInputTextarea:focus,.jxFieldSuccess .jxInputSelect:focus,.jxFieldSuccess .jxInputCombo:focus,.jxFieldSuccess .jxInputColor:focus{border-color:#264409;}.jxFieldError .jxInputLabel,.jxFieldError .jxInputTag{color:#8a1f11;}.jxFieldSuccess .jxInputLabel,.jxFieldSuccess .jxInputTag{color:#264409;}.jxFormInline .jxInputContainer,form .jxFormInline .jxInputContainer,form .jxFieldset span.jxFormInline,form.jxForm span.jxFormInline{display:inline;}.jxFormInline .jxInputLabel,form .jxFormInline .jxInputLabel,form span.jxFormInline .jxInputLabel{display:inline;width:auto;}.jxFormInline .jxInputTag,form .jxFormInline .jxInputTag,form span.jxFormInline .jxInputTag{display:inline;}
 .jxFormInline .jxInputGroup,form .jxFormInline .jxInputGroup{padding-left:0;}.jxFormInline .jxInputGroup .jxFieldsetLegend,form .jxFormInline .jxInputGroup .jxFieldsetLegend{position:relative;left:auto;}.jxFormInline .jxInputGroup .jxInputLabel,form .jxFormInline .jxInputGroup .jxInputLabel{display:inline;width:auto;}.jxFormBlock .jxInputContainer,form .jxFormBlock .jxInputContainer,form .jxFieldset span.jxFormBlock,form.jxform span.jxFormBlock{display:block;}.jxFormBlock .jxInputLabel,form .jxFormBlock .jxInputLabel,form span.jxFormBlock .jxInputLabel{display:block;width:auto;margin-left:4px;}.jxFormBlock .jxInputContainerCheck .jxInputLabel,.jxFormBlock .jxInputContainerRadio .jxInputLabel,form .jxFormBlock .jxInputContainerCheck .jxInputLabel,form .jxFormBlock .jxInputContainerRadio .jxInputLabel,form span.jxFormBlock .jxInputContainerCheck .jxInputLabel,form span.jxFormBlock .jxInputContainerRadio .jxInputLabel{display:-moz-inline-box;display:inline-block;}.jxFormBlock .
 jxInputGroup,form .jxFormBlock .jxInputGroup{padding-left:0;}.jxFormBlock .jxInputGroup .jxFieldsetLegend,form .jxFormBlock .jxInputGroup .jxFieldsetLegend{position:relative;left:auto;}.jxFormBlock .jxInputGroup .jxInputLabel,form .jxFormBlock .jxInputGroup .jxInputLabel{display:-moz-inline-box;display:inline-block;width:auto;}.jxFormInlineblock .jxInputContainer,form .jxFormInlineblock .jxInputContainer,form .jxFieldset span.jxFormInlineblock,form.jxForm span.jxFormInlineblock{display:block;white-space:nowrap;}.jxFormInlineblock .jxInputLabel,form .jxFormInlineblock .jxInputLabel,form span.jxFormInlineblock .jxInputLabel{display:-moz-inline-box;display:inline-block;width:200px;}.jxFormInlineblock .jxInputGroup,form .jxFormInlineblock .jxInputGroup{padding-left:200px;}.jxFormInlineblock .jxInputGroup .jxFieldsetLegend,form .jxFormInlineblock .jxInputGroup .jxFieldsetLegend{position:absolute;left:-200px;width:200px;}.jxFormInlineblock .jxInputGroup .jxInputLabel,form .jxFormI
 nlineblock .jxInputGroup .jxInputLabel{display:-moz-inline-box;display:inline-block;width:auto;}.jxGridCellContent .jxInputContainer,.jxGridCellContent .jxInputRadio,.jxGridCellContent .jxInputCheck{display:inline;position:relative;border:none;margin:0;padding:0;font-size:0;line-height:0;color:#000;}.jxGridContainer{position:absolute;top:0;left:0;border-left:0 solid #d8d8d8;border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;overflow:hidden;}.jxGridTable{width:100%;position:relative;table-layout:fixed;border-collapse:collapse;border-style:none;cursor:default;}.jxGridTableBody{position:relative;table-layout:fixed;border-collapse:collapse;border-style:none;cursor:default;}.jxGridCell{border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;overflow:hidden;}.jxGridCellContent{position:relative;display:-moz-inline-box;display:inline-block;overflow:hidden;padding:0 3px;overflow:hidd
 en;vertical-align:middle;font-family:Arial,Verdana,sans-serif;font-size:11px;font-weight:normal;line-height:16px;white-space:nowrap;cursor:cell;text-overflow:ellipsis;}.jxGridColHead .jxGridCellContent{padding:0 3px;text-align:center;font-weight:bold;color:#333;height:100%;}.jxGridRowHead .jxGridCellContent{text-align:center;font-weight:bold;color:#333;}.jxGridColHead{height:100%;border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;background-color:#f2f2f2;background-image:url('images/table_col.png');background-position:0 0;background-repeat:repeat-x;text-align:center;cursor:default;padding:0;white-space:nowrap;overflow:hidden;}.jxGridRowHead{border-top:0 solid #d8d8d8;border-right:1px solid #d8d8d8;border-bottom:1px solid #d8d8d8;border-left:0 solid #d8d8d8;background-color:#f2f2f2;background-image:url('images/table_row.png');background-position:0 0;background-repeat:repeat-y;text-align:center;cursor:default;o
 verflow:hidden;white-space:nowrap;}.jxGridRowAll{background-color:#fff;}.jxGridColumnHeaderSelected{background-color:#e1e1e1;background-position:0 -200px;}.jxGridRowHeaderSelected{background-color:#e1e1e1;background-position:-400px 0;}.jxGridColumnSelected{background-color:#f7f7f7;}.jxGridRowSelected td,.jxGridRowSelected th{background-color:#f7f7f7;}td.jxGridCellSelected,th.jxGridCellSelected{background-color:#ebebeb;}.jxGridColumnHeaderPrelight{background-color:#cee5ff;background-position:0 -300px;}.jxGridRowHeaderPrelight{background-color:#cee5ff;background-position:-600px 0;}.jxGridColumnPrelight{background-color:#e5f1ff;}.jxGridRowPrelight td,.jxGridRowPrelight th{background-color:#e5f1ff;}td.jxGridCellPrelight,th.jxGridCellPrelight{background-color:#cce3ff;}.jxGridHeader .jxColSortable img{vertical-align:top;width:16px;height:16px;background-image:url('images/emblems.png');background-repeat:no-repeat;background-position:right top;}.jxGridHeader .jxColSortable .jxGridCe
 llContent{margin-right:-16px;}.jxGridHeader .jxGridColumnSortedAsc img{background-position:right -162px;}.jxGridHeader .jxGridColumnSortedDesc img{background-position:right -18px;}.jxGridColumnResize,.jxGridRowResize{display:block;position:absolute;background-image:url(images/a_pixel.png);z-index:1;}.jxGridColumnResize{right:0;top:0;height:100%;width:4px;cursor:col-resize;}.jxColSortable .jxGridColumnResize{right:8px;}.jxGridRowResize{left:0;bottom:0;height:4px;width:100%;cursor:row-resize;}.jxGridEditorPopup{min-width:130px;margin:3px;font-size:10px;box-shadow:2px 2px 5px #888;-moz-box-shadow:2px 2px 5px #888;-webkit-box-shadow:2px 2px 5px #888;background-color:#ddd;border:1px solid #999;position:absolute;}.jxGridEditorPopupInnerWrapper{position:relative;height:100%;width:100%;}.jxListView{position:relative;display:block;list-style:none;margin:0;padding:0;}.jxListView .jxListItemContainer{position:relative;display:block;outline:none;overflow:hidden;border:none;margin:0 1px;
 padding:0;}.jxListItem{position:relative;display:block;cursor:pointer;outline:none;overflow:hidden;border:none;margin:0 1px;padding:0;z-index:0;font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#000;text-decoration:none;line-height:20px;height:20px;}.jxListView .jxHover{margin:0;border-left:1px solid #CDDFFD;border-right:1px solid #CDDFFD;background-image:url(images/listitem.png);background-repeat:repeat-x;background-color:#CDE5FF;background-position:left -24px;}.jxListItem:focus{margin:0;border-left:1px dotted #75ADFF;border-right:1px dotted #75ADFF;background-image:url(images/listitem.png);background-repeat:repeat-x;background-position:left -72px;}.jxListView .jxPressed,.jxListView .jxSelected{margin:0;border-left:1px solid #8AABFB;border-right:1px solid #8AABFB;background-color:#CDE5FF;background-image:url(images/listitem.png);background-repeat:repeat-x;background-position:left -48px;}.jxMenuContainer .jxChrome{background-image:url(images/flyout_chrome.png);padd
 ing:5px 5px 7px 6px;}.jxButtonMenu span.jxMenuItemSpan{padding-right:16px;}.jxMenuContainer{position:absolute;top:0;left:-10000px;display:none;z-index:2000;padding:0;}ul.jxMenu{display:block;position:relative;list-style-type:none;padding:1px;margin:6px 6px 8px 7px;background-color:#fff;border:1px solid #999;}li.jxMenuItemContainer{display:block;position:relative;font-size:0;line-height:0;margin:0;padding:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}a.jxMenuItem{display:block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;outline:none;border:1px solid #fff;background-image:url(images/menuitem.png);background-repeat:no-repeat;background-position:left top;font-family:Arial,Helvetica,sans-serif;font-size:11px;text-decoration:none;margin:0;padding:0;color:#000;}a.jxMenuItemActive{background-position:left -98px;}a.jxMenuItem:focus{background-position:left -74px;}a.jxMenuItem:focus span.jxMenuItemContent{border-right:1px dotted #75ADFF;}a
 .jxMenuItemActive:focus{background-position:left -170px;}a.jxMenuItem:hover{background-color:#CDE5FF;background-position:left -26px;}a.jxMenuItem:hover span.jxMenuItemContent{border-right:1px solid #C5E0FF;}a.jxMenuItemActive:hover{background-position:left -122px;}a.jxMenuItemPressed,a.jxMenuItemPressed:hover{background-color:#CDE5FF;background-position:left -50px;}.jxDisabled a.jxMenuItem,.jxDisabled span.jxMenuItemContent span{cursor:default;}.jxDisabled a.jxMenuItem:focus,.jxDisabled a.jxMenuItemPressed,.jxDisabled a.jxMenuItemPressed:hover,.jxDisabled a.jxMenuItem:hover{background-color:#fff;background-position:left top;border-right:1px solid #fff;}.jxDisabled a.jxMenuItem:hover span.jxMenuItemContent{border-right:1px solid #fff;}span.jxMenuItemContent{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:0;line-height:0;white-space:nowrap;padding:0 20px 0 0;margin:0;border-right:1px solid #fff;}.jxButtonSubMenu span.jxMenuItemContent,.jxButton
 SubMenu:hover span.jxMenuItemContent{background-image:url(images/emblems.png);background-position:right -30px;background-repeat:no-repeat;}img.jxMenuItemIcon{position:absolute;top:2px;left:2px;width:16px;height:16px;background-position:left center;background-repeat:no-repeat;}span.jxMenuItemContent span{display:block;position:relative;cursor:pointer;margin:0;padding:2px 0 2px 22px;font-size:16px;line-height:16px;color:#000;}span.jxMenuItemContent span.jxMenuItemLabel{color:#000;font-size:11px;}.jxMenuItemToggle img.jxMenuItemIcon,.jxMenuItemToggleSet img.jxMenuItemIcon{background-image:url(images/emblems.png);background-position:2px 0;background-repeat:no-repeat;}.jxMenuItemToggle a.jxMenuItemActive img.jxMenuItemIcon{background-position:2px -48px;}.jxMenuItemToggleSet a.jxMenuItemActive img.jxMenuItemIcon{background-position:2px -64px;}ul.jxMenu span.jxMenuSeparator{display:block;font-size:10px;line-height:10px;background-image:url(images/toolbar_separator_v.png);background
 -repeat:repeat-x;background-position:left center;}@CHARSET "ISO-8859-1";.jxMessage{text-align:center;padding:10px;margin-top:10px;}.jxNoticeListContainer{border:none;padding:0;margin:0;font-size:0;line-height:0;z-index:500;}.jxNoticeList{position:relative;}.jxNoticeItemContainer{position:relative;overflow:hidden;}.jxNoticeItemContainer .jxChrome{background-image:url(images/flyout_chrome.png);padding:5px 5px 7px 6px;}.jxHasChrome .jxNoticeItem{margin:6px 6px 8px 7px;}.jxNoticeItem{position:relative;border:2px solid #ccc;margin:0;padding:0;background-color:#f8f8f8;background-image:url(images/notice.png);background-repeat:repeat-x;background-position:left bottom;}.jxNoticeIcon{position:absolute;top:0;left:0;margin:6px;width:16px;height:16px;background-image:url(images/icons.png);background-repeat:no-repeat;background-position:0 0;}.jxNotice{display:block;position:relative;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:18px;color:#666;margin:0;border:none;padd
 ing:6px 26px 6px 10px;}.jxNoticeError .jxNoticeItem{background-color:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;background-image:url(images/notice_error.png);}.jxNoticeWarning .jxNoticeItem{background-color:#FFF6BF;color:#514721;border-color:#FFD324;background-image:url(images/notice_warning.png);}.jxNoticeSuccess .jxNoticeItem{background-color:#E6EFC2;color:#264409;border-color:#C6D880;background-image:url(images/notice_success.png);}.jxNoticeInformation .jxNoticeItem{background-color:#F8F8F8;color:#666;border-color:#CCC;background-image:url(images/notice.png);}.jxNoticeError .jxNotice{color:#8a1f11;padding-left:28px;}.jxNoticeWarning .jxNotice{color:#514721;padding-left:28px;}.jxNoticeSuccess .jxNotice{color:#264409;padding-left:28px;}.jxNoticeInformation .jxNotice{color:#666;padding-left:28px;}.jxNoticeError .jxNoticeIcon{background-position:0 -32px;}.jxNoticeWarning .jxNoticeIcon{background-position:0 -16px;}.jxNoticeSuccess .jxNoticeIcon{background-position:0 -48px;}.jx
 NoticeInformation .jxNoticeIcon{background-position:0 -64px;}.jxNoticeClose{position:absolute;top:0;right:0;margin:6px;width:16px;height:16px;background-image:url(images/tab_close.png);background-position:0 0;background-repeat:no-repeat;}.jxPanel{display:block;position:relative;}.jxPanelContentContainer{overflow:hidden;background-color:#f0f0f0;}.jxPanelContent{position:relative;display:block;overflow:auto;background-color:#fff;margin:0;padding:0;}.jxPanelTitle{display:block;position:relative;background-image:url(images/panelbar.png);background-repeat:repeat-x;background-position:left top;height:22px;margin:0;padding:0;text-align:center;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxPanelBar{position:absolute;line-height:1px;width:100%;height:5px;cursor:row-resize;background-color:#f0f0f0;z-index:2;}.jxPanelIcon{position:absolute;left:2px;top:3px;width:16px;height:16px;border:none;padding:0;margin:0;}.jxPanelLabel{padding-left:25px;font-family:Arial,Helvet
 ica,sans-serif;font-size:11px;font-weight:bold;line-height:21px;color:#000;white-space:nowrap;}.jxPanelControls{position:absolute;top:3px;right:2px;height:16px;width:80px;overflow:hidden;}.jxPanelControls img{background-image:url('images/panel_controls.png');background-repeat:no-repeat;border:0;margin:0;width:16px;height:16px;}.jxPanelClose img{background-position:0 -32px;}.jxPanelMenu img{background-position:0 -48px;}.jxPanelHelp img{background-position:0 -64px;}.jxPanelCollapse img{background-position:0 -16px;}.jxPanelMin .jxPanelCollapse img{background-position:0 0;}.jxPanelMax .jxPanelCollapse img{background-position:0 -16px;}.jxPanelMaximize img{background-position:0 0;}.jxPanelLoading img{border:0;margin:0;width:16px;height:16px;visibility:hidden;position:absolute;top:1px;left:2px;}.jxPanelControls .jxButtonContainer,.jxPanelControls .jxButton,.jxPanelControls .jxButton:hover,.jxPanelControls .jxButton:active,.jxPanelControls .jxButtonActive,.jxPanelControls .jxButtonA
 ctive:hover,.jxPanelControls .jxButtonActive:active,.jxPanelControls .jxDisabled .jxButton,.jxPanelControls .jxDisabled .jxButton:hover,.jxPanelControls .jxDisabled .jxButton:active{padding:0;margin:0;border:none;background-color:transparent;background-image:none;}.jxPanelControls div.jxBarTop{position:absolute;right:0;background-image:none;background-color:transparent;margin:0;padding:0;border:none;height:16px;}.jxPanelControls .jxBarScroller{left:auto;right:0;}.jxPanelControls ul.jxToolbar{float:right;}.jxPanelControls ul.jxToolbar,.jxPanelControls li.jxToolItem{background-image:none;background-color:transparent;margin:0;padding:0;border:none;}.jxProgressBar-container{position:relative;display:block;width:100%;}.jxProgressBar-message{position:relative;display:block;color:black;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:20px;}.jxProgressBar{position:relative;display:block;width:100%;height:20px;border:none;margin:0;padding:0;}.jxProgressBar-outline{po
 sition:absolute;display:block;top:0;left:0;z-index:10;height:20px;border-left:1px solid #cbc8c8;border-right:1px solid #cbc8c8;background-image:url(images/progressbar.png);background-position:0 -140px;width:100%;}.jxProgressBar-fill{position:absolute;display:block;top:0;left:0;z-index:20;height:20px;border:none;background-image:url(images/progressbar.png);background-position:0 0;}.jxProgressStarting .jxProgressBar-fill{border:none;}.jxProgressWorking .jxProgressBar-fill{border-left:1px solid #49afe8;}.jxProgressFinished .jxProgressBar-fill{border-left:1px solid #49afe8;border-right:1px solid #49afe8;}.jxProgressBar-text{position:absolute;display:block;overflow:visible;top:0;left:1px;width:auto;height:20px;z-index:30;border:none;margin:0;padding:0 0 0 4px;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:20px;white-space:nowrap;}.jxHasVerticalScrollbar,.jxHasHorizontalScrollbar{position:relative;overflow:hidden;}.jxScrollbarChildWrapper{overflow:hidden;}.jxHas
 VerticalScrollbar{padding-right:25px;}.jxHasVerticalScrollbar .jxScrollbarContainer{position:absolute;right:0;top:0;width:20px;height:100%;border:none;border-left:1px solid black;}.jxHasVerticalScrollbar .jxScrollLeft,.jxHasVerticalScrollbar .jxScrollRight{width:20px;height:20px;display:block;background-color:blue;}.jxHasVerticalScrollbar .jxSliderContainer{height:100%;width:20px;border:none;}.jxHasHorizontalScrollbar{padding-bottom:25px;}.jxHasHorizontalScrollbar .jxScrollbarContainer{position:absolute;bottom:0;left:0;height:20px;width:100%;border:none;border-top:1px solid black;}.jxHasHorizontalScrollbar .jxScrollLeft,.jxHasHorizontalScrollbar .jxScrollRight{width:20px;height:20px;display:block;background-color:blue;}.jxHasHorizontalScrollbar .jxScrollLeft{float:left;}.jxHasHorizontalScrollbar .jxScrollRight{float:right;}.jxHasHorizontalScrollbar .jxSlider{float:left;}.jxHasHorizontalScrollbar .jxSliderContainer{height:20px;width:100%;border:none;}.jxHasVerticalScrollbar .
 jxSliderKnob,.jxHasHorizontalScrollbar .jxSliderKnob{width:20px;height:20px;background-color:black;cursor:pointer;}@CHARSET "ISO-8859-1";.jxSliderContainer{width:100%;height:10px;border:1px solid black;}.jxSliderKnob{width:10px;height:10px;background-color:black;cursor:pointer;}.jxSplitterMask{position:absolute;top:0;left:0;width:100%;height:100%;overflow:hidden;background-image:url(images/a_pixel.png);z-index:1;}.jxSplitBarHorizontal{display:block;position:absolute;font-size:0;line-height:0;margin:0;padding:0;border:none;width:5px;height:100%;cursor:col-resize;background-color:#f0f0f0;z-index:2;}.jxSplitBarVertical{display:block;position:absolute;font-size:0;line-height:0;margin:0;padding:0;border:none;width:100%;height:5px;cursor:row-resize;background-color:#f0f0f0;z-index:2;}.jxSplitContainer{display:block;position:relative;margin:0;padding:0;border:none;overflow:hidden;}.jxSplitArea{display:block;position:absolute;margin:0;padding:0;border:none;z-index:0;}.jxSplitBarDrag
 {background-color:#eee;}.jxSnapHorizontalBefore{width:5px;height:5px;position:absolute;top:0;left:0;background-color:#aaa;}.jxSnapHorizontalAfter{width:5px;height:5px;position:absolute;top:0;left:0;background-color:#aaa;}.jxTabSetContainer{position:relative;display:block;overflow:hidden;width:200px;height:200px;margin:0;padding:0;background-color:#fff;}.jxTabSetContainer .jxBarContainer{z-index:auto;}.tabContent{display:none;position:relative;width:100%;height:100%;overflow:auto;}.tabContentActive{display:block;}span.jxTabContainer{display:block;position:relative;margin:0;padding:2px;border:none;}a.jxTab{display:-moz-inline-box;display:inline-block;position:relative;cursor:pointer;user-select:none;-moz-user-select:none;-khtml-user-select:none;margin:0;padding:0;border:none;background-repeat:no-repeat;text-decoration:none;outline:none;}span.jxTabContent{display:-moz-inline-box;display:inline-block;font-size:0;line-height:0;margin:0;padding:0;border:none;background-repeat:no-r
 epeat;}img.jxTabIcon{display:-moz-inline-box;display:inline-block;position:relative;width:16px;height:16px;background-position:center center;background-repeat:no-repeat;}span.jxTabLabel{display:-moz-inline-box;display:inline-block;position:relative;cursor:pointer;margin:0;padding:0;color:#000;font-family:Arial,Helvetica,sans-serif;font-size:11px;line-height:16px;}a.jxTabClose{display:block;position:absolute;cursor:pointer;outline:none;user-select:none;-moz-user-select:none;-khtml-user-select:none;width:16px;height:16px;background-image:url(images/tab_close.png);background-position:0 0;background-repeat:no-repeat;}.jxDisabled a.jxTab,.jxDisabled span.jxTabContent span,.jxDisabled a.jxTabClose{cursor:default;}.jxTabBarTop .jxBarWrapper,.jxTabBarBottom .jxBarWrapper{padding-left:2px;}.jxBarTop span.jxTabContainer,.jxBarBottom span.jxTabContainer{margin-right:-1px;padding:2px 0;}.jxBarTop a.jxTab,.jxBarTop span.jxTabContent,.jxTabBarTop .jxBarControls a.jxButton,.jxTabBarTop .jx
 BarControls span.jxButtonContent{background-image:url(images/tab_top.png);}.jxBarBottom a.jxTab,.jxBarBottom span.jxTabContent,.jxTabBarBottom .jxBarControls a.jxButton,.jxTabBarBottom .jxBarControls span.jxButtonContent{background-image:url(images/tab_bottom.png);}.jxBarTop a.jxTabClose,.jxBarBottom a.jxTabClose{top:5px;right:5px;}.jxBarTop .jxTabClose span.jxTabContent,.jxBarBottom .jxTabClose span.jxTabContent{padding-right:16px;}.jxBarTop a.jxTab,.jxBarBottom a.jxTab{padding-left:4px;background-position:left -24px;}.jxBarTop span.jxTabContent,.jxBarBottom span.jxTabContent{padding:4px 4px 4px 0;background-position:right -24px;}.jxBarTop a.jxTabActive,.jxBarBottom a.jxTabActive{background-position:left -72px;}.jxBarTop a.jxTabActive span.jxTabContent,.jxBarBottom a.jxTabActive span.jxTabContent{background-position:right -72px;}.jxBarTop a.jxTab:focus,.jxBarBottom a.jxTab:focus{background-position:left -96px;}.jxBarTop a.jxTab:focus span.jxTabContent,.jxBarBottom a.jxTab:f
 ocus span.jxTabContent{background-position:right -96px;}.jxBarTop a.jxTabActive:focus,.jxBarBottom a.jxTabActive:focus{background-position:left -144px;}.jxBarTop a.jxTabActive:focus span.jxTabContent,.jxBarBottom a.jxTabActive:focus span.jxTabContent{background-position:right -144px;}.jxBarTop a.jxTab:hover,.jxBarTop a.jxTabActive:hover,.jxBarBottom a.jxTab:hover,.jxBarBottom a.jxTabActive:hover{background-position:left -48px;}.jxBarTop a.jxTab:hover span.jxTabContent,.jxBarTop a.jxTabActive:hover span.jxTabContent,.jxBarBottom a.jxTab:hover span.jxTabContent,.jxBarBottom a.jxTabActive:hover span.jxTabContent{background-position:right -48px;}.jxBarTop a.jxTabPressed,.jxBarTop a.jxTabPressed:focus,.jxBarBottom a.jxTabPressed,.jxBarBottom a.jxTabPressed:focus{background-position:left -120px;}.jxBarTop a.jxTabPressed span.jxTabContent,.jxBarTop a.jxTabPressed:focus span.jxTabContent,.jxBarBottom a.jxTabPressed span.jxTabContent,.jxBarBottom a.jxTabPressed:focus span.jxTabConten
 t{background-position:right -120px;}.jxBarTop .jxDisabled a.jxTab:focus,.jxBarTop .jxDisabled a.jxTab:active,.jxBarTop .jxDisabled a.jxTab:hover,.jxBarTop .jxDisabled a.jxTabPressed,.jxBarBottom .jxDisabled a.jxTab:focus,.jxBarBottom .jxDisabled a.jxTab:active,.jxBarBottom .jxDisabled a.jxTab:hover,.jxBarBottom .jxDisabled a.jxTabPressed{background-position:left -24px;}.jxBarTop .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarTop .jxDisabled a.jxTab:active span.jxTabContent,.jxBarTop .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarTop .jxDisabled a.jxTabPressed span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:active span.jxTabContent,.jxBarBottom .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabPressed span.jxTabContent{background-position:right -24px;}.jxBarTop .jxDisabled a.jxTabActive:focus,.jxBarTop .jxDisabled a.jxTabActive:active,.jxBarTop .jxDisabled a.jxTabActive:hover,.jxBarBot
 tom .jxDisabled a.jxTabActive:focus,.jxBarBottom .jxDisabled a.jxTabActive:active,.jxBarBottom .jxDisabled a.jxTabActive:hover{background-position:left -72px;}.jxBarTop .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarTop .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarTop .jxDisabled a.jxTabActive:hover span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarBottom .jxDisabled a.jxTabActive:hover span.jxTabContent{background-position:right -72px;}.jxBarTop img.jxTabIcon,.jxBarBottom img.jxTabIcon{vertical-align:middle;}.jxBarTop span.jxTabLabel,.jxBarBottom span.jxTabLabel{vertical-align:middle;height:16px;padding:0 4px 0 4px;}.jxTabBarLeft .jxBarWrapper,.jxTabBarRight .jxBarWrapper{padding-top:2px;}.jxBarLeft span.jxTabContainer,.jxBarRight span.jxTabContainer{margin-bottom:-1px;padding:0 2px;}.jxBarLeft a.jxTab,.jxBarLeft span.jxTabContent{background-image:url(im
 ages/tab_left.png);}.jxBarRight a.jxTab,.jxBarRight span.jxTabContent{background-image:url(images/tab_right.png);}.jxBarLeft a.jxTabClose,.jxBarRight a.jxTabClose{top:5px;left:5px;}.jxBarLeft .jxTabClose span.jxTabContent,.jxBarRight .jxTabClose span.jxTabContent{padding-top:16px;}.jxBarLeft a.jxTab,.jxBarRight a.jxTab{padding-top:4px;background-position:-24px top;}.jxBarLeft span.jxTabContent,.jxBarRight span.jxTabContent{padding:0 4px 4px 4px;background-position:-24px bottom;}.jxBarLeft a.jxTabActive,.jxBarRight a.jxTabActive{background-position:-72px top;}.jxBarLeft a.jxTabActive span.jxTabContent,.jxBarRight a.jxTabActive span.jxTabContent{background-position:-72px bottom;}.jxBarLeft a.jxTab:focus,.jxBarRight a.jxTab:focus{background-position:-96px top;}.jxBarLeft a.jxTab:focus span.jxTabContent,.jxBarRight a.jxTab:focus span.jxTabContent{background-position:-96px bottom;}.jxBarLeft a.jxTabActive:focus,.jxBarRight a.jxTabActive:focus{background-position:-144px top;}.jxBa
 rLeft a.jxTabActive:focus span.jxTabContent,.jxBarRight a.jxTabActive:focus span.jxTabContent{background-position:-144px bottom;}.jxBarLeft a.jxTab:hover,.jxBarLeft a.jxTabActive:hover,.jxBarRight a.jxTab:hover,.jxBarRight a.jxTabActive:hover{background-position:-48px top;}.jxBarLeft a.jxTab:hover span.jxTabContent,.jxBarLeft a.jxTabActive:hover span.jxTabContent,.jxBarRight a.jxTab:hover span.jxTabContent,.jxBarRight a.jxTabActive:hover span.jxTabContent{background-position:-48px bottom;}.jxBarLeft a.jxTabPressed,.jxBarLeft a.jxTabPressed:focus,.jxBarRight a.jxTabPressed,.jxBarRight a.jxTabPressed:focus{background-position:-120px top;}.jxBarLeft a.jxTabPressed span.jxTabContent,.jxBarLeft a.jxTabPressed:focus span.jxTabContent,.jxBarRight a.jxTabPressed span.jxTabContent,.jxBarRight a.jxTabPressed:focus span.jxTabContent{background-position:-120px bottom;}.jxBarLeft .jxDisabled a.jxTab:focus,.jxBarLeft .jxDisabled a.jxTab:active,.jxBarLeft .jxDisabled a.jxTab:hover,.jxBarLe
 ft .jxDisabled a.jxTabPressed,.jxBarRight .jxDisabled a.jxTab:focus,.jxBarRight .jxDisabled a.jxTab:active,.jxBarRight .jxDisabled a.jxTab:hover,.jxBarRight .jxDisabled a.jxTabPressed{background-position:-24px top;}.jxBarLeft .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarLeft .jxDisabled a.jxTab:active span.jxTabContent,.jxBarLeft .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabPressed span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:focus span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:active span.jxTabContent,.jxBarRight .jxDisabled a.jxTab:hover span.jxTabContent,.jxBarRight .jxDisabled a.jxTabPressed span.jxTabContent{background-position:-24px bottom;}.jxBarLeft .jxDisabled a.jxTabActive:focus,.jxBarLeft .jxDisabled a.jxTabActive:active,.jxBarLeft .jxDisabled a.jxTabActive:hover,.jxBarRight .jxDisabled a.jxTabActive:focus,.jxBarRight .jxDisabled a.jxTabActive:active,.jxBarRight .jxDisabled a.jxTabActive:hover{background-position:-72px top
 ;}.jxBarLeft .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarLeft .jxDisabled a.jxTabActive:hover span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:focus span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:active span.jxTabContent,.jxBarRight .jxDisabled a.jxTabActive:hover span.jxTabContent{background-position:-72px bottom;}.jxBarLeft span.jxTabLabel,.jxBarRight span.jxTabLabel{padding:4px 0 4px 0;}.jxBarContainer{display:block;position:relative;z-index:1;overflow:hidden;margin:0;padding:0;border:0;background-color:#f0f0f0;}.jxBarTop,.jxBarBottom{width:100%;height:28px;background-image:url(images/toolbar.png);background-repeat:repeat-x;background-position:0 0;overflow:hidden;}.jxTabBox .jxTabBarTop{background-image:url(images/tabbar.png);background-position:0 bottom;}.jxTabBox .jxTabBarBottom{background-image:url(images/tabbar_bottom.png);background-position:0 top;}.jxBarLeft,.jxBarRight{width:a
 uto;height:100%;background-image:url(images/toolbar.png);background-repeat:repeat-x;background-position:0 0;float:left;overflow:hidden;}.jxTabBox .jxTabBarLeft{background-image:url(images/tabbar_left.png);background-repeat:repeat-y;background-position:right 0;}.jxTabBox .jxTabBarRight{background-image:url(images/tabbar_right.png);background-repeat:repeat-y;background-position:left 0;}.jxBarTop .jxBarScroller,.jxBarBottom .jxBarScroller{float:left;height:28px;overflow:hidden;z-index:0;}.jxBarTop .jxBarScroller .jxBarWrapper,.jxBarBottom .jxBarScroller .jxBarWrapper{float:left;height:28px;overflow:hidden;width:10000%;}.jxBarTop .jxBarControls .jxButtonContainer,.jxBarBottom .jxBarControls .jxButtonContainer{z-index:1;padding:2px 0;margin-left:-1px;}.jxBarTop .jxBarScrollLeft img.jxButtonIcon,.jxBarBottom .jxBarScrollLeft img.jxButtonIcon{background-image:url(images/emblems.png);background-position:0 -80px;}.jxBarTop .jxBarScrollRight img.jxButtonIcon,.jxBarBottom .jxBarScrollR
 ight img.jxButtonIcon{background-image:url(images/emblems.png);background-position:0 -96px;}.jxBarControls{float:right;position:relative;font-size:0;line-height:0;}ul.jxToolbar,ul.jxTabBar{display:block;position:relative;float:left;clear:none;list-style-type:none;margin:0;padding:0;border:none;}li.jxToolItem{display:block;position:relative;float:left;font-size:0;line-height:0;white-space:nowrap;padding:0;margin:0;border:none;}li.jxToolItem .jxInputWrapper{white-space:nowrap;}li.jxToolItem span.jxBarSeparator{display:block;position:relative;float:left;font-size:0;line-height:0;border:0;margin:0;padding:4px;background-repeat:no-repeat;background-position:center center;}.jxBarTop li.jxToolItem span.jxBarSeparator,.jxBarBottom li.jxToolItem span.jxBarSeparator{width:8px;height:20px;background-image:url(images/toolbar_separator_h.png);}.jxBarLeft li.jxToolItem span.jxBarSeparator,.jxBarRight li.jxToolItem span.jxBarSeparator{width:20px;height:8px;background-image:url(images/toolb
 ar_separator_v.png);}.jxBarLeft ul.jxToolbar,.jxBarLeft ul.jxTabBar,.jxBarLeft li.jxToolItem,.jxBarRight ul.jxToolbar,.jxBarRight ul.jxTabBar,.jxBarRight li.jxToolItem{clear:both;}.jxToolbarAlignLeft ul{float:left;}.jxToolbarAlignRight ul{float:right;}.jxToolbarAlignCenter{text-align:center;}.jxToolbarAlignCenter ul{float:none;}.jxToolbarAlignCenter ul li{float:none;display:inline;}.jxTooltip{width:auto;height:auto;background-color:black;color:white;padding:5px;z-index:65536;}.jxTree,.jxTreeRoot{position:relative;display:block;list-style:none;margin:0;padding:0;}.jxTreeNest{list-style:none;margin:0;padding:0;background-repeat:repeat-y;background-position:left top;}li.jxTreeContainer{position:relative;display:block;margin:0;padding:0;background-repeat:no-repeat;background-position:left top;white-space:nowrap;font-size:0;line-height:0;user-select:none;-moz-user-select:none;-khtml-user-select:none;}.jxTree li.jxTreeContainer{margin-left:16px;}a.jxTreeItem{position:relative;disp
 lay:block;cursor:pointer;outline:none;overflow:hidden;background-image:url(images/tree_hover.png);background-repeat:repeat-x;background-position:left top;border:none;margin:0 1px 0 17px;padding:0 0 0 20px;z-index:0;font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#000;text-decoration:none;line-height:20px;height:20px;}a.jxTreeItem:focus{border-left:1px dotted #75ADFF;border-right:1px dotted #75ADFF;margin:0 0 0 16px;background-position:left -72px;}a.jxTreeItem:hover,li.jxTreeContainer a.jxHover{border-left:1px solid #CDDFFD;border-right:1px solid #CDDFFD;margin:0 0 0 16px;background-color:#CDE5FF;background-position:left -24px;}li.jxTreeContainer a.jxSelected,li.jxTreeContainer a.jxSelected:hover,li.jxTreeContainer a.jxPressed,li.jxTreeContainer a.jxPressed:hover{border-left:1px solid #8AABFB;border-right:1px solid #8AABFB;margin:0 0 0 16px;background-color:#CDE5FF;background-position:left -48px;}li.jxDisabled a.jxTreeItem{cursor:default;}li.jxDisabled a.jxTreeIte
 m:focus,li.jxDisabled a.jxTreeItem:hover{background-position:left top;background-color:transparent;border:none;margin:0 1px 0 17px;}.jxTreeNest{background-image:url(images/tree_vert_line.png);}img.jxTreeImage,img.jxTreeIcon{position:absolute;display:inline;left:0;top:0;width:16px;height:20px;z-index:1;background-image:url(images/tree.png);background-repeat:no-repeat;border:0;margin:0;}img.jxTreeIcon{height:16px;top:2px;left:1px;}.jxTreeBranchOpen .jxTreeIcon,.jxTreeBranchLastOpen .jxTreeIcon{background-position:left -40px;}.jxTreeBranchOpen .jxTreeImage{background-position:left -100px;}.jxTreeBranchLastOpen .jxTreeImage{background-position:left -160px;}.jxTreeBranchClosed .jxTreeIcon,.jxTreeBranchLastClosed .jxTreeIcon{background-position:left -20px;}.jxTreeBranchClosed .jxTreeImage{background-position:left -80px;}.jxTreeBranchLastClosed .jxTreeImage{background-position:left -140px;}.jxTreeLeaf .jxTreeIcon,.jxTreeLeafLast .jxTreeIcon{background-position:left 0;}.jxTreeLeaf .
 jxTreeImage{background-position:left -60px;}.jxTreeLeafLast .jxTreeImage{background-position:left -120px;}a.jxTreeItem,img.jxTreeImage,img.jxTreeIcon,span.jxTreeLabel,.jxTreeItemContainer input{vertical-align:middle;}img.jxTreeImage.jxBusy{background-image:url(images/spinner_16.gif);background-position:left top;}.jxFileUploadPanel{padding:5px;}.jxUploadQueue li{display:block;position:relative;overflow:hidden;padding:2px;}.jxUploadQueue div span{display:block;}.jxUploadQueue li span.jxUploadFileName{font-size:12px;line-height:16px;padding-left:2px;}.jxUploadQueue li span.jxUploadFileDelete,.jxUploadQueue li span.jxUploadFileProgress,.jxUploadQueue li span.jxUploadFileComplete,.jxUploadQueue li span.jxUploadFileError{position:absolute;top:2px;right:2px;width:16px;height:16px;background-repeat:no-repeat;cursor:pointer;}.jxUploadQueue li span.jxUploadFileDelete{background-image:url('images/icons.png');background-position:0 -128px;}.jxUploadQueue li span.jxUploadFileProgress{back
 ground-image:url('images/spinner_16.gif');background-position:top left;}.jxUploadQueue li span.jxUploadFileComplete{background-image:url('images/icons.png');background-position:0 -48px;}.jxUploadQueue li span.jxUploadFileError{background-image:url('images/icons.png');background-position:0 -32px;}.jxUploadFileErrorTip{padding:4px 4px 4px 20px;border:2px solid #ddd;background:url("images/icons.png") no-repeat 0 -32px;color:black;width:100px;}
\ No newline at end of file

Added: trunk/lib/jxLib/themes/delicious/jxtheme.uncompressed.css
===================================================================
--- trunk/lib/jxLib/themes/delicious/jxtheme.uncompressed.css	                        (rev 0)
+++ trunk/lib/jxLib/themes/delicious/jxtheme.uncompressed.css	2011-04-15 18:46:31 UTC (rev 2371)
@@ -0,0 +1,3662 @@
+/*
+Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+Code licensed under the BSD License:
+http://developer.yahoo.net/yui/license.txt
+version: 0.11.0
+*/
+body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}
+table{border-collapse:collapse;border-spacing:0;}
+fieldset,img{border:0;}
+address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}
+ol,ul {list-style:none;}
+caption,th {text-align:left;}
+h1,h2,h3,h4,h5,h6{font-size:100%;}
+q:before,q:after{content:'';}/**
+ * @project         Jx
+ * @revision        $Id: button.css 593 2009-11-09 20:29:54Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* BUTTON STYLES */
+/* ============= */
+/* jxButtons consist of an A, containing a SPAN, which contains an image.
+   Buttons can use the sliding door technique with background images to horizontally
+   accomodate icons with labels. */
+
+.jxButtonContainer {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+  /* float: left; */
+
+  margin: 0px;
+  padding: 2px;
+  border: none;
+}
+
+/* normal button */
+.jxButton {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+  /* float: left; */
+
+  /* Using background images, the A contains the left side of the background */
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 0px 0px 0px 4px; /* makes room for the left of the button bg */
+  border: none;
+  background-image: url(images/button.png);
+  background-position: left -24px;
+  background-repeat: no-repeat;
+  text-decoration: none;
+  outline: none;
+}
+
+/* normal button */
+a.jxButton {
+  /* Base setup */
+  cursor: pointer;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+ul.jxToolbar .jxButton {
+  background-position: left top;
+}
+
+span.jxButtonContent {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  /* float: left; */
+  font-size: 0px;
+  line-height: 0px;
+
+  /* Using background images, the SPAN contains the right side of the background */
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 4px 4px 4px 0px; /* makes room for the right of the button bg */
+  border: none;
+  background-image: url(images/button.png);
+  background-position: right -24px;
+  background-repeat: no-repeat;
+}
+
+ul.jxToolbar span.jxButtonContent {
+  background-position: right top;
+}
+
+/* active button */
+ul.jxToolbar .jxButtonActive,
+.jxButtonActive {
+  background-position: left -72px;
+}
+
+ul.jxToolbar .jxButtonActive span.jxButtonContent,
+.jxButtonActive span.jxButtonContent {
+  background-position: right -72px;
+}
+
+/* focus button */
+ul.jxToolbar .jxButton:focus,
+.jxButton:focus {
+  background-position: left -96px;
+}
+
+ul.jxToolbar .jxButton:focus span.jxButtonContent,
+.jxButton:focus span.jxButtonContent {
+  background-position: right -96px;
+}
+
+/* focus active button */
+ul.jxToolbar .jxButtonActive:focus,
+.jxButtonActive:focus {
+  background-position: left -144px;
+}
+
+ul.jxToolbar .jxButtonActive:focus span.jxButtonContent,
+.jxButtonActive:focus span.jxButtonContent {
+  background-position: right -144px;
+}
+
+/* hover normal and active button */
+ul.jxToolbar .jxButton:hover,
+ul.jxToolbar .jxButtonActive:hover,
+.jxButton:hover,
+.jxButtonActive:hover {
+  background-position: left -48px;
+}
+
+ul.jxToolbar .jxButton:hover span.jxButtonContent,
+ul.jxToolbar .jxButtonActive:hover span.jxButtonContent,
+.jxButton:hover span.jxButtonContent,
+.jxButtonActive:hover span.jxButtonContent {
+  background-position: right -48px;
+}
+
+/* clicking normal and focused button */
+ul.jxToolbar .jxButtonPressed,
+ul.jxToolbar .jxButtonPressed:focus,
+.jxButtonPressed,
+.jxButtonPressed:focus {
+  background-position: left -120px;
+}
+
+ul.jxToolbar .jxButtonPressed span.jxButtonContent,
+ul.jxToolbar .jxButtonPressed:focus span.jxButtonContent,
+.jxButtonPressed span.jxButtonContent,
+.jxButtonPressed:focus span.jxButtonContent {
+  background-position: right -120px;
+}
+
+/* disabled buttons */
+.jxDisabled .jxButton,
+.jxDisabled span.jxButtonContent span {
+ cursor: default; 
+}
+
+/* hover, focus and pressing disabled button */
+ul.jxToolbar .jxDisabled .jxButton:focus,
+ul.jxToolbar .jxDisabled .jxButton:active,
+ul.jxToolbar .jxDisabled .jxButton:hover,
+ul.jxToolbar .jxDisabled .jxButtonPressed {
+  background-position: left top;
+}
+
+.jxDisabled .jxButton:focus,
+.jxDisabled .jxButton:active,
+.jxDisabled .jxButton:hover,
+.jxDisabled .jxButtonPressed {
+  background-position: left -24px;
+}
+
+ul.jxToolbar .jxDisabled .jxButton:focus span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButton:active span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButton:hover span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonPressed span.jxButtonContent {
+  background-position: right top;
+}
+  
+.jxDisabled .jxButton:focus span.jxButtonContent,
+.jxDisabled .jxButton:active span.jxButtonContent,
+.jxDisabled .jxButton:hover span.jxButtonContent,
+.jxDisabled .jxButtonPressed span.jxButtonContent {
+  background-position: right -24px;
+}
+
+/* hover and focus disabled active button */
+ul.jxToolbar .jxDisabled .jxButtonActive:focus,
+ul.jxToolbar .jxDisabled .jxButtonActive:active,
+ul.jxToolbar .jxDisabled .jxButtonActive:hover,
+.jxDisabled .jxButtonActive:focus,
+.jxDisabled .jxButtonActive:active,
+.jxDisabled .jxButtonActive:hover {
+  background-position: left -72px;
+}
+
+ul.jxToolbar .jxDisabled .jxButtonActive:focus span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonActive:active span.jxButtonContent,
+ul.jxToolbar .jxDisabled .jxButtonActive:hover span.jxButtonContent,
+.jxDisabled .jxButtonActive:focus span.jxButtonContent,
+.jxDisabled .jxButtonActive:active span.jxButtonContent,
+.jxDisabled .jxButtonActive:hover span.jxButtonContent {
+  background-position: right -72px;
+}
+
+img.jxButtonIcon {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  vertical-align: middle;
+  /* float: left; */
+
+  width: 16px;
+  height: 16px;
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+span.jxButtonContent span {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  vertical-align: middle;
+  /* float: left; */
+  cursor: pointer;
+
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  line-height: 16px;
+  height: 16px;
+  white-space: nowrap;
+}
+
+span.jxButtonContent span.jxButtonLabel {
+  margin: 0px;
+  padding: 0 4px 0 4px;
+  color: #000;
+  font-size: 11px;
+}
+
+/* ========================== */
+/* JX BUTTON EXTENSION STYLES */
+/* ========================== */
+
+.jxDiscloser span.jxButtonContent {
+  padding-right: 0px;
+}
+
+.jxDiscloser span.jxButtonContent span {
+  padding-right: 16px;
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+a.jxButtonDisclose {
+  position: absolute;
+  display: -moz-inline-box;
+  display: inline-block;
+  padding: 4px 0px;
+  font-size: 0px;
+  line-height: 0px;
+  right: 2px;
+  top: 2px;
+  background-image: url(images/button_multi_disclose.png);
+  background-position: right 0px;
+  background-repeat: no-repeat;
+  outline: none;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+a.jxButtonDisclose img {
+  width: 16px;
+  height: 16px;
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+a.jxButtonDisclose:focus,
+a.jxButtonDisclose:active {
+  background-position: right -96px;
+}
+
+a.jxButtonDisclose:hover {
+  background-position: right -48px;
+}
+
+a.jxButtonDisclosePressed {
+  background-position: right -120px;
+}
+
+.jxDisabled a.jxButtonDisclose,
+.jxDisabled a.jxButtonDisclose:focus, 
+.jxDisabled a.jxButtonDisclose:active, 
+.jxDisabled a.jxButtonDisclose:hover, 
+.jxDisabled a.jxButtonDisclosePressed {
+  cursor: default; 
+  background-position: right 0px;
+}
+
+/* note, jxButtonHover is set by Multi button JS */
+ul.jxToolbar .jxButtonHover {
+  background-position: left -24px !important;
+}
+
+ul.jxToolbar .jxButtonHover span.jxButtonContent {
+  background-position: right -24px !important;
+}
+
+
+/* Jx Flyout Styles */
+
+.jxFlyout .jxChrome {
+  background-image: url(images/flyout_chrome.png);
+  padding: 5px 5px 7px 6px;
+}
+
+.jxFlyout {
+  /* Base setup */
+  position: absolute;
+  display: block;
+  z-index: 100;
+
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxFlyoutContent {
+  position: relative;
+  display: block;
+  overflow: auto;
+  margin: 6px 6px 8px 7px;
+  background-color: #fff;
+  border: 1px solid #999;
+}
+
+/* Jx Combo and Multi Button Styles */
+
+.jxButtonMulti,
+.jxButtonMulti span.jxButtonContent {
+  background-image: url(images/button_multi.png);
+}
+
+.jxButtonEditCombo,
+.jxButtonEditCombo span.jxButtonContent {
+  background-image: url(images/button_combo.png);
+}
+
+/*a.jxButtonEditCombo {
+  user-select: text;
+  -moz-user-select: text;
+  -khtml-user-select: text;
+}*/
+
+.jxButtonMulti span.jxButtonContent span {
+  padding-right: 21px;
+}
+
+.jxButtonEditCombo span.jxButtonContent span {
+  font-size: 0px;
+}
+
+.jxButtonComboDefault span.jxButtonContent span,
+.jxButtonComboDefault input {
+  font-style: italic;
+  color: #999;
+}
+
+.jxButtonEditCombo input {
+  float: left;
+  line-height: 16px;
+  height: 16px;
+  padding: 0px 4px;
+  margin: 0px;
+  border: none;
+  font-size: 11px;
+  font-family: Arial, Helvetica, sans-serif;
+  background-color: transparent;
+}/**
+ * @project         Jx
+ * @revision        $Id: chrome.css 454 2009-06-03 14:50:22Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* CHROME STYLES */
+/* ============= */
+/* Chrome uses four absolutely positioned DIVs containing an image for each of 
+   four quadrants.  The chrome image is used as a sprite map. */
+
+.jxChrome {
+  /* Base setup */
+  position:absolute;
+  display: block;
+  font-size: 0px;
+  line-height: 0px;
+  z-index: -1;
+  width: 100%;
+  height: 100%;
+  top: 0px;
+  left: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxChromeDrag {
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+}
+
+.jxChromeTL { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 0%; 
+  top: 0%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeTR { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 50%; 
+  top: 0%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeBL { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 0%; 
+  top: 50%; 
+  width: 50%; 
+  height: 50%; 
+}
+
+.jxChromeBR { 
+  position: absolute; 
+  overflow: hidden; 
+  left: 50%; 
+  top: 50%;  
+  width: 50%; 
+  height: 50%; 
+}
+
+
+.jxChromeTL img { 
+  position: absolute; 
+  top: 0px; 
+  left: 0px;
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeTR img { 
+  position: absolute; 
+  top: 0px; 
+  right: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeBL img { 
+  position: absolute; 
+  bottom: 0px; 
+  left: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+.jxChromeBR img { 
+  position: absolute; 
+  bottom: 0px; 
+  right: 0px; 
+  width: 1000px;
+  height: 600px;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: color.css 423 2009-05-12 12:37:56Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =================== */
+/* COLOR PICKER STYLES */
+/* =================== */
+
+/*.jxColorPicker {
+    position: absolute;
+    display: none;
+    top: 100%;
+    width: 212px;
+    left: 0px;
+    border: 1px solid #000;
+    padding: 2px;
+    background-color: #eee;
+}*/
+
+.jxColorBar {
+    position: relative;
+    overflow: hidden;
+}
+
+table.jxColorGrid {
+    position: relative;
+    border-collapse: collapse;
+    empty-cells: show;
+    clear:both;
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid tr {
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid td {
+    border: 1px solid #000;
+    padding: 0px;
+    margin: 0px;
+}
+
+.jxColorGrid td.emptyCell {
+    border: 0px solid #000;
+}
+
+.jxColorGrid td.emptyCell span {
+    display: block;
+    width: 7px;
+    height: 7px;
+    line-height: 0px;
+    font-size: 0px;
+    border: 0px solid #000;
+    padding: 1px;
+    margin: 0px;
+}
+
+.jxColorGrid a.colorSwatch {
+    display: block;
+    width: 7px;
+    height: 7px;
+    line-height: 0px;
+    font-size: 0px;
+    border: 0px solid #000;
+    margin: 0px;
+    padding: 1px;
+}
+
+.jxColorGrid a.borderWhite:hover {
+    border: 1px solid #fff;
+    padding: 0px;
+}
+
+.jxColorGrid a.borderBlack:hover {
+    border: 1px solid #000;
+    padding: 0px;
+}
+
+input.jxHexInput {
+    width: 55px;
+    vertical-align: middle;
+}
+
+input.jxAlphaInput {
+    width: 30px;
+    vertical-align: middle;
+}
+
+div.jxColorPreview {
+    float: left;
+    position: relative;
+    width: 20px;
+    height: 20px;
+    border: 1px solid #000;
+    margin: 2px;
+    vertical-align: middle;
+    background-image: url('images/grid.png');
+    overflow: hidden;
+}
+
+.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch {
+    display: block;
+    float: left;
+    width: 14px;
+    height: 14px;
+    border: 1px solid #000;
+    background-image: url('images/grid.png');
+    background-position: 0px 0px;
+    background-repeat: repeat;
+    padding-right: 0px !important;
+}
+
+.jxButtonFlyout span.jxButtonContent span.jxButtonSwatch span {
+    display: block;
+    width: 14px;
+    height: 14px;
+    position: absolute;
+    padding-right: 0px;
+    background: none;
+}
+
+div.jxColorPreview img {
+    position: absolute;
+    z-index: 0;
+}
+
+div.jxColorPreview div {
+    width: 20px;
+    height: 10px;
+    position: absolute;
+    display: block;
+    left: 0px;
+    z-index: 1;
+    font-size: 10px;
+    line-height: 0px;
+}
+
+div.jxColorPreview div.jxColorSelected {
+    top: 0px;
+}
+
+div.jxColorPreview div.jxColorHover {
+    bottom: 0px;
+}
+
+label.jxColorLabel,
+label.jxAlphaLabel {
+    width: auto;
+    font-family: Arial, sans-serif;
+    font-size: 11px;
+    line-height: 24px;
+    padding: 2px;
+    vertical-align: middle;
+}
+
+a.jxColorClose {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    width: 16px;
+    height: 16px;
+}
+
+a.jxColorClose img {
+    width: 16px;
+    height: 16px;
+}/**
+ * @project         Jx
+ * @revision        $Id: common.css 736 2010-03-05 16:04:56Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* COMMON STYLES */
+/* ============= */
+
+.jxClearer {
+  display: block;
+  position: relative;
+  float: none;
+  clear: both;
+  font-size: 0;
+  line-height: 0;
+  width: 0;
+  height: 0;
+  margin: 0;
+  padding: 0;
+}
+
+.jxDisabled {
+  opacity: 0.4;
+  -ms-filter: "Alpha(opacity=40)";
+}
+
+.jxDisabled * {
+  -ms-filter: "Alpha(opacity=40)";
+}
+
+/* ============= */
+/*  MASK STYLES  */
+/* ============= */
+
+.jxMask {
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+  background-color: #fff;
+}
+
+.jxModalMask {
+  background-color: #000;
+  opacity: 0.2;
+  -ms-filter: "Alpha(opacity=20)";
+}
+
+.jxEventMask {
+  background-image: url(images/a_pixel.png);
+}
+
+
+.jxSpinner {
+  position: absolute;
+  opacity: 0.5;
+  -ms-filter: "Alpha(opacity=50)";
+  z-index: 999;
+  background: #fff;
+}
+
+/* .jxSpinnerContent { } */
+
+.jxSpinnerMessage {
+  text-align: center;
+  font-weight: bold;
+}
+
+.jxSpinnerSmall .jxSpinnerMessage {
+  margin: 0px;
+  padding: 0px;
+  font-size: 11px;
+  line-height: 24px;
+}
+
+.jxSpinnerLarge .jxSpinnerImage {
+  background: url(images/spinner_24.gif) no-repeat;
+  width: 24px;
+  height: 24px;
+  margin: 0 auto;
+}
+
+.jxSpinnerSmall .jxSpinnerImage {
+  background: url(images/spinner_16.gif) no-repeat;
+  width: 16px;
+  height: 16px;
+  margin-right: 4px;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+/*iframe.jxIframeShim { }*/
+
+/**
+ * Confirm and Prompt dialog classes
+ */
+.jxConfirmQuestion, .jxPrompt {
+    text-align: center;
+    padding: 10px;
+    margin-top: 10px;
+}/**
+ * @project         Jx
+ * @revision        $Id: dialog.css 732 2010-03-05 14:38:36Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* DIALOG STYLES */
+/* ============= */
+
+.jxDialog .jxChrome {
+  background-image: url(images/dialog_chrome.png);
+}
+
+.jxDialog {
+  /* Base setup */
+  display: block;
+  z-index: 1000;
+  overflow: hidden;
+  
+  /* initial state is hidden */
+  visibility: hidden;
+}
+
+.jxDialogContentContainer {
+  z-index: 1;
+  margin: 0px 11px 13px 12px;
+  border: 1px solid #b7b7b7;
+  background-color: #f0f0f0;
+}
+
+.jxDialogContent {
+  /* Base setup */
+  display: block;
+  position:relative;
+  overflow: auto;
+
+  padding: 0px;
+  z-index: 1;
+}
+
+.jxDialogTitle {
+   /* Base setup */
+  display: block;
+  position: relative;
+
+  /* this makes the dialog draggable by the title bar in IE
+   * Without it, only the label is draggable
+   */
+  background-image: url(images/a_pixel.png);
+
+  text-align: center;
+  height: 24px;
+  line-height: 24px;
+  z-index: 1;
+
+  margin: 6px 6px 0px 7px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxDialogMin .jxDialogTitle {
+  margin-bottom: 8px;
+}
+
+.jxDialogMoveable,
+.jxDialogMoveable .jxDialogLabel {
+  cursor: move;
+}
+
+.jxDialogIcon {
+  position: absolute;
+  left: 2px;
+  top: 3px;
+  width: 16px;
+  height: 16px;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+.jxDialogLabel {
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  font-weight: bold;
+  /* line-height vertically aligns the label in the containing div. */
+  line-height:21px;
+  color: #000;
+  white-space: nowrap;
+  cursor: default;
+}
+
+.jxDialogResize {
+  /* Base setup */
+  position: absolute;
+
+  bottom: 7px;
+  right: 6px;
+  width: 16px;
+  height: 16px;
+  z-index: 2;
+  border: 0px;
+  cursor: se-resize;
+  background-image: url(images/dialog_resize.png);
+}
+
+.jxDialogControls {
+  position: absolute;
+  top: 3px;
+  right: 2px;
+  height: 16px;
+  width: 80px;
+}
+
+.jxDialogControls img {
+  background-image: url('images/panel_controls.png');
+  background-repeat: no-repeat;
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+}
+
+.jxDialogClose img {
+  background-position: 0px -32px;
+}
+
+.jxDialogMenu img {
+  background-position: 0px -48px;
+}
+
+.jxDialogHelp img {
+  background-position: 0px -64px;
+}
+
+.jxDialogCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxDialogMin .jxDialogCollapse img {
+  background-position: 0px 0px;
+}
+
+.jxDialogMax .jxDialogCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxDialogMaximize img {
+  background-position: 0px -80px;
+}
+
+.jxDialogMaximized .jxDialogMaximize img {
+  background-position: 0px -96px;
+}
+
+.jxDialogLoading img {
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+  visibility:hidden;
+  position: absolute;
+  top: 1px;
+  left: 2px;
+}
+
+
+/* ========================= */
+/* JX BUTTON STYLES OVERIDES */
+/* ========================= */
+
+.jxDialogControls .jxButtonContainer,
+.jxDialogControls .jxButton,
+.jxDialogControls .jxButton:hover,
+.jxDialogControls .jxButton:active ,
+.jxDialogControls .jxButtonActive,
+.jxDialogControls .jxButtonActive:hover,
+.jxDialogControls .jxButtonActive:active,
+.jxDialogControls .jxDisabled .jxButton,
+.jxDialogControls .jxDisabled .jxButton:hover,
+.jxDialogControls .jxDisabled .jxButton:active {
+  padding: 0px;
+  margin: 0px;
+  border: none;
+  background-color: transparent;
+  background-image: none;
+}
+
+
+/* ========================== */
+/* JX TOOLBAR STYLES OVERIDES */
+/* ========================== */
+
+/* Multiple toolbars can be housed in  the toolbar container the container will expand vertically to accomodate wrapped toolbars */
+
+.jxDialogControls .jxBarContainer {
+  /* Base setup */
+  position: absolute;
+  right: 0px;
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  height: 16px;
+}
+
+.jxDialogControls .jxBarScroller {
+  left: auto;
+  right: 0px;
+}
+
+.jxDialogControls ul.jxToolbar {
+  float: right;
+}
+
+.jxDialogControls ul.jxToolbar,
+.jxDialogControls li.jxToolItem {
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+}/**
+ * File Input classes
+ */
+div.jxFileInputs {
+    position: relative;
+}
+
+div.jxFileFake {
+    position: absolute;
+    top: 0px;
+    left: 0px;
+    z-index: 1;
+}
+
+div.jxFileFake span {
+    float: left;
+    display: block;
+}
+
+div.jxFileFake .jxInputContainer {
+    width: 150px;
+}
+
+div.jxFileFake .jxInputText {
+    width: 135px;
+} 
+
+div.jxFileFake .jxButtonContainer {
+    margin-top: 2px;
+    float: left;
+}
+
+.jxInputFile {
+    position: relative;
+    text-align: right;
+    -moz-opacity: 0;
+    filter: alpha(opacity:0);
+    opacity: 0;
+    z-index: 2;
+    margin-top: -5px;
+    height: 35px;
+}/**
+ * @project         Jx
+ * @revision        $Id: form.css 827 2010-04-01 14:22:49Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* FORM STYLES */
+/* =========== */
+/* JxForm classes are a set of styles that can be used for laying out forms 
+ * There are three different types of layouts: Inline, Inlineblock and Block.
+ * Each can be used to layout an entire form, a fieldset or an individual input.
+ */
+
+/* debuggiong styles */
+/*
+.jxForm           { background-color: yellow; }
+.jxFieldset       { background-color: khaki; }
+.jxInputGroup     { background-color: tan; }
+.jxFieldsetLegend { background-color: orange; }
+.jxInputContainer { background-color: pink; }
+.jxInputLabel     { background-color: plum; }
+.jxInputTag       { background-color: lime; }
+*/
+
+ /* Base and Typography Styles */
+ 
+.jxForm {
+  display: block;
+  position: relative;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxFieldset {
+  display: block;
+  position: relative;
+  border: 1px solid #ccc;
+  margin: 10px;
+  padding: 5px;
+}
+
+.jxFieldsetLegend,
+.jxFieldset legend {
+  position: relative;
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 14px;
+  line-height: 26px;
+  color: #000;
+}
+
+.jxInputContainer {
+  display: block;
+  position: relative;
+  padding: 0px;
+  margin: 2px;
+  border: none;
+}
+
+.jxInputLabel,
+.jxInputTag {
+  display: -moz-inline-box;
+  display: inline-block;
+  margin: 0px;
+  padding: 0px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 26px;
+  color: #000;
+}
+
+.jxInputLabel {
+  vertical-align: top;
+}
+
+.jxInputTag {
+  vertical-align: bottom;
+}
+
+.jxInputWrapper {
+  display: inline-block;
+  white-space: normal;
+  position: relative;
+}
+
+.jxInputText,
+.jxInputPassword,
+.jxInputTextarea,
+.jxInputCombo,
+.jxInputColor {
+  margin: 0px 4px;
+  padding: 4px;
+  border: 1px solid #bbb;
+  /* overall width is 250px, margins+padding+border is 18px */
+  width: 232px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputCombo,
+.jxInputColor {
+  padding: 4px 20px 4px 20px;
+  /* overall width is 250px, margins+padding+border is 50px */
+  width: 200px;
+}
+
+.jxInputIconHidden .jxInputCombo {
+  /* overall width is 250px, margins+padding+border is 34px */
+  padding-left: 4px;
+  width: 216px;
+}
+
+.jxInputIcon {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  left: 0px;
+  top: 0px;
+  margin: 4px 4px 4px 8px;
+}
+
+.jxInputContainerColor .jxInputIcon {
+  border: 1px solid #bbb;
+  width: 15px;
+  height: 15px;
+}
+
+.jxInputIconHidden .jxInputIcon {
+  display: none;
+}
+
+.jxInputRevealer {
+  position: absolute;
+  width: 16px;
+  height: 16px;
+  right: 0px;
+  top: 0px;
+  margin: 4px 8px 4px 4px;
+  font-size: 0px;
+  line-height: 0px;
+}
+
+img.jxInputRevealerIcon {
+  background-image: url(images/emblems.png);
+  background-position: right -16px;
+  background-repeat: no-repeat;
+}
+
+.jxInputRevealer .jxButtonContainer,
+.jxInputRevealer .jxButton {
+  padding: 0px;
+  margin: 0px;
+  border: 0px;
+  background-color: transparent;
+  background-image: none;
+}
+
+.jxInputSelect {
+  margin: 0px 4px;
+  padding: 3px 4px 3px 1px;
+  border: 1px solid #bbb;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputRadio,
+.jxInputCheck {
+  margin: 5px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 12px;
+  line-height: 16px;
+  color: #000;
+}
+
+.jxInputText:focus,
+.jxInputPassword:focus,
+.jxInputTextarea:focus,
+.jxInputSelect:focus,
+.jxInputCombo:focus,
+.jxInputColor:focus {
+  border: 1px solid #000;
+}
+
+.jxInputContainer .jxButtonContainer {
+  padding: 0px;
+  margin: 0px 4px;
+}
+
+/* Input Group */
+
+.jxInputGroup {
+  border: none;
+  padding: 0px;
+  margin: 2px;
+}
+
+.jxInputGroup legend {
+  font-size: 0px;
+  line-height: 0px;
+  padding: 0px;
+  margin: 0px;
+  border: none;
+}
+
+.jxInputGroup .jxFieldsetLegend {
+  font-size: 12px;
+}
+
+.jxInputGroup .jxInputLabel {
+  width: auto;
+}
+
+/* Field Validation */
+
+.jxFieldError .jxInputText,
+.jxFieldError .jxInputPassword,
+.jxFieldError .jxInputTextarea,
+.jxFieldError .jxInputSelect,
+.jxFieldError .jxInputCombo,
+.jxFieldError .jxInputColor {
+  background-color: #FBE3E4; 
+  color: #8a1f11; 
+  border-color: #FBC2C4;
+}
+
+.jxFieldSuccess .jxInputText,
+.jxFieldSuccess .jxInputPassword,
+.jxFieldSuccess .jxInputTextarea,
+.jxFieldSuccess .jxInputSelect,
+.jxFieldSuccess .jxInputCombo,
+.jxFieldSuccess .jxInputColor {
+  background-color: #E6EFC2; 
+  color: #264409; 
+  border-color: #C6D880;
+}
+
+.jxFieldError .jxInputText:focus,
+.jxFieldError .jxInputPassword:focus,
+.jxFieldError .jxInputTextarea:focus,
+.jxFieldError .jxInputSelect:focus,
+.jxFieldError .jxInputCombo:focus,
+.jxFieldError .jxInputColor:focus {
+  border-color: #8a1f11;
+}
+
+.jxFieldSuccess .jxInputText:focus,
+.jxFieldSuccess .jxInputPassword:focus,
+.jxFieldSuccess .jxInputTextarea:focus,
+.jxFieldSuccess .jxInputSelect:focus,
+.jxFieldSuccess .jxInputCombo:focus,
+.jxFieldSuccess .jxInputColor:focus {
+  border-color: #264409;
+}
+
+.jxFieldError .jxInputLabel,
+.jxFieldError .jxInputTag {
+  color: #8a1f11; 
+}
+
+.jxFieldSuccess .jxInputLabel,
+.jxFieldSuccess .jxInputTag {
+  color: #264409; 
+}
+
+
+/* For Reference
+   Success, notice and error boxes from Blueprint */
+
+/* 
+.error      { background: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; }
+.notice     { background: #FFF6BF; color: #514721; border-color: #FFD324; }
+.success    { background: #E6EFC2; color: #264409; border-color: #C6D880; }
+.error a    { color: #8a1f11; }
+.notice a   { color: #514721; }
+.success a  { color: #264409; }
+*/
+
+
+
+/* INLINE FORM 
+ * Sets up form elements to work as inline objects like they do by default
+ * These styles rely on increasing specificity to provide overrides */
+
+/* Inline Input Container */
+.jxFormInline .jxInputContainer,
+form .jxFormInline .jxInputContainer,
+form .jxFieldset span.jxFormInline,
+form.jxForm span.jxFormInline {
+  display: inline;
+}
+
+/* Inline Label */
+.jxFormInline .jxInputLabel,
+form .jxFormInline .jxInputLabel,
+form span.jxFormInline .jxInputLabel {
+  display: inline;
+  width: auto;
+}
+
+/* Inline Tag */
+.jxFormInline .jxInputTag,
+form .jxFormInline .jxInputTag,
+form span.jxFormInline .jxInputTag {
+  display: inline;
+}
+
+/* Inline Input Group */
+.jxFormInline .jxInputGroup,
+form .jxFormInline .jxInputGroup {
+  padding-left: 0px;
+}
+
+.jxFormInline .jxInputGroup .jxFieldsetLegend,
+form .jxFormInline .jxInputGroup .jxFieldsetLegend {
+  position: relative;
+  left: auto;
+}
+
+.jxFormInline .jxInputGroup .jxInputLabel,
+form .jxFormInline .jxInputGroup .jxInputLabel {
+  display: inline;
+  width: auto;
+}
+
+
+/* BLOCK FORM 
+ *  Sets up form elements to work as block objects so they can appear stacked */
+
+/* Block Input Container */
+.jxFormBlock .jxInputContainer,
+form .jxFormBlock .jxInputContainer,
+form .jxFieldset span.jxFormBlock,
+form.jxform span.jxFormBlock {
+  display: block;
+}
+ 
+/* Block Label */
+.jxFormBlock .jxInputLabel,
+form .jxFormBlock .jxInputLabel,
+form span.jxFormBlock .jxInputLabel {
+  display: block;
+  width: auto;
+  margin-left: 4px;
+}
+
+/* Checks and Radios Label
+ * Most inputs are preceeded by their labels, but in the case of radio buttons
+ * and checkboxes, the inputs are followed by their labels */
+.jxFormBlock .jxInputContainerCheck .jxInputLabel,
+.jxFormBlock .jxInputContainerRadio .jxInputLabel,
+form .jxFormBlock .jxInputContainerCheck .jxInputLabel,
+form .jxFormBlock .jxInputContainerRadio .jxInputLabel,
+form span.jxFormBlock .jxInputContainerCheck .jxInputLabel,
+form span.jxFormBlock .jxInputContainerRadio .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+}
+
+/* Block Input Group */
+.jxFormBlock .jxInputGroup,
+form .jxFormBlock .jxInputGroup {
+  padding-left: 0px;
+}
+
+.jxFormBlock .jxInputGroup .jxFieldsetLegend,
+form .jxFormBlock .jxInputGroup .jxFieldsetLegend {
+  position: relative;
+  left: auto;
+}
+
+.jxFormBlock .jxInputGroup .jxInputLabel,
+form .jxFormBlock .jxInputGroup .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: auto;
+}
+
+
+/* INLINE-BLOCK FORM 
+ * Sets up form elements to work as inline-block objects so labels can have set 
+ * widths to simulate a 2 column display for label / input pairs. */
+
+/* Inline-Block Input Container */
+.jxFormInlineblock .jxInputContainer,
+form .jxFormInlineblock .jxInputContainer,
+form .jxFieldset span.jxFormInlineblock,
+form.jxForm span.jxFormInlineblock {
+  display: block;
+  white-space: nowrap;
+}
+
+/* Inline-Block Label */
+.jxFormInlineblock .jxInputLabel,
+form .jxFormInlineblock .jxInputLabel,
+form span.jxFormInlineblock .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: 200px;
+}
+
+/* Inline-Block Input Group */
+.jxFormInlineblock .jxInputGroup,
+form .jxFormInlineblock .jxInputGroup {
+  padding-left: 200px;
+}
+
+.jxFormInlineblock .jxInputGroup .jxFieldsetLegend,
+form .jxFormInlineblock .jxInputGroup .jxFieldsetLegend {
+  position: absolute;
+  left: -200px; /* for ie? */
+  width: 200px;
+}
+
+.jxFormInlineblock .jxInputGroup .jxInputLabel,
+form .jxFormInlineblock .jxInputGroup .jxInputLabel {
+  display: -moz-inline-box;
+  display: inline-block;
+  width: auto;
+}
+
+/** Jx.Grid Overrides **/
+
+.jxGridCellContent .jxInputContainer,
+.jxGridCellContent .jxInputRadio,
+.jxGridCellContent .jxInputCheck {
+  display: inline;
+  position: relative;
+  border: none;
+  margin: 0px;
+  padding: 0px;
+  font-size: 0px;
+  line-height: 0px;
+  color: #000;
+}/**
+ * @project         Jx
+ * @revision        $Id: grid.css 796 2010-03-26 19:56:43Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============= */
+/* GRID STYLES */
+/* ============= */
+
+.jxGridContainer {
+    position: absolute;
+    top: 0;
+    left: 0;
+    border-left: 0px solid #d8d8d8;
+    border-top: 0px solid #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    overflow: hidden;
+}
+
+.jxGridTable {
+    width: 100%;
+    position: relative;
+    table-layout: fixed;
+    border-collapse: collapse;
+    border-style: none;
+
+    cursor: default;
+}
+
+.jxGridTableBody {
+    position: relative;
+    table-layout: fixed;
+    border-collapse: collapse;
+    border-style: none;
+   /* width: 100%;*/
+    cursor: default;
+}
+
+.jxGridCell {
+    border-top: 0px solid #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid #d8d8d8;
+    overflow: hidden;
+}
+
+.jxGridCellContent {
+    position: relative;
+    display: -moz-inline-box;
+    display: inline-block;
+    overflow: hidden;
+    padding: 0px 3px;
+    overflow: hidden;
+    vertical-align: middle;
+    
+    font-family: Arial, Verdana, sans-serif;
+    font-size: 11px;
+    font-weight: normal;
+    line-height: 16px;
+    
+    /* can change this to normal */
+    white-space: nowrap;
+    cursor: cell;
+    /* only applies in IE and Safari right now */
+    text-overflow: ellipsis;
+}
+
+.jxGridColHead .jxGridCellContent {
+    padding: 0px 3px;
+    text-align: center;
+    font-weight: bold;
+    color: #333;
+    height: 100%;
+}
+
+.jxGridRowHead .jxGridCellContent {
+  text-align: center;
+  font-weight: bold;
+  color: #333;
+}
+/* Normal Styles */
+
+.jxGridColHead {
+    height: 100%;
+  
+    border-top: 0px solid  #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid  #d8d8d8;
+    background-color: #f2f2f2;
+    background-image: url('images/table_col.png');
+    background-position: 0px 0px;
+    background-repeat: repeat-x;
+
+    text-align: center;
+    cursor: default;
+    padding: 0px;
+    white-space: nowrap;
+    
+    overflow: hidden;
+}
+
+.jxGridRowHead {
+    border-top: 0px solid  #d8d8d8;
+    border-right: 1px solid #d8d8d8;
+    border-bottom: 1px solid #d8d8d8;
+    border-left: 0px solid  #d8d8d8;
+    background-color:  #f2f2f2;
+    background-image: url('images/table_row.png');
+    background-position: 0px 0px;
+    background-repeat: repeat-y;
+
+    text-align: center;
+    cursor: default;
+    overflow: hidden;
+    white-space: nowrap;
+}
+
+/* Alternating Row Styles */
+
+.jxGridRowAll {
+    background-color: #fff;
+}
+
+.jxGridRowOdd {}
+.jxGridRowEven {}
+.jxGridRowOdd td {}
+.jxGridRowEven td {}
+
+/* Selected Styles */
+
+.jxGridColumnHeaderSelected {
+    background-color: #e1e1e1;
+    background-position: 0px -200px;
+}
+
+.jxGridRowHeaderSelected {
+    background-color: #e1e1e1;
+    background-position: -400px 0px;
+}
+
+.jxGridColumnSelected {
+    background-color: #f7f7f7;
+}
+
+.jxGridRowSelected td,
+.jxGridRowSelected th {
+    background-color: #f7f7f7;
+}
+
+td.jxGridCellSelected,
+th.jxGridCellSelected {
+    background-color: #ebebeb;
+}
+
+/* Prelight Styles */
+
+.jxGridColumnHeaderPrelight {
+    background-color: #cee5ff;
+    background-position: 0px -300px;
+}
+
+.jxGridRowHeaderPrelight {
+    background-color: #cee5ff;
+    background-position: -600px 0px;
+}
+
+.jxGridColumnPrelight {
+    background-color: #e5f1ff;
+}
+
+.jxGridRowPrelight td,
+.jxGridRowPrelight th {
+    background-color: #e5f1ff;
+}
+
+td.jxGridCellPrelight,
+th.jxGridCellPrelight {
+  background-color: #cce3ff;
+}
+
+.jxGridHeader .jxColSortable img {
+    vertical-align: top;
+    width: 16px;
+    height: 16px;
+    background-image: url('images/emblems.png');
+    background-repeat: no-repeat;
+    background-position: right top;
+}
+
+.jxGridHeader .jxColSortable .jxGridCellContent {
+  margin-right: -16px; /* recenter the column heading text */
+}
+ 
+.jxGridHeader .jxGridColumnSortedAsc img {
+    background-position: right -162px;
+}
+ 
+.jxGridHeader .jxGridColumnSortedDesc img {
+    background-position: right -18px;
+}
+ 
+.jxGridColumnResize,
+.jxGridRowResize {
+    display: block;
+    position: absolute;
+    background-image: url(images/a_pixel.png);
+    z-index: 1;
+    
+}
+.jxGridColumnResize {
+    right: 0px;
+    top: 0px;
+    height: 100%;
+    width: 4px;
+    cursor: col-resize;
+}
+
+.jxColSortable .jxGridColumnResize {
+  right: 8px;
+}
+
+.jxGridRowResize {
+    left: 0px;
+    bottom: 0px;
+    height: 4px;
+    width: 100%;
+    cursor: row-resize;
+}
+
+/* Editor Styles */
+.jxGridEditorPopup {
+  min-width: 130px;
+  margin: 3px;
+  font-size: 10px;
+  box-shadow: 2px 2px 5px #888; /* i really like the boxshadow */
+  -moz-box-shadow : 2px 2px 5px #888;
+  -webkit-box-shadow : 2px 2px 5px #888;
+  /*
+  border-radius: 5px;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  */
+  background-color: #dddddd;
+  border: 1px solid #999999;
+  position: absolute;
+}
+
+.jxGridEditorPopupInnerWrapper {
+  position: relative;
+  height: 100%;
+  width: 100%;
+}.jxListView {
+  position:relative;
+  display: block;
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxListView .jxListItemContainer {
+    position: relative;
+    display: block;
+    outline: none;
+    overflow: hidden;
+    
+    border: none;
+    margin: 0px 1px;
+    padding: 0px;
+}
+
+.jxListItem {
+    position: relative;
+    display: block;
+    cursor: pointer;
+    outline: none;
+    overflow: hidden;
+
+    border: none;
+    margin: 0px 1px;
+    padding: 0px;
+    z-index: 0;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 11px;
+    color: #000;
+    text-decoration: none;
+    /* Line Height needs to be an even number so branches line up properly */
+    line-height: 20px;
+    height: 20px;
+}
+
+.jxListView .jxHover {
+    margin: 0px;
+    border-left: 1px solid #CDDFFD;
+    border-right: 1px solid #CDDFFD;
+    background-image: url(images/listitem.png);
+    background-repeat: repeat-x;
+    background-color: #CDE5FF;
+    background-position: left -24px;
+    
+}
+
+.jxListItem:focus {
+    margin: 0px;
+    border-left: 1px dotted #75ADFF;
+    border-right: 1px dotted #75ADFF;
+    background-image: url(images/listitem.png);
+    background-repeat: repeat-x;
+    background-position: left -72px;
+}
+
+.jxListView .jxPressed,
+.jxListView .jxSelected {
+  margin: 0px;
+  border-left: 1px solid #8AABFB;
+  border-right: 1px solid #8AABFB;
+  background-color: #CDE5FF;
+  background-image: url(images/listitem.png);
+  background-repeat: repeat-x;
+  background-position: left -48px;
+}
+/**
+ * @project         Jx
+ * @revision        $Id: menu.css 601 2009-11-10 18:44:35Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============== */
+/* JX MENU STYLES */
+/* ============== */
+
+.jxMenuContainer .jxChrome {
+  /* the background image gets used by Jx.Chrome to create a stretchable chrome */
+  background-image: url(images/flyout_chrome.png);
+  /* the padding reflects the amount of space to leave around the content area
+   * for the chrome, typically to leave space for a shadow
+   */
+  padding: 5px 5px 7px 6px;
+}
+
+.jxButtonMenu span.jxMenuItemSpan {
+  padding-right: 16px;
+}
+
+/* Jx Menus and Sub-menus are all built out of nested ULs
+   For this to work visually, the margins and padding need to be flattened
+   out, and the list marker needs to be hidden
+*/
+
+.jxMenuContainer {
+    position: absolute;
+    top: 0;
+    left: -10000px;
+    display: none;
+    z-index: 2000;
+    padding: 0px;
+}
+
+ul.jxMenu {
+  /* Base setup */
+  display: block;
+  position: relative;
+
+  list-style-type: none;
+  padding: 1px;
+  margin: 6px 6px 8px 7px;
+  background-color: #fff;
+  border: 1px solid #999;
+}
+
+li.jxMenuItemContainer {
+  /* This is needed for IE to make sure submenus don't open space in the parent menu */
+  /* Base setup */
+  display: block;
+  position: relative;
+  font-size: 0px;
+  line-height: 0px;
+
+  margin: 0px;
+  padding: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+a.jxMenuItem {
+  /* Base setup */
+  display: block;
+  position: relative;
+  overflow: hidden;
+  text-decoration: none;
+  cursor: pointer;
+  outline: none;
+
+  border: 1px solid #fff;
+  background-image: url(images/menuitem.png);
+  background-repeat: no-repeat;
+  background-position: left top;
+
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  text-decoration: none;
+  margin: 0px;
+  padding: 0px;
+  color: #000;
+}
+
+a.jxMenuItemActive {
+  background-position: left -98px;
+}
+
+a.jxMenuItem:focus {
+  background-position: left -74px;
+}
+
+a.jxMenuItem:focus span.jxMenuItemContent {
+  border-right: 1px dotted #75ADFF;
+}
+
+a.jxMenuItemActive:focus {
+  background-position: left -170px;
+}
+
+a.jxMenuItem:hover {
+  background-color: #CDE5FF;
+  background-position: left -26px;
+}
+
+a.jxMenuItem:hover span.jxMenuItemContent {
+  border-right: 1px solid #C5E0FF;
+}
+
+a.jxMenuItemActive:hover {
+  background-position: left -122px;
+}
+
+a.jxMenuItemPressed,
+a.jxMenuItemPressed:hover {
+  background-color: #CDE5FF;
+  background-position: left -50px;
+}
+
+.jxDisabled a.jxMenuItem,
+.jxDisabled span.jxMenuItemContent span {
+  cursor: default;
+}
+
+.jxDisabled a.jxMenuItem:focus,
+.jxDisabled a.jxMenuItemPressed,
+.jxDisabled a.jxMenuItemPressed:hover,
+.jxDisabled a.jxMenuItem:hover {
+  background-color: #fff;
+  background-position: left top;
+  border-right: 1px solid #fff;
+}
+
+.jxDisabled a.jxMenuItem:hover span.jxMenuItemContent {
+  border-right: 1px solid #fff;
+}
+
+span.jxMenuItemContent {
+  /* If using background images, the SPAN contains the right side of the background */
+  /* use padding to make space between the icon and button edge */
+  /* padding-left: 0px;*/ /* butts up to the left of the button bg image */
+  /* Base setup */
+  display: block;
+  position: relative;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 0px;
+  line-height: 0px;
+  white-space: nowrap;
+  padding: 0px 20px 0px 0px; /* space for the arrow */
+  margin: 0px;
+  border-right: 1px solid #fff; /* forces IE to render properly */
+  /*overflow: hidden;*/
+}
+
+.jxButtonSubMenu span.jxMenuItemContent,
+.jxButtonSubMenu:hover span.jxMenuItemContent {
+  background-image: url(images/emblems.png);
+  background-position: right -30px;
+  background-repeat: no-repeat;
+}
+
+img.jxMenuItemIcon {
+  /* Base setup */
+  position: absolute;
+  top: 2px;
+  left: 2px;
+
+  width: 16px;
+  height: 16px;
+  background-position: left center;
+  background-repeat: no-repeat;
+}
+
+span.jxMenuItemContent span {
+  /* Base setup for empty labels */
+  display: block;
+  position: relative;
+  cursor: pointer;
+  margin: 0px; /* margins don't seem to work properly in IE */
+  padding: 2px 0px 2px 22px; /* space for the icon */
+  font-size: 16px;
+  line-height: 16px;
+  /*height: 20px;*/
+  color: #000;
+}
+
+span.jxMenuItemContent span.jxMenuItemLabel {
+  /* Base setup, overrides empty labels */
+  color: #000;
+  font-size: 11px;
+}
+
+.jxMenuItemToggle img.jxMenuItemIcon,
+.jxMenuItemToggleSet img.jxMenuItemIcon {
+  background-image: url(images/emblems.png);
+  background-position: 2px 0px;
+  background-repeat: no-repeat;
+}
+
+.jxMenuItemToggle a.jxMenuItemActive img.jxMenuItemIcon {
+  background-position: 2px -48px;
+}
+
+.jxMenuItemToggleSet a.jxMenuItemActive img.jxMenuItemIcon {
+  background-position: 2px -64px;
+}
+
+ul.jxMenu span.jxMenuSeparator {
+  /* Base setup */
+  display: block;
+
+  font-size: 10px;
+  line-height: 10px;
+  background-image: url(images/toolbar_separator_v.png);
+  background-repeat: repeat-x;
+  background-position: left center;
+}
+ at CHARSET "ISO-8859-1";
+
+
+/**
+ * Confirm dialog classes
+ */
+.jxMessage {
+    text-align: center;
+    padding: 10px;
+    margin-top: 10px;
+}/* For Reference
+   Success, notice and error boxes from Blueprint */
+
+/* 
+.error      { background: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; }
+.notice     { background: #FFF6BF; color: #514721; border-color: #FFD324; }
+.success    { background: #E6EFC2; color: #264409; border-color: #C6D880; }
+.error a    { color: #8a1f11; }
+.notice a   { color: #514721; }
+.success a  { color: #264409; }
+*/
+
+
+.jxNoticeListContainer {
+  border: none;
+  padding: 0px;
+  margin: 0px;
+  font-size: 0px;
+  line-height: 0px;
+  z-index: 500;
+}
+
+.jxNoticeList {
+    position: relative;
+}
+
+.jxNoticeItemContainer {
+    position: relative;
+    overflow: hidden;
+}
+
+.jxNoticeItemContainer .jxChrome {
+  background-image: url(images/flyout_chrome.png);
+  padding: 5px 5px 7px 6px;
+}
+
+.jxHasChrome .jxNoticeItem  {
+  margin: 6px 6px 8px 7px;
+}
+
+.jxNoticeItem {
+    position: relative;
+    border: 2px solid #ccc;
+    margin: 0px;
+    padding: 0px;
+    background-color: #f8f8f8;
+    background-image: url(images/notice.png);
+    background-repeat: repeat-x;
+    background-position: left bottom;
+}
+
+.jxNoticeIcon {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  margin: 6px;
+  width: 16px;
+  height: 16px;
+  background-image: url(images/icons.png);
+  background-repeat: no-repeat;
+  background-position: 0px 0px;
+}
+
+.jxNotice {
+    display: block;
+    position: relative;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 18px;
+    color: #666;
+    margin: 0px;
+    border: none;
+    padding: 6px 26px 6px 10px; 
+}                          
+
+.jxNoticeError .jxNoticeItem { background-color: #FBE3E4; color: #8a1f11; border-color: #FBC2C4; background-image: url(images/notice_error.png);}
+.jxNoticeWarning .jxNoticeItem { background-color: #FFF6BF; color: #514721; border-color: #FFD324; background-image: url(images/notice_warning.png);}
+.jxNoticeSuccess .jxNoticeItem { background-color: #E6EFC2; color: #264409; border-color: #C6D880; background-image: url(images/notice_success.png);}
+.jxNoticeInformation .jxNoticeItem { background-color: #F8F8F8; color: #666666; border-color: #CCCCCC; background-image: url(images/notice.png);}
+
+.jxNoticeError .jxNotice { color: #8a1f11; padding-left: 28px; }
+.jxNoticeWarning .jxNotice { color: #514721; padding-left: 28px; }
+.jxNoticeSuccess .jxNotice { color: #264409; padding-left: 28px; }
+.jxNoticeInformation .jxNotice { color: #666666; padding-left: 28px; }
+
+.jxNoticeError .jxNoticeIcon { background-position: 0px -32px; }
+.jxNoticeWarning .jxNoticeIcon { background-position: 0px -16px; }
+.jxNoticeSuccess .jxNoticeIcon { background-position: 0px -48px; }            
+.jxNoticeInformation .jxNoticeIcon { background-position: 0px -64px; }
+
+.jxNoticeClose {
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    margin: 6px;
+    width: 16px;
+    height: 16px;
+    background-image: url(images/tab_close.png);
+    background-position: 0px 0px;
+    background-repeat: no-repeat;
+}/**
+ * @project         Jx
+ * @revision        $Id: panel.css 716 2010-03-02 16:07:19Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =============== */
+/* JX PANEL STYLES */
+/* =============== */
+
+.jxPanel {
+  /* Base setup */
+  display: block;
+  position:relative;
+}
+
+.jxPanelContentContainer {
+  /* Base setup */
+  /* need to test various scenarios to see if this is limiting */
+  overflow: hidden;
+  /*margin: 5px;*/
+  background-color: #f0f0f0;
+}
+
+/* the content panel inside a panel */
+.jxPanelContent {
+  /* Base setup */
+  /* position relative is required for panels to work correctly in safari */
+  position: relative;
+  display: block;
+  overflow: auto;
+
+  /*border: 1px solid #d8d8d8;*/
+  background-color: #fff;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxPanelTitle {
+  /* Base setup */
+  /* position relative is required for panel dragging to work correctly in safari */
+  display: block;
+  position: relative;
+
+  background-image: url(images/panelbar.png);
+  background-repeat: repeat-x;
+  background-position: left top;
+  /* note this is hard coded into jx.js JxPanel initialize function - change there as well as here */
+  height: 22px;
+  margin: 0;
+  padding: 0;
+
+  text-align: center;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+/* JX PANELSET STYLE FOR TITLE BAR */
+.jxPanelBar {
+  position: absolute;
+  line-height: 1px;
+  width: 100%;
+  height: 5px;
+  cursor: row-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxPanelIcon {
+  position: absolute;
+  left: 2px;
+  top: 3px;
+  width: 16px;
+  height: 16px;
+  border: none;
+  padding: 0px;
+  margin: 0px;
+}
+
+.jxPanelLabel {
+  /* make room for the loading spinner */
+  padding-left: 25px;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  font-weight: bold;
+  /* line-height vertically aligns the label in the containing div. */
+  line-height:21px;
+  color: #000;
+  white-space: nowrap;
+}
+
+.jxPanelControls {
+  position: absolute;
+  top: 3px;
+  right: 2px;
+  height: 16px;
+  width: 80px;
+  overflow: hidden;
+}
+
+.jxPanelControls img {
+  background-image: url('images/panel_controls.png');
+  background-repeat: no-repeat;
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+}
+
+.jxPanelClose img {
+  background-position: 0px -32px;
+}
+
+.jxPanelMenu img {
+  background-position: 0px -48px;
+}
+
+.jxPanelHelp img {
+  background-position: 0px -64px;
+}
+
+.jxPanelCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxPanelMin .jxPanelCollapse img {
+  background-position: 0px 0px;
+}
+
+.jxPanelMax .jxPanelCollapse img {
+  background-position: 0px -16px;
+}
+
+.jxPanelMaximize img {
+  background-position: 0px 0px;
+}
+
+.jxPanelLoading img {
+  border: 0px;
+  /* the margin needs to make up the difference between it's width/height
+     and the width/height of the parent a */
+  margin: 0px;
+  /* width/height has to be the actual image width/height */
+  width: 16px;
+  height: 16px;
+  visibility:hidden;
+  position: absolute;
+  top: 1px;
+  left: 2px;
+}
+
+
+
+/* ========================= */
+/* JX BUTTON STYLES OVERIDES */
+/* ========================= */
+
+.jxPanelControls .jxButtonContainer,
+.jxPanelControls .jxButton,
+.jxPanelControls .jxButton:hover,
+.jxPanelControls .jxButton:active ,
+.jxPanelControls .jxButtonActive,
+.jxPanelControls .jxButtonActive:hover,
+.jxPanelControls .jxButtonActive:active,
+.jxPanelControls .jxDisabled .jxButton,
+.jxPanelControls .jxDisabled .jxButton:hover,
+.jxPanelControls .jxDisabled .jxButton:active {
+  padding: 0px;
+  margin: 0px;
+  border: none;
+  background-color: transparent;
+  background-image: none;
+}
+
+
+/* ========================== */
+/* JX TOOLBAR STYLES OVERIDES */
+/* ========================== */
+
+/* Multiple toolbars can be housed in  the toolbar container the container will expand vertically to accomodate wrapped toolbars */
+
+.jxPanelControls div.jxBarTop {
+  /* Base setup */
+  position: absolute;
+  right: 0px;
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  height: 16px;
+}
+
+.jxPanelControls .jxBarScroller {
+  left: auto;
+  right: 0px;
+}
+
+.jxPanelControls ul.jxToolbar {
+  float: right; 
+}
+
+.jxPanelControls ul.jxToolbar,
+.jxPanelControls li.jxToolItem {
+  background-image: none;
+  background-color: transparent;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+}/**
+ * progress bar classes
+ */
+.jxProgressBar-container {
+    position: relative;
+    display: block;
+    width: 100%;
+}
+
+.jxProgressBar-message {
+    position: relative;
+    display: block;
+    color: black;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 20px;
+}
+
+.jxProgressBar {
+    position: relative;
+    display: block;
+    width: 100%;
+    height: 20px;
+    border: none;
+    margin: 0px;
+    padding: 0px;
+}
+
+.jxProgressBar-outline {
+    position: absolute;
+    display: block;
+    top: 0px;
+    left: 0px;
+    z-index: 10;
+    height: 20px;
+    border-left: 1px solid #cbc8c8;
+    border-right: 1px solid #cbc8c8;
+    background-image: url(images/progressbar.png);
+    background-position: 0px -140px;
+    width: 100%;
+}
+
+.jxProgressBar-fill {
+    position: absolute;
+    display: block;
+    top: 0px;
+    left: 0px;
+    z-index: 20;
+    height: 20px;
+    border: none;
+    background-image: url(images/progressbar.png);
+    background-position: 0px 0px;
+}
+
+.jxProgressStarting .jxProgressBar-fill {
+    border: none;
+}
+
+.jxProgressWorking .jxProgressBar-fill {
+    border-left: 1px solid #49afe8;
+}
+
+.jxProgressFinished .jxProgressBar-fill {
+    border-left: 1px solid #49afe8;
+    border-right: 1px solid #49afe8;
+}
+
+.jxProgressBar-text {
+    position: absolute;
+    display: block;
+    overflow: visible;
+    top: 0px;
+    left: 1px;
+    width: auto;
+    height: 20px;
+    z-index: 30;
+    border: none;
+    margin: 0px;
+    padding: 0px 0px 0px 4px;
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    line-height: 20px;
+    white-space: nowrap;
+}
+.jxHasVerticalScrollbar,
+.jxHasHorizontalScrollbar {
+    position: relative;
+    overflow: hidden;
+}
+
+.jxScrollbarChildWrapper {
+    overflow: hidden;
+}
+
+.jxHasVerticalScrollbar {
+    padding-right: 25px;
+}
+
+.jxHasVerticalScrollbar .jxScrollbarContainer {
+    position: absolute;
+    right: 0;
+    top: 0;
+    width: 20px;
+    height: 100%;
+    border: none;
+    border-left: 1px solid black;
+}
+
+.jxHasVerticalScrollbar .jxScrollLeft,
+.jxHasVerticalScrollbar .jxScrollRight {
+    width: 20px;
+    height: 20px;
+    display: block;
+    background-color: blue;
+}
+
+.jxHasVerticalScrollbar .jxSliderContainer {
+    height: 100%;
+    width: 20px;
+    border: none;
+}
+
+.jxHasHorizontalScrollbar {
+    padding-bottom: 25px;
+}
+
+.jxHasHorizontalScrollbar .jxScrollbarContainer {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    height: 20px;
+    width: 100%;
+    border: none;
+    border-top: 1px solid black;
+}
+
+.jxHasHorizontalScrollbar .jxScrollLeft,
+.jxHasHorizontalScrollbar .jxScrollRight {
+    width: 20px;
+    height: 20px;
+    display: block;
+    background-color: blue;
+}
+ 
+.jxHasHorizontalScrollbar .jxScrollLeft {
+    float: left;
+}
+
+.jxHasHorizontalScrollbar .jxScrollRight {
+    float: right;
+}
+
+.jxHasHorizontalScrollbar .jxSlider {
+    float: left;
+}
+
+.jxHasHorizontalScrollbar .jxSliderContainer {
+    height: 20px;
+    width: 100%;
+    border: none;
+}
+
+.jxHasVerticalScrollbar .jxSliderKnob,
+.jxHasHorizontalScrollbar .jxSliderKnob {
+    width: 20px;
+    height: 20px;
+    background-color: black;
+    cursor: pointer;
+}@CHARSET "ISO-8859-1";
+
+/**
+ * Slider classes
+ */
+.jxSliderContainer {
+    width: 100%;
+    height: 10px;
+    border: 1px solid black;
+}
+
+.jxSliderKnob {
+    width: 10px;
+    height: 10px;
+    background-color: black;
+    cursor: pointer;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: splitter.css 294 2009-04-02 12:26:26Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =============== */
+/* SPLITTER STYLES */
+/* =============== */
+.jxSplitterMask{
+    position:absolute;
+    top:0;
+    left:0;
+    width:100%;
+    height:100%;
+    overflow:hidden;
+    background-image: url(images/a_pixel.png);
+    z-index:1;
+}
+
+.jxSplitBarHorizontal {
+  display: block;
+  position: absolute;
+  font-size: 0px;
+  line-height: 0px;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  width: 5px;
+  height: 100%;
+  cursor: col-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxSplitBarVertical {
+  display: block;
+  position: absolute;
+  font-size: 0px;
+  line-height: 0px;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  width: 100%;
+  height: 5px;
+  cursor: row-resize;
+  /*background-image: url(images/a_pixel.png);*/
+  background-color: #f0f0f0;
+  z-index: 2;
+}
+
+.jxSplitContainer {
+  display: block;
+  position: relative;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  overflow: hidden;
+}
+
+.jxSplitArea {
+  display: block;
+  position: absolute;
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  /*overflow: hidden;*/
+  z-index: 0;
+}
+
+.jxSplitBarDrag {
+  background-color: #eee;
+}
+
+.jxSnapHorizontalBefore {
+  width: 5px;
+  height: 5px;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  background-color: #aaa;
+}
+
+.jxSnapHorizontalAfter {
+  width: 5px;
+  height: 5px;
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  background-color: #aaa;
+}/**
+ * @project         Jx
+ * @revision        $Id: tab.css 875 2010-04-24 06:10:53Z fred.warnock $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ================== */
+/* TAB CONTENT STYLES */
+/* ================== */
+
+/* The tabBox consists of a box containing a tabbar and the tab content areas.
+   It can be used within the body or nested within another object.
+*/
+
+.jxTabSetContainer {
+  /* This is an example of a container that can be used to hold a tabBox
+     the position need to be explicitly set, as well as the width and height. */
+  /* Base setup */
+  position: relative;
+  display: block;
+  overflow: hidden;
+
+  width: 200px;
+  height: 200px;
+  margin: 0px;
+  padding: 0px;
+  background-color: #fff;
+}
+
+.jxTabSetContainer  .jxBarContainer {
+  /* Base setup */
+  z-index: auto;
+}
+
+.tabContent {
+  /* the width and height need to be set to 100% to:
+     1. fill the tab box area
+     2. allow a scrolling content area in IE */
+  /* Base setup */
+  display: none;
+  position: relative;
+  width:100%;
+  height: 100%;
+  overflow: auto;
+}
+
+.tabContentActive {
+  /* Base setup */
+  display: block;
+}
+
+/* ======================== */
+/* BASE TAB (BUTTON) STYLES */
+/* ======================== */
+
+span.jxTabContainer {
+  /* Base setup */
+  display: block;
+  position: relative;
+
+  margin: 0px;
+  padding: 2px;
+  border: none;
+}
+
+a.jxTab {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+
+  /* Using background images, the A contains the left side of the background */
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  background-repeat: no-repeat;
+  text-decoration: none;
+  outline: none;
+}
+
+span.jxTabContent {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  font-size: 0px;
+  line-height: 0px;
+
+  /* The SPAN contains the other side of the tab background image
+     and the tab label */
+  margin: 0px;
+  padding: 0px;
+  border: none;
+  background-repeat: no-repeat;
+}
+
+img.jxTabIcon {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+
+  width: 16px;
+  height: 16px;
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+
+span.jxTabLabel {
+  /* Base setup */
+  display: -moz-inline-box;
+  display: inline-block;
+  position: relative;
+  cursor: pointer;
+
+  margin: 0px;
+  padding: 0px;
+  color: #000;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  line-height: 16px;
+}
+
+a.jxTabClose {
+  /* Base setup */
+  display: block;
+  position: absolute;
+  cursor: pointer;
+  outline: none;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+  width: 16px;
+  height: 16px;
+  background-image: url(images/tab_close.png);
+  background-position: 0px 0px;
+  background-repeat: no-repeat;
+}
+
+.jxDisabled a.jxTab,
+.jxDisabled span.jxTabContent span,
+.jxDisabled a.jxTabClose {
+  cursor: default;
+}
+
+.jxTabBox {
+}
+
+/* ======================================= */
+/* HORIZONTALTAB BAR - TOP and BOTTOM TABS */
+/* ======================================= */
+
+.jxTabBarTop .jxBarWrapper, 
+.jxTabBarBottom .jxBarWrapper {
+  padding-left: 2px;
+}
+
+.jxBarTop span.jxTabContainer,
+.jxBarBottom span.jxTabContainer {
+  /* Base setup */
+  margin-right: -1px;
+  padding: 2px 0px;
+}
+
+.jxBarTop a.jxTab,
+.jxBarTop span.jxTabContent,
+.jxTabBarTop .jxBarControls a.jxButton,
+.jxTabBarTop .jxBarControls span.jxButtonContent{
+  background-image: url(images/tab_top.png);
+}
+
+.jxBarBottom a.jxTab,
+.jxBarBottom span.jxTabContent,
+.jxTabBarBottom .jxBarControls a.jxButton,
+.jxTabBarBottom .jxBarControls span.jxButtonContent {
+  background-image: url(images/tab_bottom.png);
+}
+
+/* Closeable Tab */
+.jxBarTop a.jxTabClose,
+.jxBarBottom a.jxTabClose {
+  top: 5px;
+  right: 5px;
+}
+
+.jxBarTop .jxTabClose span.jxTabContent,
+.jxBarBottom .jxTabClose span.jxTabContent {
+  padding-right: 16px;
+}
+
+/* Normal Tab */
+.jxBarTop a.jxTab,
+.jxBarBottom a.jxTab {
+  /* Base setup */
+
+  padding-left: 4px; /* makes room for the left of the tab bg */
+  background-position: left -24px; 
+}
+
+.jxBarTop span.jxTabContent,
+.jxBarBottom span.jxTabContent {
+  /* Base setup */
+
+  padding: 4px 4px 4px 0px; /* makes space around the label */
+  background-position: right -24px; 
+}
+
+/* Active tab */
+.jxBarTop a.jxTabActive,
+.jxBarBottom a.jxTabActive {
+  background-position: left -72px; 
+}
+
+.jxBarTop a.jxTabActive span.jxTabContent,
+.jxBarBottom a.jxTabActive span.jxTabContent {
+  background-position: right -72px; 
+}
+
+/* Focus tab */
+.jxBarTop a.jxTab:focus,
+.jxBarBottom a.jxTab:focus {
+  background-position: left -96px; 
+}
+
+.jxBarTop a.jxTab:focus span.jxTabContent,
+.jxBarBottom a.jxTab:focus span.jxTabContent {
+  background-position: right -96px; 
+}
+
+/* Focus Active tab */
+.jxBarTop a.jxTabActive:focus,
+.jxBarBottom a.jxTabActive:focus {
+  background-position: left -144px; 
+}
+
+.jxBarTop a.jxTabActive:focus span.jxTabContent,
+.jxBarBottom a.jxTabActive:focus span.jxTabContent {
+  background-position: right -144px; 
+}
+
+/* Hover Normal and Active  Tab */
+.jxBarTop a.jxTab:hover,
+.jxBarTop a.jxTabActive:hover,
+.jxBarBottom a.jxTab:hover,
+.jxBarBottom a.jxTabActive:hover {
+  background-position: left -48px; 
+}
+
+.jxBarTop a.jxTab:hover span.jxTabContent,
+.jxBarTop a.jxTabActive:hover span.jxTabContent,
+.jxBarBottom a.jxTab:hover span.jxTabContent,
+.jxBarBottom a.jxTabActive:hover span.jxTabContent {
+  background-position: right -48px; 
+}
+
+/* Click Normal and Focused Tab */
+.jxBarTop a.jxTabPressed,
+.jxBarTop a.jxTabPressed:focus,
+.jxBarBottom a.jxTabPressed,
+.jxBarBottom a.jxTabPressed:focus {
+  background-position: left -120px; 
+}
+
+.jxBarTop a.jxTabPressed span.jxTabContent,
+.jxBarTop a.jxTabPressed:focus span.jxTabContent,
+.jxBarBottom a.jxTabPressed span.jxTabContent,
+.jxBarBottom a.jxTabPressed:focus span.jxTabContent {
+  background-position: right -120px; 
+}
+
+/* Hover, Focus and Pressing Disabled Tab */
+.jxBarTop .jxDisabled a.jxTab:focus,
+.jxBarTop .jxDisabled a.jxTab:active,
+.jxBarTop .jxDisabled a.jxTab:hover,
+.jxBarTop .jxDisabled a.jxTabPressed,
+.jxBarBottom .jxDisabled a.jxTab:focus,
+.jxBarBottom .jxDisabled a.jxTab:active,
+.jxBarBottom .jxDisabled a.jxTab:hover,
+.jxBarBottom .jxDisabled a.jxTabPressed {
+  background-position: left -24px; /* do not switch the left BG */
+}
+
+  
+.jxBarTop .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabPressed span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabPressed  span.jxTabContent {
+  background-position: right -24px; /* do not switch the right BG */
+}
+
+/* Hover, Focus Disabled Active Tab */
+.jxBarTop .jxDisabled a.jxTabActive:focus,
+.jxBarTop .jxDisabled a.jxTabActive:active,
+.jxBarTop .jxDisabled a.jxTabActive:hover,
+.jxBarBottom .jxDisabled a.jxTabActive:focus,
+.jxBarBottom .jxDisabled a.jxTabActive:active,
+.jxBarBottom .jxDisabled a.jxTabActive:hover {
+  background-position: left -72px; /* do not switch the left BG */
+}
+
+  
+.jxBarTop .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarTop .jxDisabled a.jxTabActive:hover span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarBottom .jxDisabled a.jxTabActive:hover span.jxTabContent {
+  background-position: right -72px; /* do not switch the right BG */
+}
+
+.jxBarTop img.jxTabIcon,
+.jxBarBottom img.jxTabIcon {
+  vertical-align: middle;
+  /* Base setup */
+}
+
+.jxBarTop span.jxTabLabel,
+.jxBarBottom span.jxTabLabel {
+  /* Base setup */
+  vertical-align: middle;
+  height: 16px;
+
+  padding: 0px 4px 0px 4px;
+}
+
+
+/* ================================= */
+/* VERTICAL TAB BAR - LEFT and RIGHT */
+/* ================================= */
+
+.jxTabBarLeft .jxBarWrapper, 
+.jxTabBarRight .jxBarWrapper {
+  padding-top: 2px;
+}
+
+.jxBarLeft span.jxTabContainer,
+.jxBarRight span.jxTabContainer {
+  /* Base setup */
+  margin-bottom: -1px;
+  padding: 0px 2px;
+}
+
+
+.jxBarLeft a.jxTab,
+.jxBarLeft span.jxTabContent {
+  background-image: url(images/tab_left.png);
+}
+
+.jxBarRight a.jxTab,
+.jxBarRight span.jxTabContent {
+  background-image: url(images/tab_right.png);
+}
+
+/* Closeable Tab */
+.jxBarLeft a.jxTabClose,
+.jxBarRight a.jxTabClose {
+  top: 5px;
+  left: 5px;
+}
+
+.jxBarLeft .jxTabClose span.jxTabContent,
+.jxBarRight .jxTabClose span.jxTabContent {
+  padding-top: 16px;
+}
+
+/* Normal Tab */
+.jxBarLeft a.jxTab,
+.jxBarRight a.jxTab {
+  padding-top: 4px; /* makes room for the top of the tab bg */
+  background-position: -24px top; 
+}
+
+.jxBarLeft span.jxTabContent,
+.jxBarRight span.jxTabContent {
+  padding: 0px 4px 4px 4px; /* makes space around the label */
+  background-position: -24px bottom; 
+}
+
+/* Active Tab */
+.jxBarLeft a.jxTabActive,
+.jxBarRight a.jxTabActive {
+  background-position: -72px top; 
+}
+
+.jxBarLeft a.jxTabActive span.jxTabContent,
+.jxBarRight a.jxTabActive span.jxTabContent {
+  background-position: -72px bottom; 
+}
+
+/* Focus tab */
+.jxBarLeft a.jxTab:focus,
+.jxBarRight a.jxTab:focus {
+  background-position: -96px top; 
+}
+
+.jxBarLeft a.jxTab:focus span.jxTabContent,
+.jxBarRight a.jxTab:focus span.jxTabContent {
+  background-position: -96px bottom; 
+}
+
+/* Focus Active tab */
+.jxBarLeft a.jxTabActive:focus,
+.jxBarRight a.jxTabActive:focus {
+  background-position: -144px top; 
+}
+
+.jxBarLeft a.jxTabActive:focus span.jxTabContent,
+.jxBarRight a.jxTabActive:focus span.jxTabContent {
+  background-position: -144px bottom; 
+}
+
+/* Hover Normal and Active tab */
+.jxBarLeft a.jxTab:hover,
+.jxBarLeft a.jxTabActive:hover,
+.jxBarRight a.jxTab:hover,
+.jxBarRight a.jxTabActive:hover {
+  background-position: -48px top; 
+}
+
+.jxBarLeft a.jxTab:hover span.jxTabContent,
+.jxBarLeft a.jxTabActive:hover span.jxTabContent,
+.jxBarRight a.jxTab:hover span.jxTabContent,
+.jxBarRight a.jxTabActive:hover span.jxTabContent {
+  background-position: -48px bottom; 
+}
+
+/* Click Normal and Focused Tab */
+.jxBarLeft a.jxTabPressed,
+.jxBarLeft a.jxTabPressed:focus,
+.jxBarRight a.jxTabPressed,
+.jxBarRight a.jxTabPressed:focus {
+  background-position: -120px top; 
+}
+
+.jxBarLeft a.jxTabPressed span.jxTabContent,
+.jxBarLeft a.jxTabPressed:focus span.jxTabContent,
+.jxBarRight a.jxTabPressed span.jxTabContent,
+.jxBarRight a.jxTabPressed:focus span.jxTabContent {
+  background-position: -120px bottom; 
+}
+
+/* Hover, Focus and Pressing Disabled Tab */
+.jxBarLeft .jxDisabled a.jxTab:focus,
+.jxBarLeft .jxDisabled a.jxTab:active,
+.jxBarLeft .jxDisabled a.jxTab:hover,
+.jxBarLeft .jxDisabled a.jxTabPressed,
+.jxBarRight .jxDisabled a.jxTab:focus,
+.jxBarRight .jxDisabled a.jxTab:active,
+.jxBarRight .jxDisabled a.jxTab:hover,
+.jxBarRight .jxDisabled a.jxTabPressed {
+  background-position: -24px top; /* do not switch the left BG */
+}
+
+  
+.jxBarLeft .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabPressed span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:focus span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:active span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTab:hover span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabPressed  span.jxTabContent {
+  background-position: -24px bottom; /* do not switch the right BG */
+}
+
+/* Hover, Focus Disabled Active Tab */
+.jxBarLeft .jxDisabled a.jxTabActive:focus,
+.jxBarLeft .jxDisabled a.jxTabActive:active,
+.jxBarLeft .jxDisabled a.jxTabActive:hover,
+.jxBarRight .jxDisabled a.jxTabActive:focus,
+.jxBarRight .jxDisabled a.jxTabActive:active,
+.jxBarRight .jxDisabled a.jxTabActive:hover {
+  background-position: -72px top; /* do not switch the left BG */
+}
+
+.jxBarLeft .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarLeft .jxDisabled a.jxTabActive:hover span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:focus span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:active span.jxTabContent,
+.jxBarRight .jxDisabled a.jxTabActive:hover span.jxTabContent {
+  background-position: -72px bottom; /* do not switch the right BG */
+}
+
+.jxBarLeft span.jxTabLabel,
+.jxBarRight span.jxTabLabel {
+  padding: 4px 0px 4px 0px;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: toolbar.css 912 2010-05-21 21:33:08Z pagameba $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* ============== */
+/* TOOLBAR STYLES */
+/* ============== */
+
+/* Multiple toolbars can be housed in  the toolbar container.
+   The container will expand vertically to accomodate wrapped toolbars */
+
+.jxBarContainer {
+  /* Base setup */
+  display: block;
+  position: relative;
+  z-index: 1;
+  overflow: hidden;
+
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+
+  background-color: #f0f0f0;
+}
+
+.jxBarTop,
+.jxBarBottom {
+  /* Horizontally oriented toolbars */
+  /* Base setup */
+  width: 100%; /* fills the width, may be needed for JS style sniffing */
+  height: 28px;
+  background-image: url(images/toolbar.png);
+  background-repeat: repeat-x;
+  background-position: 0px 0px;
+  overflow: hidden;
+}
+
+.jxTabBox .jxTabBarTop {
+  background-image: url(images/tabbar.png);
+  background-position: 0px bottom;
+}
+
+.jxTabBox .jxTabBarBottom {
+  background-image: url(images/tabbar_bottom.png);
+  background-position: 0px top;
+}
+
+.jxBarLeft,
+.jxBarRight {
+  /* Vertically oriented toolbars */
+  /* Base setup */
+  width: auto;
+  height: 100%;
+  background-image: url(images/toolbar.png);
+  background-repeat: repeat-x;
+  background-position: 0px 0px;
+  float: left;
+  overflow: hidden;
+}
+
+.jxTabBox .jxTabBarLeft {
+  background-image: url(images/tabbar_left.png);
+  background-repeat: repeat-y;
+  background-position: right 0px;
+}
+
+.jxTabBox .jxTabBarRight {
+  background-image: url(images/tabbar_right.png);
+  background-repeat: repeat-y;
+  background-position: left 0px;
+}
+
+
+.jxBarTop .jxBarScroller,
+.jxBarBottom .jxBarScroller {
+  float: left;
+  height: 28px;
+  overflow: hidden;
+  z-index: 0;
+}
+
+.jxBarTop .jxBarScroller .jxBarWrapper,
+.jxBarBottom .jxBarScroller .jxBarWrapper {
+  float: left;
+  height: 28px;
+  overflow: hidden;
+  width: 10000%;
+}
+
+.jxBarTop .jxBarControls .jxButtonContainer,
+.jxBarBottom .jxBarControls .jxButtonContainer {
+  /* float: right; */
+  z-index: 1;
+  padding: 2px 0px;
+  margin-left: -1px;
+}
+
+.jxBarTop .jxBarScrollLeft img.jxButtonIcon,
+.jxBarBottom .jxBarScrollLeft img.jxButtonIcon {
+  background-image: url(images/emblems.png);
+  background-position: 0px -80px;
+}
+
+.jxBarTop .jxBarScrollRight img.jxButtonIcon,
+.jxBarBottom .jxBarScrollRight img.jxButtonIcon {
+  background-image: url(images/emblems.png);
+  background-position: 0px -96px;
+}
+
+.jxBarControls {
+    float: right;
+    position: relative;
+    font-size: 0px;
+    line-height: 0px;
+}
+
+/* The jx toolbar and tabbar are both built out of a UL
+   The margins/padding are flattened out, and the list markers are hidden
+   UL's are floated left so multiple toolbars can be in the samae row.
+   In IE, the UL needs to have a specified width to prevent button wrapping.
+
+   The tab background uses the sliding door technique so tabs can expand to
+   accomodate content up to 200 px wide (top/bottom tabs) or 200px high
+   (left/right tabs).  All parts and states of the tab BG graphics are in the
+   same image so they can be treated like sprites.
+
+   Horizontal tabs can contain text or an image label.  Vertical tabs need an
+   image label.
+*/
+
+ul.jxToolbar,
+ul.jxTabBar {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  clear: none;
+  list-style-type: none;
+
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  padding: 0px;
+  border: none;
+}
+
+.jxBarTop ul.jxToolbar,
+.jxBarBottom ul.jxToolbar,
+.jxBarTop ul.jxTabBar,
+.jxBarBottom ul.jxTabBar {
+}
+
+/* LI's are floated to the left, to make a horizontal row of buttons*/
+
+li.jxToolItem {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  font-size: 0px;
+  line-height: 0px;
+  white-space: nowrap;
+  padding: 0px;
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  border: none;
+}
+
+/* inputs in toolbars wrap in IE */
+li.jxToolItem .jxInputWrapper {
+  white-space: nowrap;
+}
+
+
+/* Seperator height should match that of button images
+   and the margins+padding+border should add up to the same total too. */
+
+li.jxToolItem  span.jxBarSeparator {
+  /* Base setup */
+  display: block;
+  position: relative;
+  float: left;
+  font-size: 0px;
+  line-height: 0px;
+
+  border: 0px;
+  margin: 0px;  /* margins don't seem to work properly in IE */
+  padding: 4px;
+  background-repeat: no-repeat;
+  background-position: center center;
+}
+
+.jxBarTop  li.jxToolItem  span.jxBarSeparator,
+.jxBarBottom  li.jxToolItem  span.jxBarSeparator {
+  /* width/height should be defined */
+  width: 8px;
+  height: 20px;
+  background-image: url(images/toolbar_separator_h.png);
+}
+
+.jxBarLeft  li.jxToolItem  span.jxBarSeparator,
+.jxBarRight  li.jxToolItem  span.jxBarSeparator {
+  /* width/height should be defined */
+  width: 20px;
+  height: 8px;
+  background-image: url(images/toolbar_separator_v.png);
+}
+
+/* Vertically oriented toolbars need floats cleared */
+
+.jxBarLeft ul.jxToolbar,
+.jxBarLeft ul.jxTabBar,
+.jxBarLeft li.jxToolItem,
+.jxBarRight ul.jxToolbar,
+.jxBarRight ul.jxTabBar,
+.jxBarRight li.jxToolItem
+{
+  /* Base setup */
+  clear: both;
+}
+/* Aligning options */
+.jxToolbarAlignLeft ul {
+    float: left;
+}
+
+.jxToolbarAlignRight ul {
+    float: right;
+}
+
+.jxToolbarAlignCenter {
+    text-align: center;
+}
+
+.jxToolbarAlignCenter ul {
+    float: none;
+}
+
+
+.jxToolbarAlignCenter ul li {
+    float: none;
+    display: inline;
+}/*
+ * Tooltip classes
+ */
+.jxTooltip {
+	width: auto;
+	height: auto;
+	background-color: black;
+	color: white;
+	padding: 5px;
+	z-index: 65536;
+}
+
+/**
+ * @project         Jx
+ * @revision        $Id: tree.css 755 2010-03-15 03:09:37Z jonlb at comcast.net $
+ * @author          Fred Warnock (fwarnock at dmsolutions.ca)
+ * @copyright       (c) 2006 DM Solutions Group Inc.
+ */
+
+/* =========== */
+/* TREE STYLES */
+/* =========== */
+
+/* The jx tree built out of nested ULs
+   For this to work visually, the margins and padding need to be flattened
+   out, and the list marker needs to be hidden
+*/
+
+
+.jxTree,
+.jxTreeRoot {
+  /* relative positioning is required for IE to fix the peek-a-boo bug */
+  position:relative;
+  display: block;
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+}
+
+.jxTreeNest {
+  list-style: none;
+  margin: 0px;
+  padding: 0px;
+  background-repeat: repeat-y;
+  background-position: left top;
+}
+
+/* Node Classes */
+
+li.jxTreeContainer {
+  /* relative positioning is required for IE to fix the peek-a-boo bug */
+  position:relative;
+  display: block;
+  margin: 0px;
+  padding: 0px;
+  background-repeat: no-repeat;
+  /* background branches may need to shift up/down according to height of the node */
+  background-position: left top;
+  white-space: nowrap;
+  font-size: 0px;
+  line-height: 0px;
+  user-select: none;
+  -moz-user-select: none;
+  -khtml-user-select: none;
+}
+
+.jxTree li.jxTreeContainer {
+  margin-left: 16px;
+}
+
+a.jxTreeItem {
+  position: relative;
+  display: block;
+  cursor: pointer;
+  outline: none;
+  overflow: hidden;
+
+  background-image: url(images/tree_hover.png);
+  background-repeat: repeat-x;
+  background-position: left top;
+  border: none;
+
+  margin: 0px 1px 0px 17px;
+  padding: 0px 0px 0px 20px;
+  z-index: 0;
+  font-family: Arial, Helvetica, sans-serif;
+  font-size: 11px;
+  color: #000;
+  text-decoration: none;
+  /* Line Height needs to be an even number so branches line up properly */
+  line-height: 20px;
+  height: 20px;
+}
+
+
+a.jxTreeItem:focus {
+  border-left: 1px dotted #75ADFF;
+  border-right: 1px dotted #75ADFF;
+  margin: 0px 0px 0px 16px;
+  background-position: left -72px;
+}
+
+a.jxTreeItem:hover,
+li.jxTreeContainer a.jxHover {
+  /*border: 1px solid #C5E0FF;*/
+  border-left: 1px solid #CDDFFD;
+  border-right: 1px solid #CDDFFD;
+  margin: 0px 0px 0px 16px;
+  background-color: #CDE5FF;
+  background-position: left -24px;
+}
+
+li.jxTreeContainer a.jxSelected,
+li.jxTreeContainer a.jxSelected:hover,
+li.jxTreeContainer a.jxPressed,
+li.jxTreeContainer a.jxPressed:hover {
+  border-left: 1px solid #8AABFB;
+  border-right: 1px solid #8AABFB;
+  margin: 0px 0px 0px 16px;
+  background-color: #CDE5FF;
+  background-position: left -48px;
+}
+
+li.jxDisabled a.jxTreeItem {
+  cursor: default;
+}
+            
+li.jxDisabled a.jxTreeItem:focus,
+li.jxDisabled a.jxTreeItem:hover {
+  background-position: left top;
+  background-color: transparent;
+  border: none;
+  margin: 0px 1px 0px 17px;
+}
+
+.jxTreeNest {
+  background-image: url(images/tree_vert_line.png);
+}
+
+img.jxTreeImage,
+img.jxTreeIcon {
+  position: absolute;
+  display: inline;
+
+  left: 0px;
+  top: 0px;
+  width: 16px;
+  height: 20px;
+
+  z-index: 1;
+
+  background-image: url(images/tree.png);
+  background-repeat: no-repeat;
+
+  border: 0px;
+  margin: 0px;
+}
+
+img.jxTreeIcon { 
+  height: 16px;
+  top: 2px;
+  left: 1px;
+}
+
+.jxTreeBranchOpen .jxTreeIcon,
+.jxTreeBranchLastOpen .jxTreeIcon {
+  background-position: left -40px; /* open folder image */
+}
+
+
+.jxTreeBranchOpen .jxTreeImage {
+  background-position: left -100px; /* minus image */
+}
+
+.jxTreeBranchLastOpen .jxTreeImage {
+  background-position: left -160px; /* minus last image */
+}
+
+.jxTreeBranchClosed .jxTreeIcon,
+.jxTreeBranchLastClosed .jxTreeIcon {
+  background-position: left -20px; /* closed folder image */
+}
+
+
+.jxTreeBranchClosed .jxTreeImage {
+  background-position: left -80px; /* plus image */
+}
+
+.jxTreeBranchLastClosed .jxTreeImage {
+  background-position: left -140px; /* plus last image */
+}
+
+.jxTreeLeaf .jxTreeIcon,
+.jxTreeLeafLast .jxTreeIcon {
+  background-position: left 0px; /* page image */
+}
+
+.jxTreeLeaf .jxTreeImage {
+  background-position: left -60px; /* node image */
+}
+
+.jxTreeLeafLast .jxTreeImage {
+  background-position: left -120px; /* last node image */
+}
+
+
+a.jxTreeItem,
+img.jxTreeImage,
+img.jxTreeIcon,
+span.jxTreeLabel,
+.jxTreeItemContainer input {
+    vertical-align: middle;
+}
+
+img.jxTreeImage.jxBusy {
+    background-image: url(images/spinner_16.gif);
+    background-position: left top;
+}/* FileUploadPanel */
+.jxFileUploadPanel {
+    padding: 5px;
+}
+
+.jxFileUploadPanel .jxInputContainer {
+    /*width: 300px;*/
+}
+
+.jxUploadQueue {
+    /*width: 100%;*/
+    /*margin-top: 10px;*/
+}
+
+.jxUploadQueue li {
+    display: block;
+    position: relative;
+    overflow: hidden;
+    /*width: 95%;*/
+    /*clear: both;*/
+    /*border-top: 1px solid black;*/
+    padding: 2px;
+}
+
+.jxUploadQueue div span {
+    display: block;
+}
+
+.jxUploadQueue li span.jxUploadFileName {
+    /*float: left;*/
+    font-size: 12px;
+    line-height: 16px;
+    padding-left: 2px;
+}
+
+.jxUploadQueue li span.jxUploadFileDelete,
+.jxUploadQueue li span.jxUploadFileProgress,
+.jxUploadQueue li span.jxUploadFileComplete,
+.jxUploadQueue li span.jxUploadFileError {
+    /*float: right;*/
+    position: absolute;
+    top: 2px;
+    right: 2px;
+    width: 16px;
+    height: 16px;
+    background-repeat: no-repeat;
+    cursor: pointer;
+}
+
+.jxUploadQueue li span.jxUploadFileDelete {
+    background-image: url('images/icons.png');
+    background-position: 0px -128px;
+}
+
+.jxUploadQueue li span.jxUploadFileProgress {
+    background-image: url('images/spinner_16.gif');
+    background-position: top left;
+}
+
+.jxUploadQueue li span.jxUploadFileComplete {
+    background-image: url('images/icons.png');
+    background-position: 0px -48px;
+}
+
+.jxUploadQueue li span.jxUploadFileError {
+    background-image: url('images/icons.png');
+    background-position: 0px -32px;
+}
+
+.jxUploadFileErrorTip {
+  padding: 4px 4px 4px 20px;
+  border: 2px solid #ddd;
+  background: url("images/icons.png") no-repeat 0px -32px;
+  color: black;
+  width: 100px;
+}

Deleted: trunk/lib/proj4js-combined.js
===================================================================
--- trunk/lib/proj4js-combined.js	2011-04-14 19:43:29 UTC (rev 2370)
+++ trunk/lib/proj4js-combined.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -1,5142 +0,0 @@
-/*
-  proj4js.js -- Javascript reprojection library. 
-  
-  Authors:      Mike Adair madairATdmsolutions.ca
-                Richard Greenwood richATgreenwoodmap.com
-                Didier Richard didier.richardATign.fr
-                Stephen Irons
-  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html 
-                Note: This program is an almost direct port of the C library
-                Proj4.
-*/
-/* ======================================================================
-    proj4js.js
-   ====================================================================== */
-
-/*
-Author:       Mike Adair madairATdmsolutions.ca
-              Richard Greenwood rich at greenwoodmap.com
-License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html
-
-$Id: Proj.js 2956 2007-07-09 12:17:52Z steven $
-*/
-
-/**
- * Namespace: Proj4js
- *
- * Proj4js is a JavaScript library to transform point coordinates from one 
- * coordinate system to another, including datum transformations.
- *
- * This library is a port of both the Proj.4 and GCTCP C libraries to JavaScript. 
- * Enabling these transformations in the browser allows geographic data stored 
- * in different projections to be combined in browser-based web mapping 
- * applications.
- * 
- * Proj4js must have access to coordinate system initialization strings (which
- * are the same as for PROJ.4 command line).  Thes can be included in your 
- * application using a <script> tag or Proj4js can load CS initialization 
- * strings from a local directory or a web service such as spatialreference.org.
- *
- * Similarly, Proj4js must have access to projection transform code.  These can
- * be included individually using a <script> tag in your page, built into a 
- * custom build of Proj4js or loaded dynamically at run-time.  Using the
- * -combined and -compressed versions of Proj4js includes all projection class
- * code by default.
- *
- * Note that dynamic loading of defs and code happens ascynchrously, check the
- * Proj.readyToUse flag before using the Proj object.  If the defs and code
- * required by your application are loaded through script tags, dynamic loading
- * is not required and the Proj object will be readyToUse on return from the 
- * constructor.
- * 
- * All coordinates are handled as points which have a .x and a .y property
- * which will be modified in place.
- *
- * Override Proj4js.reportError for output of alerts and warnings.
- *
- * See http://trac.osgeo.org/proj4js/wiki/UserGuide for full details.
-*/
-
-/**
- * Global namespace object for Proj4js library
- */
-Proj4js = {
-
-    /**
-     * Property: defaultDatum
-     * The datum to use when no others a specified
-     */
-    defaultDatum: 'WGS84',                  //default datum
-
-    /** 
-    * Method: transform(source, dest, point)
-    * Transform a point coordinate from one map projection to another.  This is
-    * really the only public method you should need to use.
-    *
-    * Parameters:
-    * source - {Proj4js.Proj} source map projection for the transformation
-    * dest - {Proj4js.Proj} destination map projection for the transformation
-    * point - {Object} point to transform, may be geodetic (long, lat) or
-    *     projected Cartesian (x,y), but should always have x,y properties.
-    */
-    transform: function(source, dest, point) {
-        if (!source.readyToUse) {
-            this.reportError("Proj4js initialization for:"+source.srsCode+" not yet complete");
-            return point;
-        }
-        if (!dest.readyToUse) {
-            this.reportError("Proj4js initialization for:"+dest.srsCode+" not yet complete");
-            return point;
-        }
-        
-        // Workaround for Spherical Mercator
-        if ((source.srsProjNumber =="900913" && dest.datumCode != "WGS84") ||
-            (dest.srsProjNumber == "900913" && source.datumCode != "WGS84")) {
-            var wgs84 = Proj4js.WGS84;
-            this.transform(source, wgs84, point);
-            source = wgs84;
-        }
-
-        // Transform source points to long/lat, if they aren't already.
-        if ( source.projName=="longlat") {
-            point.x *= Proj4js.common.D2R;  // convert degrees to radians
-            point.y *= Proj4js.common.D2R;
-        } else {
-            if (source.to_meter) {
-                point.x *= source.to_meter;
-                point.y *= source.to_meter;
-            }
-            source.inverse(point); // Convert Cartesian to longlat
-        }
-
-        // Adjust for the prime meridian if necessary
-        if (source.from_greenwich) { 
-            point.x += source.from_greenwich; 
-        }
-
-        // Convert datums if needed, and if possible.
-        point = this.datum_transform( source.datum, dest.datum, point );
-
-        // Adjust for the prime meridian if necessary
-        if (dest.from_greenwich) {
-            point.x -= dest.from_greenwich;
-        }
-
-        if( dest.projName=="longlat" ) {             
-            // convert radians to decimal degrees
-            point.x *= Proj4js.common.R2D;
-            point.y *= Proj4js.common.R2D;
-        } else  {               // else project
-            dest.forward(point);
-            if (dest.to_meter) {
-                point.x /= dest.to_meter;
-                point.y /= dest.to_meter;
-            }
-        }
-        return point;
-    }, // transform()
-
-    /** datum_transform()
-      source coordinate system definition,
-      destination coordinate system definition,
-      point to transform in geodetic coordinates (long, lat, height)
-    */
-    datum_transform : function( source, dest, point ) {
-
-      // Short cut if the datums are identical.
-      if( source.compare_datums( dest ) ) {
-          return point; // in this case, zero is sucess,
-                    // whereas cs_compare_datums returns 1 to indicate TRUE
-                    // confusing, should fix this
-      }
-
-      // Explicitly skip datum transform by setting 'datum=none' as parameter for either source or dest
-      if( source.datum_type == Proj4js.common.PJD_NODATUM
-          || dest.datum_type == Proj4js.common.PJD_NODATUM) {
-          return point;
-      }
-
-      // If this datum requires grid shifts, then apply it to geodetic coordinates.
-      if( source.datum_type == Proj4js.common.PJD_GRIDSHIFT )
-      {
-        alert("ERROR: Grid shift transformations are not implemented yet.");
-        /*
-          pj_apply_gridshift( pj_param(source.params,"snadgrids").s, 0,
-                              point_count, point_offset, x, y, z );
-          CHECK_RETURN;
-
-          src_a = SRS_WGS84_SEMIMAJOR;
-          src_es = 0.006694379990;
-        */
-      }
-
-      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
-      {
-        alert("ERROR: Grid shift transformations are not implemented yet.");
-        /*
-          dst_a = ;
-          dst_es = 0.006694379990;
-        */
-      }
-
-      // Do we need to go through geocentric coordinates?
-      if( source.es != dest.es || source.a != dest.a
-          || source.datum_type == Proj4js.common.PJD_3PARAM
-          || source.datum_type == Proj4js.common.PJD_7PARAM
-          || dest.datum_type == Proj4js.common.PJD_3PARAM
-          || dest.datum_type == Proj4js.common.PJD_7PARAM)
-      {
-
-        // Convert to geocentric coordinates.
-        source.geodetic_to_geocentric( point );
-        // CHECK_RETURN;
-
-        // Convert between datums
-        if( source.datum_type == Proj4js.common.PJD_3PARAM || source.datum_type == Proj4js.common.PJD_7PARAM ) {
-          source.geocentric_to_wgs84(point);
-          // CHECK_RETURN;
-        }
-
-        if( dest.datum_type == Proj4js.common.PJD_3PARAM || dest.datum_type == Proj4js.common.PJD_7PARAM ) {
-          dest.geocentric_from_wgs84(point);
-          // CHECK_RETURN;
-        }
-
-        // Convert back to geodetic coordinates
-        dest.geocentric_to_geodetic( point );
-          // CHECK_RETURN;
-      }
-
-      // Apply grid shift to destination if required
-      if( dest.datum_type == Proj4js.common.PJD_GRIDSHIFT )
-      {
-        alert("ERROR: Grid shift transformations are not implemented yet.");
-        // pj_apply_gridshift( pj_param(dest.params,"snadgrids").s, 1, point);
-        // CHECK_RETURN;
-      }
-      return point;
-    }, // cs_datum_transform
-
-    /**
-     * Function: reportError
-     * An internal method to report errors back to user. 
-     * Override this in applications to report error messages or throw exceptions.
-     */
-    reportError: function(msg) {
-      //console.log(msg);
-    },
-
-/**
- *
- * Title: Private Methods
- * The following properties and methods are intended for internal use only.
- *
- * This is a minimal implementation of JavaScript inheritance methods so that 
- * Proj4js can be used as a stand-alone library.
- * These are copies of the equivalent OpenLayers methods at v2.7
- */
- 
-/**
- * Function: extend
- * Copy all properties of a source object to a destination object.  Modifies
- *     the passed in destination object.  Any properties on the source object
- *     that are set to undefined will not be (re)set on the destination object.
- *
- * Parameters:
- * destination - {Object} The object that will be modified
- * source - {Object} The object with properties to be set on the destination
- *
- * Returns:
- * {Object} The destination object.
- */
-    extend: function(destination, source) {
-      destination = destination || {};
-      if(source) {
-          for(var property in source) {
-              var value = source[property];
-              if(value !== undefined) {
-                  destination[property] = value;
-              }
-          }
-      }
-      return destination;
-    },
-
-/**
- * Constructor: Class
- * Base class used to construct all other classes. Includes support for 
- *     multiple inheritance. 
- *  
- */
-    Class: function() {
-      var Class = function() {
-          this.initialize.apply(this, arguments);
-      };
-  
-      var extended = {};
-      var parent;
-      for(var i=0; i<arguments.length; ++i) {
-          if(typeof arguments[i] == "function") {
-              // get the prototype of the superclass
-              parent = arguments[i].prototype;
-          } else {
-              // in this case we're extending with the prototype
-              parent = arguments[i];
-          }
-          Proj4js.extend(extended, parent);
-      }
-      Class.prototype = extended;
-      
-      return Class;
-    },
-
-    /**
-     * Function: bind
-     * Bind a function to an object.  Method to easily create closures with
-     *     'this' altered.
-     * 
-     * Parameters:
-     * func - {Function} Input function.
-     * object - {Object} The object to bind to the input function (as this).
-     * 
-     * Returns:
-     * {Function} A closure with 'this' set to the passed in object.
-     */
-    bind: function(func, object) {
-        // create a reference to all arguments past the second one
-        var args = Array.prototype.slice.apply(arguments, [2]);
-        return function() {
-            // Push on any additional arguments from the actual function call.
-            // These will come after those sent to the bind call.
-            var newArgs = args.concat(
-                Array.prototype.slice.apply(arguments, [0])
-            );
-            return func.apply(object, newArgs);
-        };
-    },
-    
-/**
- * The following properties and methods handle dynamic loading of JSON objects.
- *
-    /**
-     * Property: scriptName
-     * {String} The filename of this script without any path.
-     */
-    scriptName: "proj4js-combined.js",
-
-    /**
-     * Property: defsLookupService
-     * AJAX service to retreive projection definition parameters from
-     */
-    defsLookupService: 'http://spatialreference.org/ref',
-
-    /**
-     * Property: libPath
-     * internal: http server path to library code.
-     */
-    libPath: null,
-
-    /**
-     * Function: getScriptLocation
-     * Return the path to this script.
-     *
-     * Returns:
-     * Path to this script
-     */
-    getScriptLocation: function () {
-        if (this.libPath) return this.libPath;
-        var scriptName = this.scriptName;
-        var scriptNameLen = scriptName.length;
-
-        var scripts = document.getElementsByTagName('script');
-        for (var i = 0; i < scripts.length; i++) {
-            var src = scripts[i].getAttribute('src');
-            if (src) {
-                var index = src.lastIndexOf(scriptName);
-                // is it found, at the end of the URL?
-                if ((index > -1) && (index + scriptNameLen == src.length)) {
-                    this.libPath = src.slice(0, -scriptNameLen);
-                    break;
-                }
-            }
-        }
-        return this.libPath||"";
-    },
-
-    /**
-     * Function: loadScript
-     * Load a JS file from a URL into a <script> tag in the page.
-     * 
-     * Parameters:
-     * url - {String} The URL containing the script to load
-     * onload - {Function} A method to be executed when the script loads successfully
-     * onfail - {Function} A method to be executed when there is an error loading the script
-     * loadCheck - {Function} A boolean method that checks to see if the script 
-     *            has loaded.  Typically this just checks for the existance of
-     *            an object in the file just loaded.
-     */
-    loadScript: function(url, onload, onfail, loadCheck) {
-      var script = document.createElement('script');
-      script.defer = false;
-      script.type = "text/javascript";
-      script.id = url;
-      script.src = url;
-      script.onload = onload;
-      script.onerror = onfail;
-      script.loadCheck = loadCheck;
-      if (/MSIE/.test(navigator.userAgent)) {
-        script.onreadystatechange = this.checkReadyState;
-      }
-      document.getElementsByTagName('head')[0].appendChild(script);
-    },
-    
-    /**
-     * Function: checkReadyState
-     * IE workaround since there is no onerror handler.  Calls the user defined 
-     * loadCheck method to determine if the script is loaded.
-     * 
-     */
-    checkReadyState: function() {
-      if (this.readyState == 'loaded') {
-        if (!this.loadCheck()) {
-          this.onerror();
-        } else {
-          this.onload();
-        }
-      }
-    }
-};
-
-/**
- * Class: Proj4js.Proj
- *
- * Proj objects provide transformation methods for point coordinates
- * between geodetic latitude/longitude and a projected coordinate system. 
- * once they have been initialized with a projection code.
- *
- * Initialization of Proj objects is with a projection code, usually EPSG codes,
- * which is the key that will be used with the Proj4js.defs array.
- * 
- * The code passed in will be stripped of colons and converted to uppercase
- * to locate projection definition files.
- *
- * A projection object has properties for units and title strings.
- */
-Proj4js.Proj = Proj4js.Class({
-
-  /**
-   * Property: readyToUse
-   * Flag to indicate if initialization is complete for this Proj object
-   */
-  readyToUse: false,   
-  
-  /**
-   * Property: title
-   * The title to describe the projection
-   */
-  title: null,  
-  
-  /**
-   * Property: projName
-   * The projection class for this projection, e.g. lcc (lambert conformal conic,
-   * or merc for mercator).  These are exactly equivalent to their Proj4 
-   * counterparts.
-   */
-  projName: null,
-  /**
-   * Property: units
-   * The units of the projection.  Values include 'm' and 'degrees'
-   */
-  units: null,
-  /**
-   * Property: datum
-   * The datum specified for the projection
-   */
-  datum: null,
-  /**
-   * Property: x0
-   * The x coordinate origin
-   */
-  x0: 0,
-  /**
-   * Property: y0
-   * The y coordinate origin
-   */
-  y0: 0,
-  /**
-   * Property: localCS
-   * Flag to indicate if the projection is a local one in which no transforms
-   * are required.
-   */
-  localCS: false,
-
-  /**
-   * Constructor: initialize
-   * Constructor for Proj4js.Proj objects
-  *
-  * Parameters:
-  * srsCode - a code for map projection definition parameters.  These are usually
-  * (but not always) EPSG codes.
-  */
-  initialize: function(srsCode) {
-      this.srsCodeInput = srsCode;
-      
-      //check to see if this is a WKT string
-      if ((srsCode.indexOf('GEOGCS') >= 0) ||
-          (srsCode.indexOf('GEOCCS') >= 0) ||
-          (srsCode.indexOf('PROJCS') >= 0) ||
-          (srsCode.indexOf('LOCAL_CS') >= 0)) {
-            this.parseWKT(srsCode);
-            this.datum = new Proj4js.datum(this);
-            this.loadProjCode(this.projName);
-            return;
-      }
-      
-      // DGR 2008-08-03 : support urn and url
-      if (srsCode.indexOf('urn:') == 0) {
-          //urn:ORIGINATOR:def:crs:CODESPACE:VERSION:ID
-          var urn = srsCode.split(':');
-          if ((urn[1] == 'ogc' || urn[1] =='x-ogc') &&
-              (urn[2] =='def') &&
-              (urn[3] =='crs')) {
-              srsCode = urn[4]+':'+urn[urn.length-1];
-          }
-      } else if (srsCode.indexOf('http://') == 0) {
-          //url#ID
-          var url = srsCode.split('#');
-          if (url[0].match(/epsg.org/)) {
-            // http://www.epsg.org/#
-            srsCode = 'EPSG:'+url[1];
-          } else if (url[0].match(/RIG.xml/)) {
-            //http://librairies.ign.fr/geoportail/resources/RIG.xml#
-            //http://interop.ign.fr/registers/ign/RIG.xml#
-            srsCode = 'IGNF:'+url[1];
-          }
-      }
-      this.srsCode = srsCode.toUpperCase();
-      if (this.srsCode.indexOf("EPSG") == 0) {
-          this.srsCode = this.srsCode;
-          this.srsAuth = 'epsg';
-          this.srsProjNumber = this.srsCode.substring(5);
-      // DGR 2007-11-20 : authority IGNF
-      } else if (this.srsCode.indexOf("IGNF") == 0) {
-          this.srsCode = this.srsCode;
-          this.srsAuth = 'IGNF';
-          this.srsProjNumber = this.srsCode.substring(5);
-      // DGR 2008-06-19 : pseudo-authority CRS for WMS
-      } else if (this.srsCode.indexOf("CRS") == 0) {
-          this.srsCode = this.srsCode;
-          this.srsAuth = 'CRS';
-          this.srsProjNumber = this.srsCode.substring(4);
-      } else {
-          this.srsAuth = '';
-          this.srsProjNumber = this.srsCode;
-      }
-      this.loadProjDefinition();
-  },
-  
-/**
- * Function: loadProjDefinition
- *    Loads the coordinate system initialization string if required.
- *    Note that dynamic loading happens asynchronously so an application must 
- *    wait for the readyToUse property is set to true.
- *    To prevent dynamic loading, include the defs through a script tag in
- *    your application.
- *
- */
-    loadProjDefinition: function() {
-      //check in memory
-      if (Proj4js.defs[this.srsCode]) {
-        this.defsLoaded();
-        return;
-      }
-
-      //else check for def on the server
-      var url = Proj4js.getScriptLocation() + 'defs/' + this.srsAuth.toUpperCase() + this.srsProjNumber + '.js';
-      Proj4js.loadScript(url, 
-                Proj4js.bind(this.defsLoaded, this),
-                Proj4js.bind(this.loadFromService, this),
-                Proj4js.bind(this.checkDefsLoaded, this) );
-    },
-
-/**
- * Function: loadFromService
- *    Creates the REST URL for loading the definition from a web service and 
- *    loads it.
- *
- */
-    loadFromService: function() {
-      //else load from web service
-      var url = Proj4js.defsLookupService +'/' + this.srsAuth +'/'+ this.srsProjNumber + '/proj4js/';
-      Proj4js.loadScript(url, 
-            Proj4js.bind(this.defsLoaded, this),
-            Proj4js.bind(this.defsFailed, this),
-            Proj4js.bind(this.checkDefsLoaded, this) );
-    },
-
-/**
- * Function: defsLoaded
- * Continues the Proj object initilization once the def file is loaded
- *
- */
-    defsLoaded: function() {
-      this.parseDefs();
-      this.loadProjCode(this.projName);
-    },
-    
-/**
- * Function: checkDefsLoaded
- *    This is the loadCheck method to see if the def object exists
- *
- */
-    checkDefsLoaded: function() {
-      if (Proj4js.defs[this.srsCode]) {
-        return true;
-      } else {
-        return false;
-      }
-    },
-
- /**
- * Function: defsFailed
- *    Report an error in loading the defs file, but continue on using WGS84
- *
- */
-   defsFailed: function() {
-      Proj4js.reportError('failed to load projection definition for: '+this.srsCode);
-      Proj4js.defs[this.srsCode] = Proj4js.defs['WGS84'];  //set it to something so it can at least continue
-      this.defsLoaded();
-    },
-
-/**
- * Function: loadProjCode
- *    Loads projection class code dynamically if required.
- *     Projection code may be included either through a script tag or in
- *     a built version of proj4js
- *
- */
-    loadProjCode: function(projName) {
-      if (Proj4js.Proj[projName]) {
-        this.initTransforms();
-        return;
-      }
-
-      //the URL for the projection code
-      var url = Proj4js.getScriptLocation() + 'projCode/' + projName + '.js';
-      Proj4js.loadScript(url, 
-              Proj4js.bind(this.loadProjCodeSuccess, this, projName),
-              Proj4js.bind(this.loadProjCodeFailure, this, projName), 
-              Proj4js.bind(this.checkCodeLoaded, this, projName) );
-    },
-
- /**
- * Function: loadProjCodeSuccess
- *    Loads any proj dependencies or continue on to final initialization.
- *
- */
-    loadProjCodeSuccess: function(projName) {
-      if (Proj4js.Proj[projName].dependsOn){
-        this.loadProjCode(Proj4js.Proj[projName].dependsOn);
-      } else {
-        this.initTransforms();
-      }
-    },
-
- /**
- * Function: defsFailed
- *    Report an error in loading the proj file.  Initialization of the Proj
- *    object has failed and the readyToUse flag will never be set.
- *
- */
-    loadProjCodeFailure: function(projName) {
-      Proj4js.reportError("failed to find projection file for: " + projName);
-      //TBD initialize with identity transforms so proj will still work?
-    },
-    
-/**
- * Function: checkCodeLoaded
- *    This is the loadCheck method to see if the projection code is loaded
- *
- */
-    checkCodeLoaded: function(projName) {
-      if (Proj4js.Proj[projName]) {
-        return true;
-      } else {
-        return false;
-      }
-    },
-
-/**
- * Function: initTransforms
- *    Finalize the initialization of the Proj object
- *
- */
-    initTransforms: function() {
-      Proj4js.extend(this, Proj4js.Proj[this.projName]);
-      this.init();
-      this.readyToUse = true;
-  },
-
-/**
- * Function: parseWKT
- * Parses a WKT string to get initialization parameters
- *
- */
- wktRE: /^(\w+)\[(.*)\]$/,
- parseWKT: function(wkt) {
-    var wktMatch = wkt.match(this.wktRE);
-    if (!wktMatch) return;
-    var wktObject = wktMatch[1];
-    var wktContent = wktMatch[2];
-    var wktTemp = wktContent.split(",");
-    var wktName = wktTemp.shift();
-    wktName = wktName.replace(/^\"/,"");
-    wktName = wktName.replace(/\"$/,"");
-    
-    /*
-    wktContent = wktTemp.join(",");
-    var wktArray = wktContent.split("],");
-    for (var i=0; i<wktArray.length-1; ++i) {
-      wktArray[i] += "]";
-    }
-    */
-    
-    var wktArray = new Array();
-    var bkCount = 0;
-    var obj = "";
-    for (var i=0; i<wktTemp.length; ++i) {
-      var token = wktTemp[i];
-      for (var j=0; j<token.length; ++j) {
-        if (token.charAt(j) == "[") ++bkCount;
-        if (token.charAt(j) == "]") --bkCount;
-      }
-      obj += token;
-      if (bkCount === 0) {
-        wktArray.push(obj);
-        obj = "";
-      } else {
-        obj += ",";
-      }
-    }
-    
-    //do something based on the type of the wktObject being parsed
-    //add in variations in the spelling as required
-    switch (wktObject) {
-      case 'LOCAL_CS':
-        this.projName = 'identity'
-        this.localCS = true;
-        this.srsCode = wktName;
-        break;
-      case 'GEOGCS':
-        this.projName = 'longlat'
-        this.geocsCode = wktName;
-        if (!this.srsCode) this.srsCode = wktName;
-        break;
-      case 'PROJCS':
-        this.srsCode = wktName;
-        break;
-      case 'GEOCCS':
-        break;
-      case 'PROJECTION':
-        this.projName = Proj4js.wktProjections[wktName]
-        break;
-      case 'DATUM':
-        this.datumName = wktName;
-        break;
-      case 'LOCAL_DATUM':
-        this.datumCode = 'none';
-        break;
-      case 'SPHEROID':
-        this.ellps = wktName;
-        this.a = parseFloat(wktArray.shift());
-        this.rf = parseFloat(wktArray.shift());
-        break;
-      case 'PRIMEM':
-        this.from_greenwich = parseFloat(wktArray.shift()); //to radians?
-        break;
-      case 'UNIT':
-        this.units = wktName;
-        this.unitsPerMeter = parseFloat(wktArray.shift());
-        break;
-      case 'PARAMETER':
-        var name = wktName;
-        var value = parseFloat(parseFloat(wktArray.shift()));
-        //there amy be many variations on the wktName values, add in case
-        //statements as required
-        switch (name) {
-          case 'false_easting':
-            this.x0 = value;
-            break;
-          case 'false_northing':
-            this.y0 = value;
-            break;
-          case 'scale_factor':
-            this.k0 = value;
-            break;
-          case 'central_meridian':
-            this.long0 = value;
-            break;
-          case 'latitude_of_origin':
-            this.lat0 = value;
-            break;
-          case 'more_here':
-            break;
-          default:
-            break;
-        }
-        break;
-      case 'TOWGS84':
-        this.datum_params = wktArray;
-        break;
-      case 'MORE_HERE':
-        break;
-      default:
-        break;
-    }
-    for (var i=0; i<wktArray.length; ++i) {
-      this.parseWKT(wktArray[i]);
-    }
- },
-
-/**
- * Function: parseDefs
- * Parses the PROJ.4 initialization string and sets the associated properties.
- *
- */
-  parseDefs: function() {
-      this.defData = Proj4js.defs[this.srsCode];
-      var paramName, paramVal;
-      if (!this.defData) {
-        return;
-      }
-      var paramArray=this.defData.split("+");
-
-      for (var prop=0; prop<paramArray.length; prop++) {
-          var property = paramArray[prop].split("=");
-          paramName = property[0].toLowerCase();
-          paramVal = property[1];
-
-          switch (paramName.replace(/\s/gi,"")) {  // trim out spaces
-              case "": break;   // throw away nameless parameter
-              case "title":  this.title = paramVal; break;
-              case "proj":   this.projName =  paramVal.replace(/\s/gi,""); break;
-              case "units":  this.units = paramVal.replace(/\s/gi,""); break;
-              case "datum":  this.datumCode = paramVal.replace(/\s/gi,""); break;
-              case "nadgrids": this.nagrids = paramVal.replace(/\s/gi,""); break;
-              case "ellps":  this.ellps = paramVal.replace(/\s/gi,""); break;
-              case "a":      this.a =  parseFloat(paramVal); break;  // semi-major radius
-              case "b":      this.b =  parseFloat(paramVal); break;  // semi-minor radius
-              // DGR 2007-11-20
-              case "rf":     this.rf = parseFloat(paramVal); break; // inverse flattening rf= a/(a-b)
-              case "lat_0":  this.lat0 = paramVal*Proj4js.common.D2R; break;        // phi0, central latitude
-              case "lat_1":  this.lat1 = paramVal*Proj4js.common.D2R; break;        //standard parallel 1
-              case "lat_2":  this.lat2 = paramVal*Proj4js.common.D2R; break;        //standard parallel 2
-              case "lat_ts": this.lat_ts = paramVal*Proj4js.common.D2R; break;      // used in merc and eqc
-              case "lon_0":  this.long0 = paramVal*Proj4js.common.D2R; break;       // lam0, central longitude
-              case "alpha":  this.alpha =  parseFloat(paramVal)*Proj4js.common.D2R; break;  //for somerc projection
-              case "lonc":   this.longc = paramVal*Proj4js.common.D2R; break;       //for somerc projection
-              case "x_0":    this.x0 = parseFloat(paramVal); break;  // false easting
-              case "y_0":    this.y0 = parseFloat(paramVal); break;  // false northing
-              case "k_0":    this.k0 = parseFloat(paramVal); break;  // projection scale factor
-              case "k":      this.k0 = parseFloat(paramVal); break;  // both forms returned
-              case "r_a":    this.R_A = true; break;                 // sphere--area of ellipsoid
-              case "zone":   this.zone = parseInt(paramVal); break;  // UTM Zone
-              case "south":   this.utmSouth = true; break;  // UTM north/south
-              case "towgs84":this.datum_params = paramVal.split(","); break;
-              case "to_meter": this.to_meter = parseFloat(paramVal); break; // cartesian scaling
-              case "from_greenwich": this.from_greenwich = paramVal*Proj4js.common.D2R; break;
-              // DGR 2008-07-09 : if pm is not a well-known prime meridian take
-              // the value instead of 0.0, then convert to radians
-              case "pm":     paramVal = paramVal.replace(/\s/gi,"");
-                             this.from_greenwich = Proj4js.PrimeMeridian[paramVal] ?
-                                Proj4js.PrimeMeridian[paramVal] : parseFloat(paramVal);
-                             this.from_greenwich *= Proj4js.common.D2R; 
-                             break;
-              case "no_defs": break; 
-              default: //alert("Unrecognized parameter: " + paramName);
-          } // switch()
-      } // for paramArray
-      this.deriveConstants();
-  },
-
-/**
- * Function: deriveConstants
- * Sets several derived constant values and initialization of datum and ellipse
- *     parameters.
- *
- */
-  deriveConstants: function() {
-      if (this.nagrids == '@null') this.datumCode = 'none';
-      if (this.datumCode && this.datumCode != 'none') {
-        var datumDef = Proj4js.Datum[this.datumCode];
-        if (datumDef) {
-          this.datum_params = datumDef.towgs84 ? datumDef.towgs84.split(',') : null;
-          this.ellps = datumDef.ellipse;
-          this.datumName = datumDef.datumName ? datumDef.datumName : this.datumCode;
-        }
-      }
-      if (!this.a) {    // do we have an ellipsoid?
-          var ellipse = Proj4js.Ellipsoid[this.ellps] ? Proj4js.Ellipsoid[this.ellps] : Proj4js.Ellipsoid['WGS84'];
-          Proj4js.extend(this, ellipse);
-      }
-      if (this.rf && !this.b) this.b = (1.0 - 1.0/this.rf) * this.a;
-      if (Math.abs(this.a - this.b)<Proj4js.common.EPSLN) {
-        this.sphere = true;
-        this.b= this.a;
-      }
-      this.a2 = this.a * this.a;          // used in geocentric
-      this.b2 = this.b * this.b;          // used in geocentric
-      this.es = (this.a2-this.b2)/this.a2;  // e ^ 2
-      this.e = Math.sqrt(this.es);        // eccentricity
-      if (this.R_A) {
-        this.a *= 1. - this.es * (Proj4js.common.SIXTH + this.es * (Proj4js.common.RA4 + this.es * Proj4js.common.RA6));
-        this.a2 = this.a * this.a;
-        this.b2 = this.b * this.b;
-        this.es = 0.;
-      }
-      this.ep2=(this.a2-this.b2)/this.b2; // used in geocentric
-      if (!this.k0) this.k0 = 1.0;    //default value
-
-      this.datum = new Proj4js.datum(this);
-  }
-});
-
-Proj4js.Proj.longlat = {
-  init: function() {
-    //no-op for longlat
-  },
-  forward: function(pt) {
-    //identity transform
-    return pt;
-  },
-  inverse: function(pt) {
-    //identity transform
-    return pt;
-  }
-};
-Proj4js.Proj.identity = Proj4js.Proj.longlat;
-
-/**
-  Proj4js.defs is a collection of coordinate system definition objects in the 
-  PROJ.4 command line format.
-  Generally a def is added by means of a separate .js file for example:
-
-    <SCRIPT type="text/javascript" src="defs/EPSG26912.js"></SCRIPT>
-
-  def is a CS definition in PROJ.4 WKT format, for example:
-    +proj="tmerc"   //longlat, etc.
-    +a=majorRadius
-    +b=minorRadius
-    +lat0=somenumber
-    +long=somenumber
-*/
-Proj4js.defs = {
-  // These are so widely used, we'll go ahead and throw them in
-  // without requiring a separate .js file
-  'WGS84': "+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",
-  'EPSG:4326': "+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",
-  'EPSG:4269': "+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",
-  'EPSG:3785': "+title= Google Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"
-};
-Proj4js.defs['GOOGLE'] = Proj4js.defs['EPSG:3785'];
-Proj4js.defs['EPSG:900913'] = Proj4js.defs['EPSG:3785'];
-Proj4js.defs['EPSG:102113'] = Proj4js.defs['EPSG:3785'];
-
-Proj4js.common = {
-  PI : 3.141592653589793238, //Math.PI,
-  HALF_PI : 1.570796326794896619, //Math.PI*0.5,
-  TWO_PI : 6.283185307179586477, //Math.PI*2,
-  FORTPI : 0.78539816339744833,
-  R2D : 57.29577951308232088,
-  D2R : 0.01745329251994329577,
-  SEC_TO_RAD : 4.84813681109535993589914102357e-6, /* SEC_TO_RAD = Pi/180/3600 */
-  EPSLN : 1.0e-10,
-  MAX_ITER : 20,
-  // following constants from geocent.c
-  COS_67P5 : 0.38268343236508977,  /* cosine of 67.5 degrees */
-  AD_C : 1.0026000,                /* Toms region 1 constant */
-
-  /* datum_type values */
-  PJD_UNKNOWN  : 0,
-  PJD_3PARAM   : 1,
-  PJD_7PARAM   : 2,
-  PJD_GRIDSHIFT: 3,
-  PJD_WGS84    : 4,   // WGS84 or equivalent
-  PJD_NODATUM  : 5,   // WGS84 or equivalent
-  SRS_WGS84_SEMIMAJOR : 6378137.0,  // only used in grid shift transforms
-
-  // ellipoid pj_set_ell.c
-  SIXTH : .1666666666666666667, /* 1/6 */
-  RA4   : .04722222222222222222, /* 17/360 */
-  RA6   : .02215608465608465608, /* 67/3024 */
-  RV4   : .06944444444444444444, /* 5/72 */
-  RV6   : .04243827160493827160, /* 55/1296 */
-
-// Function to compute the constant small m which is the radius of
-//   a parallel of latitude, phi, divided by the semimajor axis.
-// -----------------------------------------------------------------
-  msfnz : function(eccent, sinphi, cosphi) {
-      var con = eccent * sinphi;
-      return cosphi/(Math.sqrt(1.0 - con * con));
-  },
-
-// Function to compute the constant small t for use in the forward
-//   computations in the Lambert Conformal Conic and the Polar
-//   Stereographic projections.
-// -----------------------------------------------------------------
-  tsfnz : function(eccent, phi, sinphi) {
-    var con = eccent * sinphi;
-    var com = .5 * eccent;
-    con = Math.pow(((1.0 - con) / (1.0 + con)), com);
-    return (Math.tan(.5 * (this.HALF_PI - phi))/con);
-  },
-
-// Function to compute the latitude angle, phi2, for the inverse of the
-//   Lambert Conformal Conic and Polar Stereographic projections.
-// ----------------------------------------------------------------
-  phi2z : function(eccent, ts) {
-    var eccnth = .5 * eccent;
-    var con, dphi;
-    var phi = this.HALF_PI - 2 * Math.atan(ts);
-    for (var i = 0; i <= 15; i++) {
-      con = eccent * Math.sin(phi);
-      dphi = this.HALF_PI - 2 * Math.atan(ts *(Math.pow(((1.0 - con)/(1.0 + con)),eccnth))) - phi;
-      phi += dphi;
-      if (Math.abs(dphi) <= .0000000001) return phi;
-    }
-    alert("phi2z has NoConvergence");
-    return (-9999);
-  },
-
-/* Function to compute constant small q which is the radius of a 
-   parallel of latitude, phi, divided by the semimajor axis. 
-------------------------------------------------------------*/
-  qsfnz : function(eccent,sinphi) {
-    var con;
-    if (eccent > 1.0e-7) {
-      con = eccent * sinphi;
-      return (( 1.0- eccent * eccent) * (sinphi /(1.0 - con * con) - (.5/eccent)*Math.log((1.0 - con)/(1.0 + con))));
-    } else {
-      return(2.0 * sinphi);
-    }
-  },
-
-/* Function to eliminate roundoff errors in asin
-----------------------------------------------*/
-  asinz : function(x) {
-    if (Math.abs(x)>1.0) {
-      x=(x>1.0)?1.0:-1.0;
-    }
-    return Math.asin(x);
-  },
-
-// following functions from gctpc cproj.c for transverse mercator projections
-  e0fn : function(x) {return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},
-  e1fn : function(x) {return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},
-  e2fn : function(x) {return(0.05859375*x*x*(1.0+0.75*x));},
-  e3fn : function(x) {return(x*x*x*(35.0/3072.0));},
-  mlfn : function(e0,e1,e2,e3,phi) {return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},
-
-  srat : function(esinp, exp) {
-    return(Math.pow((1.0-esinp)/(1.0+esinp), exp));
-  },
-
-// Function to return the sign of an argument
-  sign : function(x) { if (x < 0.0) return(-1); else return(1);},
-
-// Function to adjust longitude to -180 to 180; input in radians
-  adjust_lon : function(x) {
-    x = (Math.abs(x) < this.PI) ? x: (x - (this.sign(x)*this.TWO_PI) );
-    return x;
-  },
-
-// IGNF - DGR : algorithms used by IGN France
-
-// Function to adjust latitude to -90 to 90; input in radians
-  adjust_lat : function(x) {
-    x= (Math.abs(x) < this.HALF_PI) ? x: (x - (this.sign(x)*this.PI) );
-    return x;
-  },
-
-// Latitude Isometrique - close to tsfnz ...
-  latiso : function(eccent, phi, sinphi) {
-    if (Math.abs(phi) > this.HALF_PI) return +Number.NaN;
-    if (phi==this.HALF_PI) return Number.POSITIVE_INFINITY;
-    if (phi==-1.0*this.HALF_PI) return -1.0*Number.POSITIVE_INFINITY;
-
-    var con= eccent*sinphi;
-    return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;
-  },
-
-  fL : function(x,L) {
-    return 2.0*Math.atan(x*Math.exp(L)) - this.HALF_PI;
-  },
-
-// Inverse Latitude Isometrique - close to ph2z
-  invlatiso : function(eccent, ts) {
-    var phi= this.fL(1.0,ts);
-    var Iphi= 0.0;
-    var con= 0.0;
-    do {
-      Iphi= phi;
-      con= eccent*Math.sin(Iphi);
-      phi= this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)
-    } while (Math.abs(phi-Iphi)>1.0e-12);
-    return phi;
-  },
-
-// Needed for Gauss Schreiber
-// Original:  Denis Makarov (info at binarythings.com)
-// Web Site:  http://www.binarythings.com
-  sinh : function(x)
-  {
-    var r= Math.exp(x);
-    r= (r-1.0/r)/2.0;
-    return r;
-  },
-
-  cosh : function(x)
-  {
-    var r= Math.exp(x);
-    r= (r+1.0/r)/2.0;
-    return r;
-  },
-
-  tanh : function(x)
-  {
-    var r= Math.exp(x);
-    r= (r-1.0/r)/(r+1.0/r);
-    return r;
-  },
-
-  asinh : function(x)
-  {
-    var s= (x>= 0? 1.0:-1.0);
-    return s*(Math.log( Math.abs(x) + Math.sqrt(x*x+1.0) ));
-  },
-
-  acosh : function(x)
-  {
-    return 2.0*Math.log(Math.sqrt((x+1.0)/2.0) + Math.sqrt((x-1.0)/2.0));
-  },
-
-  atanh : function(x)
-  {
-    return Math.log((x-1.0)/(x+1.0))/2.0;
-  },
-
-// Grande Normale
-  gN : function(a,e,sinphi)
-  {
-    var temp= e*sinphi;
-    return a/Math.sqrt(1.0 - temp*temp);
-  }
-
-};
-
-/** datum object
-*/
-Proj4js.datum = Proj4js.Class({
-
-  initialize : function(proj) {
-    this.datum_type = Proj4js.common.PJD_WGS84;   //default setting
-    if (proj.datumCode && proj.datumCode == 'none') {
-      this.datum_type = Proj4js.common.PJD_NODATUM;
-    }
-    if (proj && proj.datum_params) {
-      for (var i=0; i<proj.datum_params.length; i++) {
-        proj.datum_params[i]=parseFloat(proj.datum_params[i]);
-      }
-      if (proj.datum_params[0] != 0 || proj.datum_params[1] != 0 || proj.datum_params[2] != 0 ) {
-        this.datum_type = Proj4js.common.PJD_3PARAM;
-      }
-      if (proj.datum_params.length > 3) {
-        if (proj.datum_params[3] != 0 || proj.datum_params[4] != 0 ||
-            proj.datum_params[5] != 0 || proj.datum_params[6] != 0 ) {
-          this.datum_type = Proj4js.common.PJD_7PARAM;
-          proj.datum_params[3] *= Proj4js.common.SEC_TO_RAD;
-          proj.datum_params[4] *= Proj4js.common.SEC_TO_RAD;
-          proj.datum_params[5] *= Proj4js.common.SEC_TO_RAD;
-          proj.datum_params[6] = (proj.datum_params[6]/1000000.0) + 1.0;
-        }
-      }
-    }
-    if (proj) {
-      this.a = proj.a;    //datum object also uses these values
-      this.b = proj.b;
-      this.es = proj.es;
-      this.ep2 = proj.ep2;
-      this.datum_params = proj.datum_params;
-    }
-  },
-
-  /****************************************************************/
-  // cs_compare_datums()
-  //   Returns 1 (TRUE) if the two datums match, otherwise 0 (FALSE).
-  compare_datums : function( dest ) {
-    if( this.datum_type != dest.datum_type ) {
-      return false; // false, datums are not equal
-    } else if( this.a != dest.a || Math.abs(this.es-dest.es) > 0.000000000050 ) {
-      // the tolerence for es is to ensure that GRS80 and WGS84
-      // are considered identical
-      return false;
-    } else if( this.datum_type == Proj4js.common.PJD_3PARAM ) {
-      return (this.datum_params[0] == dest.datum_params[0]
-              && this.datum_params[1] == dest.datum_params[1]
-              && this.datum_params[2] == dest.datum_params[2]);
-    } else if( this.datum_type == Proj4js.common.PJD_7PARAM ) {
-      return (this.datum_params[0] == dest.datum_params[0]
-              && this.datum_params[1] == dest.datum_params[1]
-              && this.datum_params[2] == dest.datum_params[2]
-              && this.datum_params[3] == dest.datum_params[3]
-              && this.datum_params[4] == dest.datum_params[4]
-              && this.datum_params[5] == dest.datum_params[5]
-              && this.datum_params[6] == dest.datum_params[6]);
-    } else if( this.datum_type == Proj4js.common.PJD_GRIDSHIFT ) {
-      return strcmp( pj_param(this.params,"snadgrids").s,
-                     pj_param(dest.params,"snadgrids").s ) == 0;
-    } else {
-      return true; // datums are equal
-    }
-  }, // cs_compare_datums()
-
-  /*
-   * The function Convert_Geodetic_To_Geocentric converts geodetic coordinates
-   * (latitude, longitude, and height) to geocentric coordinates (X, Y, Z),
-   * according to the current ellipsoid parameters.
-   *
-   *    Latitude  : Geodetic latitude in radians                     (input)
-   *    Longitude : Geodetic longitude in radians                    (input)
-   *    Height    : Geodetic height, in meters                       (input)
-   *    X         : Calculated Geocentric X coordinate, in meters    (output)
-   *    Y         : Calculated Geocentric Y coordinate, in meters    (output)
-   *    Z         : Calculated Geocentric Z coordinate, in meters    (output)
-   *
-   */
-  geodetic_to_geocentric : function(p) {
-    var Longitude = p.x;
-    var Latitude = p.y;
-    var Height = p.z ? p.z : 0;   //Z value not always supplied
-    var X;  // output
-    var Y;
-    var Z;
-
-    var Error_Code=0;  //  GEOCENT_NO_ERROR;
-    var Rn;            /*  Earth radius at location  */
-    var Sin_Lat;       /*  Math.sin(Latitude)  */
-    var Sin2_Lat;      /*  Square of Math.sin(Latitude)  */
-    var Cos_Lat;       /*  Math.cos(Latitude)  */
-
-    /*
-    ** Don't blow up if Latitude is just a little out of the value
-    ** range as it may just be a rounding issue.  Also removed longitude
-    ** test, it should be wrapped by Math.cos() and Math.sin().  NFW for PROJ.4, Sep/2001.
-    */
-    if( Latitude < -Proj4js.common.HALF_PI && Latitude > -1.001 * Proj4js.common.HALF_PI ) {
-        Latitude = -Proj4js.common.HALF_PI;
-    } else if( Latitude > Proj4js.common.HALF_PI && Latitude < 1.001 * Proj4js.common.HALF_PI ) {
-        Latitude = Proj4js.common.HALF_PI;
-    } else if ((Latitude < -Proj4js.common.HALF_PI) || (Latitude > Proj4js.common.HALF_PI)) {
-      /* Latitude out of range */
-      Proj4js.reportError('geocent:lat out of range:'+Latitude);
-      return null;
-    }
-
-    if (Longitude > Proj4js.common.PI) Longitude -= (2*Proj4js.common.PI);
-    Sin_Lat = Math.sin(Latitude);
-    Cos_Lat = Math.cos(Latitude);
-    Sin2_Lat = Sin_Lat * Sin_Lat;
-    Rn = this.a / (Math.sqrt(1.0e0 - this.es * Sin2_Lat));
-    X = (Rn + Height) * Cos_Lat * Math.cos(Longitude);
-    Y = (Rn + Height) * Cos_Lat * Math.sin(Longitude);
-    Z = ((Rn * (1 - this.es)) + Height) * Sin_Lat;
-
-    p.x = X;
-    p.y = Y;
-    p.z = Z;
-    return Error_Code;
-  }, // cs_geodetic_to_geocentric()
-
-
-  geocentric_to_geodetic : function (p) {
-/* local defintions and variables */
-/* end-criterium of loop, accuracy of sin(Latitude) */
-var genau = 1.E-12;
-var genau2 = (genau*genau);
-var maxiter = 30;
-
-    var P;        /* distance between semi-minor axis and location */
-    var RR;       /* distance between center and location */
-    var CT;       /* sin of geocentric latitude */
-    var ST;       /* cos of geocentric latitude */
-    var RX;
-    var RK;
-    var RN;       /* Earth radius at location */
-    var CPHI0;    /* cos of start or old geodetic latitude in iterations */
-    var SPHI0;    /* sin of start or old geodetic latitude in iterations */
-    var CPHI;     /* cos of searched geodetic latitude */
-    var SPHI;     /* sin of searched geodetic latitude */
-    var SDPHI;    /* end-criterium: addition-theorem of sin(Latitude(iter)-Latitude(iter-1)) */
-    var At_Pole;     /* indicates location is in polar region */
-    var iter;        /* # of continous iteration, max. 30 is always enough (s.a.) */
-
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0.0;   //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    At_Pole = false;
-    P = Math.sqrt(X*X+Y*Y);
-    RR = Math.sqrt(X*X+Y*Y+Z*Z);
-
-/*      special cases for latitude and longitude */
-    if (P/this.a < genau) {
-
-/*  special case, if P=0. (X=0., Y=0.) */
-        At_Pole = true;
-        Longitude = 0.0;
-
-/*  if (X,Y,Z)=(0.,0.,0.) then Height becomes semi-minor axis
- *  of ellipsoid (=center of mass), Latitude becomes PI/2 */
-        if (RR/this.a < genau) {
-            Latitude = Proj4js.common.HALF_PI;
-            Height   = -this.b;
-            return;
-        }
-    } else {
-/*  ellipsoidal (geodetic) longitude
- *  interval: -PI < Longitude <= +PI */
-        Longitude=Math.atan2(Y,X);
-    }
-
-/* --------------------------------------------------------------
- * Following iterative algorithm was developped by
- * "Institut für Erdmessung", University of Hannover, July 1988.
- * Internet: www.ife.uni-hannover.de
- * Iterative computation of CPHI,SPHI and Height.
- * Iteration of CPHI and SPHI to 10**-12 radian resp.
- * 2*10**-7 arcsec.
- * --------------------------------------------------------------
- */
-    CT = Z/RR;
-    ST = P/RR;
-    RX = 1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);
-    CPHI0 = ST*(1.0-this.es)*RX;
-    SPHI0 = CT*RX;
-    iter = 0;
-
-/* loop to find sin(Latitude) resp. Latitude
- * until |sin(Latitude(iter)-Latitude(iter-1))| < genau */
-    do
-    {
-        iter++;
-        RN = this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);
-
-/*  ellipsoidal (geodetic) height */
-        Height = P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);
-
-        RK = this.es*RN/(RN+Height);
-        RX = 1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);
-        CPHI = ST*(1.0-RK)*RX;
-        SPHI = CT*RX;
-        SDPHI = SPHI*CPHI0-CPHI*SPHI0;
-        CPHI0 = CPHI;
-        SPHI0 = SPHI;
-    }
-    while (SDPHI*SDPHI > genau2 && iter < maxiter);
-
-/*      ellipsoidal (geodetic) latitude */
-    Latitude=Math.atan(SPHI/Math.abs(CPHI));
-
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // cs_geocentric_to_geodetic()
-
-  /** Convert_Geocentric_To_Geodetic
-   * The method used here is derived from 'An Improved Algorithm for
-   * Geocentric to Geodetic Coordinate Conversion', by Ralph Toms, Feb 1996
-   */
-  geocentric_to_geodetic_noniter : function (p) {
-    var X = p.x;
-    var Y = p.y;
-    var Z = p.z ? p.z : 0;   //Z value not always supplied
-    var Longitude;
-    var Latitude;
-    var Height;
-
-    var W;        /* distance from Z axis */
-    var W2;       /* square of distance from Z axis */
-    var T0;       /* initial estimate of vertical component */
-    var T1;       /* corrected estimate of vertical component */
-    var S0;       /* initial estimate of horizontal component */
-    var S1;       /* corrected estimate of horizontal component */
-    var Sin_B0;   /* Math.sin(B0), B0 is estimate of Bowring aux variable */
-    var Sin3_B0;  /* cube of Math.sin(B0) */
-    var Cos_B0;   /* Math.cos(B0) */
-    var Sin_p1;   /* Math.sin(phi1), phi1 is estimated latitude */
-    var Cos_p1;   /* Math.cos(phi1) */
-    var Rn;       /* Earth radius at location */
-    var Sum;      /* numerator of Math.cos(phi1) */
-    var At_Pole;  /* indicates location is in polar region */
-
-    X = parseFloat(X);  // cast from string to float
-    Y = parseFloat(Y);
-    Z = parseFloat(Z);
-
-    At_Pole = false;
-    if (X != 0.0)
-    {
-        Longitude = Math.atan2(Y,X);
-    }
-    else
-    {
-        if (Y > 0)
-        {
-            Longitude = Proj4js.common.HALF_PI;
-        }
-        else if (Y < 0)
-        {
-            Longitude = -Proj4js.common.HALF_PI;
-        }
-        else
-        {
-            At_Pole = true;
-            Longitude = 0.0;
-            if (Z > 0.0)
-            {  /* north pole */
-                Latitude = Proj4js.common.HALF_PI;
-            }
-            else if (Z < 0.0)
-            {  /* south pole */
-                Latitude = -Proj4js.common.HALF_PI;
-            }
-            else
-            {  /* center of earth */
-                Latitude = Proj4js.common.HALF_PI;
-                Height = -this.b;
-                return;
-            }
-        }
-    }
-    W2 = X*X + Y*Y;
-    W = Math.sqrt(W2);
-    T0 = Z * Proj4js.common.AD_C;
-    S0 = Math.sqrt(T0 * T0 + W2);
-    Sin_B0 = T0 / S0;
-    Cos_B0 = W / S0;
-    Sin3_B0 = Sin_B0 * Sin_B0 * Sin_B0;
-    T1 = Z + this.b * this.ep2 * Sin3_B0;
-    Sum = W - this.a * this.es * Cos_B0 * Cos_B0 * Cos_B0;
-    S1 = Math.sqrt(T1*T1 + Sum * Sum);
-    Sin_p1 = T1 / S1;
-    Cos_p1 = Sum / S1;
-    Rn = this.a / Math.sqrt(1.0 - this.es * Sin_p1 * Sin_p1);
-    if (Cos_p1 >= Proj4js.common.COS_67P5)
-    {
-        Height = W / Cos_p1 - Rn;
-    }
-    else if (Cos_p1 <= -Proj4js.common.COS_67P5)
-    {
-        Height = W / -Cos_p1 - Rn;
-    }
-    else
-    {
-        Height = Z / Sin_p1 + Rn * (this.es - 1.0);
-    }
-    if (At_Pole == false)
-    {
-        Latitude = Math.atan(Sin_p1 / Cos_p1);
-    }
-
-    p.x = Longitude;
-    p.y = Latitude;
-    p.z = Height;
-    return p;
-  }, // geocentric_to_geodetic_noniter()
-
-  /****************************************************************/
-  // pj_geocentic_to_wgs84( p )
-  //  p = point to transform in geocentric coordinates (x,y,z)
-  geocentric_to_wgs84 : function ( p ) {
-
-    if( this.datum_type == Proj4js.common.PJD_3PARAM )
-    {
-      // if( x[io] == HUGE_VAL )
-      //    continue;
-      p.x += this.datum_params[0];
-      p.y += this.datum_params[1];
-      p.z += this.datum_params[2];
-
-    }
-    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
-    {
-      var Dx_BF =this.datum_params[0];
-      var Dy_BF =this.datum_params[1];
-      var Dz_BF =this.datum_params[2];
-      var Rx_BF =this.datum_params[3];
-      var Ry_BF =this.datum_params[4];
-      var Rz_BF =this.datum_params[5];
-      var M_BF  =this.datum_params[6];
-      // if( x[io] == HUGE_VAL )
-      //    continue;
-      var x_out = M_BF*(       p.x - Rz_BF*p.y + Ry_BF*p.z) + Dx_BF;
-      var y_out = M_BF*( Rz_BF*p.x +       p.y - Rx_BF*p.z) + Dy_BF;
-      var z_out = M_BF*(-Ry_BF*p.x + Rx_BF*p.y +       p.z) + Dz_BF;
-      p.x = x_out;
-      p.y = y_out;
-      p.z = z_out;
-    }
-  }, // cs_geocentric_to_wgs84
-
-  /****************************************************************/
-  // pj_geocentic_from_wgs84()
-  //  coordinate system definition,
-  //  point to transform in geocentric coordinates (x,y,z)
-  geocentric_from_wgs84 : function( p ) {
-
-    if( this.datum_type == Proj4js.common.PJD_3PARAM )
-    {
-      //if( x[io] == HUGE_VAL )
-      //    continue;
-      p.x -= this.datum_params[0];
-      p.y -= this.datum_params[1];
-      p.z -= this.datum_params[2];
-
-    }
-    else if (this.datum_type == Proj4js.common.PJD_7PARAM)
-    {
-      var Dx_BF =this.datum_params[0];
-      var Dy_BF =this.datum_params[1];
-      var Dz_BF =this.datum_params[2];
-      var Rx_BF =this.datum_params[3];
-      var Ry_BF =this.datum_params[4];
-      var Rz_BF =this.datum_params[5];
-      var M_BF  =this.datum_params[6];
-      var x_tmp = (p.x - Dx_BF) / M_BF;
-      var y_tmp = (p.y - Dy_BF) / M_BF;
-      var z_tmp = (p.z - Dz_BF) / M_BF;
-      //if( x[io] == HUGE_VAL )
-      //    continue;
-
-      p.x =        x_tmp + Rz_BF*y_tmp - Ry_BF*z_tmp;
-      p.y = -Rz_BF*x_tmp +       y_tmp + Rx_BF*z_tmp;
-      p.z =  Ry_BF*x_tmp - Rx_BF*y_tmp +       z_tmp;
-    } //cs_geocentric_from_wgs84()
-  }
-});
-
-/** point object, nothing fancy, just allows values to be
-    passed back and forth by reference rather than by value.
-    Other point classes may be used as long as they have
-    x and y properties, which will get modified in the transform method.
-*/
-Proj4js.Point = Proj4js.Class({
-
-    /**
-     * Constructor: Proj4js.Point
-     *
-     * Parameters:
-     * - x {float} or {Array} either the first coordinates component or
-     *     the full coordinates
-     * - y {float} the second component
-     * - z {float} the third component, optional.
-     */
-    initialize : function(x,y,z) {
-      if (typeof x == 'object') {
-        this.x = x[0];
-        this.y = x[1];
-        this.z = x[2] || 0.0;
-      } else if (typeof x == 'string') {
-        var coords = x.split(',');
-        this.x = parseFloat(coords[0]);
-        this.y = parseFloat(coords[1]);
-        this.z = parseFloat(coords[2]) || 0.0;
-      } else {
-        this.x = x;
-        this.y = y;
-        this.z = z || 0.0;
-      }
-    },
-
-    /**
-     * APIMethod: clone
-     * Build a copy of a Proj4js.Point object.
-     *
-     * Return:
-     * {Proj4js}.Point the cloned point.
-     */
-    clone : function() {
-      return new Proj4js.Point(this.x, this.y, this.z);
-    },
-
-    /**
-     * APIMethod: toString
-     * Return a readable string version of the point
-     *
-     * Return:
-     * {String} String representation of Proj4js.Point object. 
-     *           (ex. <i>"x=5,y=42"</i>)
-     */
-    toString : function() {
-        return ("x=" + this.x + ",y=" + this.y);
-    },
-
-    /** 
-     * APIMethod: toShortString
-     * Return a short string version of the point.
-     *
-     * Return:
-     * {String} Shortened String representation of Proj4js.Point object. 
-     *         (ex. <i>"5, 42"</i>)
-     */
-    toShortString : function() {
-        return (this.x + ", " + this.y);
-    }
-});
-
-Proj4js.PrimeMeridian = {
-    "greenwich": 0.0,               //"0dE",
-    "lisbon":     -9.131906111111,   //"9d07'54.862\"W",
-    "paris":       2.337229166667,   //"2d20'14.025\"E",
-    "bogota":    -74.080916666667,  //"74d04'51.3\"W",
-    "madrid":     -3.687938888889,  //"3d41'16.58\"W",
-    "rome":       12.452333333333,  //"12d27'8.4\"E",
-    "bern":        7.439583333333,  //"7d26'22.5\"E",
-    "jakarta":   106.807719444444,  //"106d48'27.79\"E",
-    "ferro":     -17.666666666667,  //"17d40'W",
-    "brussels":    4.367975,        //"4d22'4.71\"E",
-    "stockholm":  18.058277777778,  //"18d3'29.8\"E",
-    "athens":     23.7163375,       //"23d42'58.815\"E",
-    "oslo":       10.722916666667   //"10d43'22.5\"E"
-};
-
-Proj4js.Ellipsoid = {
-  "MERIT": {a:6378137.0, rf:298.257, ellipseName:"MERIT 1983"},
-  "SGS85": {a:6378136.0, rf:298.257, ellipseName:"Soviet Geodetic System 85"},
-  "GRS80": {a:6378137.0, rf:298.257222101, ellipseName:"GRS 1980(IUGG, 1980)"},
-  "IAU76": {a:6378140.0, rf:298.257, ellipseName:"IAU 1976"},
-  "airy": {a:6377563.396, b:6356256.910, ellipseName:"Airy 1830"},
-  "APL4.": {a:6378137, rf:298.25, ellipseName:"Appl. Physics. 1965"},
-  "NWL9D": {a:6378145.0, rf:298.25, ellipseName:"Naval Weapons Lab., 1965"},
-  "mod_airy": {a:6377340.189, b:6356034.446, ellipseName:"Modified Airy"},
-  "andrae": {a:6377104.43, rf:300.0, ellipseName:"Andrae 1876 (Den., Iclnd.)"},
-  "aust_SA": {a:6378160.0, rf:298.25, ellipseName:"Australian Natl & S. Amer. 1969"},
-  "GRS67": {a:6378160.0, rf:298.2471674270, ellipseName:"GRS 67(IUGG 1967)"},
-  "bessel": {a:6377397.155, rf:299.1528128, ellipseName:"Bessel 1841"},
-  "bess_nam": {a:6377483.865, rf:299.1528128, ellipseName:"Bessel 1841 (Namibia)"},
-  "clrk66": {a:6378206.4, b:6356583.8, ellipseName:"Clarke 1866"},
-  "clrk80": {a:6378249.145, rf:293.4663, ellipseName:"Clarke 1880 mod."},
-  "CPM": {a:6375738.7, rf:334.29, ellipseName:"Comm. des Poids et Mesures 1799"},
-  "delmbr": {a:6376428.0, rf:311.5, ellipseName:"Delambre 1810 (Belgium)"},
-  "engelis": {a:6378136.05, rf:298.2566, ellipseName:"Engelis 1985"},
-  "evrst30": {a:6377276.345, rf:300.8017, ellipseName:"Everest 1830"},
-  "evrst48": {a:6377304.063, rf:300.8017, ellipseName:"Everest 1948"},
-  "evrst56": {a:6377301.243, rf:300.8017, ellipseName:"Everest 1956"},
-  "evrst69": {a:6377295.664, rf:300.8017, ellipseName:"Everest 1969"},
-  "evrstSS": {a:6377298.556, rf:300.8017, ellipseName:"Everest (Sabah & Sarawak)"},
-  "fschr60": {a:6378166.0, rf:298.3, ellipseName:"Fischer (Mercury Datum) 1960"},
-  "fschr60m": {a:6378155.0, rf:298.3, ellipseName:"Fischer 1960"},
-  "fschr68": {a:6378150.0, rf:298.3, ellipseName:"Fischer 1968"},
-  "helmert": {a:6378200.0, rf:298.3, ellipseName:"Helmert 1906"},
-  "hough": {a:6378270.0, rf:297.0, ellipseName:"Hough"},
-  "intl": {a:6378388.0, rf:297.0, ellipseName:"International 1909 (Hayford)"},
-  "kaula": {a:6378163.0, rf:298.24, ellipseName:"Kaula 1961"},
-  "lerch": {a:6378139.0, rf:298.257, ellipseName:"Lerch 1979"},
-  "mprts": {a:6397300.0, rf:191.0, ellipseName:"Maupertius 1738"},
-  "new_intl": {a:6378157.5, b:6356772.2, ellipseName:"New International 1967"},
-  "plessis": {a:6376523.0, rf:6355863.0, ellipseName:"Plessis 1817 (France)"},
-  "krass": {a:6378245.0, rf:298.3, ellipseName:"Krassovsky, 1942"},
-  "SEasia": {a:6378155.0, b:6356773.3205, ellipseName:"Southeast Asia"},
-  "walbeck": {a:6376896.0, b:6355834.8467, ellipseName:"Walbeck"},
-  "WGS60": {a:6378165.0, rf:298.3, ellipseName:"WGS 60"},
-  "WGS66": {a:6378145.0, rf:298.25, ellipseName:"WGS 66"},
-  "WGS72": {a:6378135.0, rf:298.26, ellipseName:"WGS 72"},
-  "WGS84": {a:6378137.0, rf:298.257223563, ellipseName:"WGS 84"},
-  "sphere": {a:6370997.0, b:6370997.0, ellipseName:"Normal Sphere (r=6370997)"}
-};
-
-Proj4js.Datum = {
-  "WGS84": {towgs84: "0,0,0", ellipse: "WGS84", datumName: "WGS84"},
-  "GGRS87": {towgs84: "-199.87,74.79,246.62", ellipse: "GRS80", datumName: "Greek_Geodetic_Reference_System_1987"},
-  "NAD83": {towgs84: "0,0,0", ellipse: "GRS80", datumName: "North_American_Datum_1983"},
-  "NAD27": {nadgrids: "@conus, at alaska, at ntv2_0.gsb, at ntv1_can.dat", ellipse: "clrk66", datumName: "North_American_Datum_1927"},
-  "potsdam": {towgs84: "606.0,23.0,413.0", ellipse: "bessel", datumName: "Potsdam Rauenberg 1950 DHDN"},
-  "carthage": {towgs84: "-263.0,6.0,431.0", ellipse: "clark80", datumName: "Carthage 1934 Tunisia"},
-  "hermannskogel": {towgs84: "653.0,-212.0,449.0", ellipse: "bessel", datumName: "Hermannskogel"},
-  "ire65": {towgs84: "482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15", ellipse: "mod_airy", datumName: "Ireland 1965"},
-  "nzgd49": {towgs84: "59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993", ellipse: "intl", datumName: "New Zealand Geodetic Datum 1949"},
-  "OSGB36": {towgs84: "446.448,-125.157,542.060,0.1502,0.2470,0.8421,-20.4894", ellipse: "airy", datumName: "Airy 1830"}
-};
-
-Proj4js.WGS84 = new Proj4js.Proj('WGS84');
-Proj4js.Datum['OSB36'] = Proj4js.Datum['OSGB36']; //as returned from spatialreference.org
-
-//lookup table to go from the projection name in WKT to the Proj4js projection name
-//build this out as required
-Proj4js.wktProjections = {
-  "Lambert Tangential Conformal Conic Projection": "lcc",
-  "Mercator": "merc",
-  "Transverse_Mercator": "tmerc",
-  "Transverse Mercator": "tmerc",
-  "Lambert Azimuthal Equal Area": "laea",
-  "Universal Transverse Mercator System": "utm"
-};
-
-
-/* ======================================================================
-    projCode/aea.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                     ALBERS CONICAL EQUAL AREA 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and Northing
-		for the Albers Conical Equal Area projection.  The longitude
-		and latitude must be in radians.  The Easting and Northing
-		values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan,       	Feb, 1992
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-
-Proj4js.Proj.aea = {
-  init : function() {
-
-    if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
-       Proj4js.reportError("aeaInitEqualLatitudes");
-       return;
-    }
-    this.temp = this.b / this.a;
-    this.es = 1.0 - Math.pow(this.temp,2);
-    this.e3 = Math.sqrt(this.es);
-
-    this.sin_po=Math.sin(this.lat1);
-    this.cos_po=Math.cos(this.lat1);
-    this.t1=this.sin_po;
-    this.con = this.sin_po;
-    this.ms1 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
-    this.qs1 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
-
-    this.sin_po=Math.sin(this.lat2);
-    this.cos_po=Math.cos(this.lat2);
-    this.t2=this.sin_po;
-    this.ms2 = Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);
-    this.qs2 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
-
-    this.sin_po=Math.sin(this.lat0);
-    this.cos_po=Math.cos(this.lat0);
-    this.t3=this.sin_po;
-    this.qs0 = Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);
-
-    if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
-      this.ns0 = (this.ms1 * this.ms1 - this.ms2 *this.ms2)/ (this.qs2 - this.qs1);
-    } else {
-      this.ns0 = this.con;
-    }
-    this.c = this.ms1 * this.ms1 + this.ns0 * this.qs1;
-    this.rh = this.a * Math.sqrt(this.c - this.ns0 * this.qs0)/this.ns0;
-  },
-
-/* Albers Conical Equal Area forward equations--mapping lat,long to x,y
-  -------------------------------------------------------------------*/
-  forward: function(p){
-
-    var lon=p.x;
-    var lat=p.y;
-
-    this.sin_phi=Math.sin(lat);
-    this.cos_phi=Math.cos(lat);
-
-    var qs = Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);
-    var rh1 =this.a * Math.sqrt(this.c - this.ns0 * qs)/this.ns0;
-    var theta = this.ns0 * Proj4js.common.adjust_lon(lon - this.long0); 
-    var x = rh1 * Math.sin(theta) + this.x0;
-    var y = this.rh - rh1 * Math.cos(theta) + this.y0;
-
-    p.x = x; 
-    p.y = y;
-    return p;
-  },
-
-
-  inverse: function(p) {
-    var rh1,qs,con,theta,lon,lat;
-
-    p.x -= this.x0;
-    p.y = this.rh - p.y + this.y0;
-    if (this.ns0 >= 0) {
-      rh1 = Math.sqrt(p.x *p.x + p.y * p.y);
-      con = 1.0;
-    } else {
-      rh1 = -Math.sqrt(p.x * p.x + p.y *p.y);
-      con = -1.0;
-    }
-    theta = 0.0;
-    if (rh1 != 0.0) {
-      theta = Math.atan2(con * p.x, con * p.y);
-    }
-    con = rh1 * this.ns0 / this.a;
-    qs = (this.c - con * con) / this.ns0;
-    if (this.e3 >= 1e-10) {
-      con = 1 - .5 * (1.0 -this.es) * Math.log((1.0 - this.e3) / (1.0 + this.e3))/this.e3;
-      if (Math.abs(Math.abs(con) - Math.abs(qs)) > .0000000001 ) {
-          lat = this.phi1z(this.e3,qs);
-      } else {
-          if (qs >= 0) {
-             lat = .5 * PI;
-          } else {
-             lat = -.5 * PI;
-          }
-      }
-    } else {
-      lat = this.phi1z(e3,qs);
-    }
-
-    lon = Proj4js.common.adjust_lon(theta/this.ns0 + this.long0);
-    p.x = lon;
-    p.y = lat;
-    return p;
-  },
-  
-/* Function to compute phi1, the latitude for the inverse of the
-   Albers Conical Equal-Area projection.
--------------------------------------------*/
-  phi1z: function (eccent,qs) {
-    var con, com, dphi;
-    var phi = Proj4js.common.asinz(.5 * qs);
-    if (eccent < Proj4js.common.EPSLN) return phi;
-    
-    var eccnts = eccent * eccent; 
-    for (var i = 1; i <= 25; i++) {
-        sinphi = Math.sin(phi);
-        cosphi = Math.cos(phi);
-        con = eccent * sinphi; 
-        com = 1.0 - con * con;
-        dphi = .5 * com * com / cosphi * (qs / (1.0 - eccnts) - sinphi / com + .5 / eccent * Math.log((1.0 - con) / (1.0 + con)));
-        phi = phi + dphi;
-        if (Math.abs(dphi) <= 1e-7) return phi;
-    }
-    Proj4js.reportError("aea:phi1z:Convergence error");
-    return null;
-  }
-  
-};
-
-
-
-/* ======================================================================
-    projCode/sterea.js
-   ====================================================================== */
-
-
-Proj4js.Proj.sterea = {
-  dependsOn : 'gauss',
-
-  init : function() {
-    Proj4js.Proj['gauss'].init.apply(this);
-    if (!this.rc) {
-      Proj4js.reportError("sterea:init:E_ERROR_0");
-      return;
-    }
-    this.sinc0 = Math.sin(this.phic0);
-    this.cosc0 = Math.cos(this.phic0);
-    this.R2 = 2.0 * this.rc;
-    if (!this.title) this.title = "Oblique Stereographic Alternative";
-  },
-
-  forward : function(p) {
-    p.x = Proj4js.common.adjust_lon(p.x-this.long0); /* adjust del longitude */
-    Proj4js.Proj['gauss'].forward.apply(this, [p]);
-    sinc = Math.sin(p.y);
-    cosc = Math.cos(p.y);
-    cosl = Math.cos(p.x);
-    k = this.k0 * this.R2 / (1.0 + this.sinc0 * sinc + this.cosc0 * cosc * cosl);
-    p.x = k * cosc * Math.sin(p.x);
-    p.y = k * (this.cosc0 * sinc - this.sinc0 * cosc * cosl);
-    p.x = this.a * p.x + this.x0;
-    p.y = this.a * p.y + this.y0;
-    return p;
-  },
-
-  inverse : function(p) {
-    var lon,lat;
-    p.x = (p.x - this.x0) / this.a; /* descale and de-offset */
-    p.y = (p.y - this.y0) / this.a;
-
-    p.x /= this.k0;
-    p.y /= this.k0;
-    if ( (rho = Math.sqrt(p.x*p.x + p.y*p.y)) ) {
-      c = 2.0 * Math.atan2(rho, this.R2);
-      sinc = Math.sin(c);
-      cosc = Math.cos(c);
-      lat = Math.asin(cosc * this.sinc0 + p.y * sinc * this.cosc0 / rho);
-      lon = Math.atan2(p.x * sinc, rho * this.cosc0 * cosc - p.y * this.sinc0 * sinc);
-    } else {
-      lat = this.phic0;
-      lon = 0.;
-    }
-
-    p.x = lon;
-    p.y = lat;
-    Proj4js.Proj['gauss'].inverse.apply(this,[p]);
-    p.x = Proj4js.common.adjust_lon(p.x + this.long0); /* adjust longitude to CM */
-    return p;
-  }
-};
-
-/* ======================================================================
-    projCode/poly.js
-   ====================================================================== */
-
-/* Function to compute, phi4, the latitude for the inverse of the
-   Polyconic projection.
-------------------------------------------------------------*/
-function phi4z (eccent,e0,e1,e2,e3,a,b,c,phi) {
-	var sinphi, sin2ph, tanph, ml, mlp, con1, con2, con3, dphi, i;
-
-	phi = a;
-	for (i = 1; i <= 15; i++) {
-		sinphi = Math.sin(phi);
-		tanphi = Math.tan(phi);
-		c = tanphi * Math.sqrt (1.0 - eccent * sinphi * sinphi);
-		sin2ph = Math.sin (2.0 * phi);
-		/*
-		ml = e0 * *phi - e1 * sin2ph + e2 * sin (4.0 *  *phi);
-		mlp = e0 - 2.0 * e1 * cos (2.0 *  *phi) + 4.0 * e2 *  cos (4.0 *  *phi);
-		*/
-		ml = e0 * phi - e1 * sin2ph + e2 * Math.sin (4.0 *  phi) - e3 * Math.sin (6.0 * phi);
-		mlp = e0 - 2.0 * e1 * Math.cos (2.0 *  phi) + 4.0 * e2 * Math.cos (4.0 *  phi) - 6.0 * e3 * Math.cos (6.0 *  phi);
-		con1 = 2.0 * ml + c * (ml * ml + b) - 2.0 * a *  (c * ml + 1.0);
-		con2 = eccent * sin2ph * (ml * ml + b - 2.0 * a * ml) / (2.0 *c);
-		con3 = 2.0 * (a - ml) * (c * mlp - 2.0 / sin2ph) - 2.0 * mlp;
-		dphi = con1 / (con2 + con3);
-		phi += dphi;
-		if (Math.abs(dphi) <= .0000000001 ) return(phi);   
-	}
-	Proj4js.reportError("phi4z: No convergence");
-	return null;
-}
-
-
-/* Function to compute the constant e4 from the input of the eccentricity
-   of the spheroid, x.  This constant is used in the Polar Stereographic
-   projection.
---------------------------------------------------------------------*/
-function e4fn(x) {
-	var con, com;
-	con = 1.0 + x;
-	com = 1.0 - x;
-	return (Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));
-}
-
-
-
-
-
-/*******************************************************************************
-NAME                             POLYCONIC 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Polyconic projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-Proj4js.Proj.poly = {
-
-	/* Initialize the POLYCONIC projection
-	  ----------------------------------*/
-	init: function() {
-		var temp;			/* temporary variable		*/
-		if (this.lat0=0) this.lat0=90;//this.lat0 ca
-
-		/* Place parameters in static storage for common use
-		  -------------------------------------------------*/
-		this.temp = this.b / this.a;
-		this.es = 1.0 - Math.pow(this.temp,2);// devait etre dans tmerc.js mais n y est pas donc je commente sinon retour de valeurs nulles 
-		this.e = Math.sqrt(this.es);
-		this.e0 = Proj4js.common.e0fn(this.es);
-		this.e1 = Proj4js.common.e1fn(this.es);
-		this.e2 = Proj4js.common.e2fn(this.es);
-		this.e3 = Proj4js.common.e3fn(this.es);
-		this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this.e2, this.e3, this.lat0);//si que des zeros le calcul ne se fait pas
-		//if (!this.ml0) {this.ml0=0;}
-	},
-
-
-	/* Polyconic forward equations--mapping lat,long to x,y
-	  ---------------------------------------------------*/
-	forward: function(p) {
-		var sinphi, cosphi;	/* sin and cos value				*/
-		var al;				/* temporary values				*/
-		var c;				/* temporary values				*/
-		var con, ml;		/* cone constant, small m			*/
-		var ms;				/* small m					*/
-		var x,y;
-
-		var lon=p.x;
-		var lat=p.y;	
-
-		con = Proj4js.common.adjust_lon(lon - this.long0);
-		if (Math.abs(lat) <= .0000001) {
-			x = this.x0 + this.a * con;
-			y = this.y0 - this.a * this.ml0;
-		} else {
-			sinphi = Math.sin(lat);
-			cosphi = Math.cos(lat);	   
-
-			ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
-			ms = Proj4js.common.msfnz(this.e,sinphi,cosphi);
-			con = sinphi;
-			x = this.x0 + this.a * ms * Math.sin(con)/sinphi;
-			y = this.y0 + this.a * (ml - this.ml0 + ms * (1.0 - Math.cos(con))/sinphi);
-		}
-
-		p.x=x;
-		p.y=y;   
-		return p;
-	},
-
-
-	/* Inverse equations
-	-----------------*/
-	inverse: function(p) {
-		var sin_phi, cos_phi;	/* sin and cos value				*/
-		var al;					/* temporary values				*/
-		var b;					/* temporary values				*/
-		var c;					/* temporary values				*/
-		var con, ml;			/* cone constant, small m			*/
-		var iflg;				/* error flag					*/
-		var lon,lat;
-		p.x -= this.x0;
-		p.y -= this.y0;
-		al = this.ml0 + p.y/this.a;
-		iflg = 0;
-
-		if (Math.abs(al) <= .0000001) {
-			lon = p.x/this.a + this.long0;
-			lat = 0.0;
-		} else {
-			b = al * al + (p.x/this.a) * (p.x/this.a);
-			iflg = phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);
-			if (iflg != 1) return(iflg);
-			lon = Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x * c / this.a) / Math.sin(lat)) + this.long0);
-		}
-
-		p.x=lon;
-		p.y=lat;
-		return p;
-	}
-};
-
-
-
-/* ======================================================================
-    projCode/equi.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                             EQUIRECTANGULAR 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Equirectangular projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-Proj4js.Proj.equi = {
-
-  init: function() {
-    if(!this.x0) this.x0=0;
-    if(!this.y0) this.y0=0;
-    if(!this.lat0) this.lat0=0;
-    if(!this.long0) this.long0=0;
-    ///this.t2;
-  },
-
-
-
-/* Equirectangular forward equations--mapping lat,long to x,y
-  ---------------------------------------------------------*/
-  forward: function(p) {
-
-    var lon=p.x;				
-    var lat=p.y;			
-
-    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
-    var x = this.x0 +this. a * dlon *Math.cos(this.lat0);
-    var y = this.y0 + this.a * lat;
-
-    this.t1=x;
-    this.t2=Math.cos(this.lat0);
-    p.x=x;
-    p.y=y;
-    return p;
-  },  //equiFwd()
-
-
-
-/* Equirectangular inverse equations--mapping x,y to lat/long
-  ---------------------------------------------------------*/
-  inverse: function(p) {
-
-    p.x -= this.x0;
-    p.y -= this.y0;
-    var lat = p.y /this. a;
-
-    if ( Math.abs(lat) > Proj4js.common.HALF_PI) {
-        Proj4js.reportError("equi:Inv:DataError");
-    }
-    var lon = Proj4js.common.adjust_lon(this.long0 + p.x / (this.a * Math.cos(this.lat0)));
-    p.x=lon;
-    p.y=lat;
-  }//equiInv()
-};
-
-
-/* ======================================================================
-    projCode/merc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            MERCATOR
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Mercator projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-D. Steinwand, EROS      Nov, 1991
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-//static double r_major = a;		   /* major axis 				*/
-//static double r_minor = b;		   /* minor axis 				*/
-//static double lon_center = long0;	   /* Center longitude (projection center) */
-//static double lat_origin =  lat0;	   /* center latitude			*/
-//static double e,es;		           /* eccentricity constants		*/
-//static double m1;		               /* small value m			*/
-//static double false_northing = y0;   /* y offset in meters			*/
-//static double false_easting = x0;	   /* x offset in meters			*/
-//scale_fact = k0 
-
-Proj4js.Proj.merc = {
-  init : function() {
-	//?this.temp = this.r_minor / this.r_major;
-	//this.temp = this.b / this.a;
-	//this.es = 1.0 - Math.sqrt(this.temp);
-	//this.e = Math.sqrt( this.es );
-	//?this.m1 = Math.cos(this.lat_origin) / (Math.sqrt( 1.0 - this.es * Math.sin(this.lat_origin) * Math.sin(this.lat_origin)));
-	//this.m1 = Math.cos(0.0) / (Math.sqrt( 1.0 - this.es * Math.sin(0.0) * Math.sin(0.0)));
-    if (this.lat_ts) {
-      if (this.sphere) {
-        this.k0 = Math.cos(this.lat_ts);
-      } else {
-        this.k0 = Proj4js.common.msfnz(this.es, Math.sin(this.lat_ts), Math.cos(this.lat_ts));
-      }
-    }
-  },
-
-/* Mercator forward equations--mapping lat,long to x,y
-  --------------------------------------------------*/
-
-  forward : function(p) {	
-    //alert("ll2m coords : "+coords);
-    var lon = p.x;
-    var lat = p.y;
-    // convert to radians
-    if ( lat*Proj4js.common.R2D > 90.0 && 
-          lat*Proj4js.common.R2D < -90.0 && 
-          lon*Proj4js.common.R2D > 180.0 && 
-          lon*Proj4js.common.R2D < -180.0) {
-      Proj4js.reportError("merc:forward: llInputOutOfRange: "+ lon +" : " + lat);
-      return null;
-    }
-
-    var x,y;
-    if(Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI)  <= Proj4js.common.EPSLN) {
-      Proj4js.reportError("merc:forward: ll2mAtPoles");
-      return null;
-    } else {
-      if (this.sphere) {
-        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
-        y = this.y0 + this.a * this.k0 * Math.log(Math.tan(Proj4js.common.FORTPI + 0.5*lat));
-      } else {
-        var sinphi = Math.sin(lat);
-        var ts = Proj4js.common.tsfnz(this.e,lat,sinphi);
-        x = this.x0 + this.a * this.k0 * Proj4js.common.adjust_lon(lon - this.long0);
-        y = this.y0 - this.a * this.k0 * Math.log(ts);
-      }
-      p.x = x; 
-      p.y = y;
-      return p;
-    }
-  },
-
-
-  /* Mercator inverse equations--mapping x,y to lat/long
-  --------------------------------------------------*/
-  inverse : function(p) {	
-
-    var x = p.x - this.x0;
-    var y = p.y - this.y0;
-    var lon,lat;
-
-    if (this.sphere) {
-      lat = Proj4js.common.HALF_PI - 2.0 * Math.atan(Math.exp(-y / this.a * this.k0));
-    } else {
-      var ts = Math.exp(-y / (this.a * this.k0));
-      lat = Proj4js.common.phi2z(this.e,ts);
-      if(lat == -9999) {
-        Proj4js.reportError("merc:inverse: lat = -9999");
-        return null;
-      }
-    }
-    lon = Proj4js.common.adjust_lon(this.long0+ x / (this.a * this.k0));
-
-    p.x = lon;
-    p.y = lat;
-    return p;
-  }
-};
-
-
-/* ======================================================================
-    projCode/utm.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            TRANSVERSE MERCATOR
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Transverse Mercator projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-
-/**
-  Initialize Transverse Mercator projection
-*/
-
-Proj4js.Proj.utm = {
-  dependsOn : 'tmerc',
-
-  init : function() {
-    if (!this.zone) {
-      Proj4js.reportError("utm:init: zone must be specified for UTM");
-      return;
-    }
-    this.lat0 = 0.0;
-    this.long0 = ((6 * Math.abs(this.zone)) - 183) * Proj4js.common.D2R;
-    this.x0 = 500000.0;
-    this.y0 = this.utmSouth ? 10000000.0 : 0.0;
-    this.k0 = 0.9996;
-
-    Proj4js.Proj['tmerc'].init.apply(this);
-    this.forward = Proj4js.Proj['tmerc'].forward;
-    this.inverse = Proj4js.Proj['tmerc'].inverse;
-  }
-};
-/* ======================================================================
-    projCode/eqdc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            EQUIDISTANT CONIC 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and Northing
-		for the Equidistant Conic projection.  The longitude and
-		latitude must be in radians.  The Easting and Northing values
-		will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-/* Variables common to all subroutines in this code file
-  -----------------------------------------------------*/
-
-Proj4js.Proj.eqdc = {
-
-/* Initialize the Equidistant Conic projection
-  ------------------------------------------*/
-  init: function() {
-
-    /* Place parameters in static storage for common use
-      -------------------------------------------------*/
-
-    if(!this.mode) this.mode=0;//chosen default mode
-    this.temp = this.b / this.a;
-    this.es = 1.0 - Math.pow(this.temp,2);
-    this.e = Math.sqrt(this.es);
-    this.e0 = Proj4js.common.e0fn(this.es);
-    this.e1 = Proj4js.common.e1fn(this.es);
-    this.e2 = Proj4js.common.e2fn(this.es);
-    this.e3 = Proj4js.common.e3fn(this.es);
-
-    this.sinphi=Math.sin(this.lat1);
-    this.cosphi=Math.cos(this.lat1);
-
-    this.ms1 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
-    this.ml1 = Proj4js.common.mlfn(this.e0, this.e1, this.e2,this.e3, this.lat1);
-
-    /* format B
-    ---------*/
-    if (this.mode != 0) {
-      if (Math.abs(this.lat1 + this.lat2) < Proj4js.common.EPSLN) {
-            Proj4js.reportError("eqdc:Init:EqualLatitudes");
-            //return(81);
-       }
-       this.sinphi=Math.sin(this.lat2);
-       this.cosphi=Math.cos(this.lat2);   
-
-       this.ms2 = Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);
-       this.ml2 = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat2);
-       if (Math.abs(this.lat1 - this.lat2) >= Proj4js.common.EPSLN) {
-         this.ns = (this.ms1 - this.ms2) / (this.ml2 - this.ml1);
-       } else {
-          this.ns = this.sinphi;
-       }
-    } else {
-      this.ns = this.sinphi;
-    }
-    this.g = this.ml1 + this.ms1/this.ns;
-    this.ml0 = Proj4js.common.mlfn(this.e0, this.e1,this. e2, this.e3, this.lat0);
-    this.rh = this.a * (this.g - this.ml0);
-  },
-
-
-/* Equidistant Conic forward equations--mapping lat,long to x,y
-  -----------------------------------------------------------*/
-  forward: function(p) {
-    var lon=p.x;
-    var lat=p.y;
-
-    /* Forward equations
-      -----------------*/
-    var ml = Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
-    var rh1 = this.a * (this.g - ml);
-    var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);
-
-    var x = this.x0  + rh1 * Math.sin(theta);
-    var y = this.y0 + this.rh - rh1 * Math.cos(theta);
-    p.x=x;
-    p.y=y;
-    return p;
-  },
-
-/* Inverse equations
-  -----------------*/
-  inverse: function(p) {
-    p.x -= this.x0;
-    p.y  = this.rh - p.y + this.y0;
-    var con, rh1;
-    if (this.ns >= 0) {
-       var rh1 = Math.sqrt(p.x *p.x + p.y * p.y); 
-       var con = 1.0;
-    } else {
-       rh1 = -Math.sqrt(p.x *p. x +p. y * p.y); 
-       con = -1.0;
-    }
-    var theta = 0.0;
-    if (rh1 != 0.0) theta = Math.atan2(con *p.x, con *p.y);
-    var ml = this.g - rh1 /this.a;
-    var lat = this.phi3z(ml,this.e0,this.e1,this.e2,this.e3);
-    var lon = Proj4js.common.adjust_lon(this.long0 + theta / this.ns);
-
-     p.x=lon;
-     p.y=lat;  
-     return p;
-    },
-    
-/* Function to compute latitude, phi3, for the inverse of the Equidistant
-   Conic projection.
------------------------------------------------------------------*/
-  phi3z: function(ml,e0,e1,e2,e3) {
-    var phi;
-    var dphi;
-
-    phi = ml;
-    for (var i = 0; i < 15; i++) {
-      dphi = (ml + e1 * Math.sin(2.0 * phi) - e2 * Math.sin(4.0 * phi) + e3 * Math.sin(6.0 * phi))/ e0 - phi;
-      phi += dphi;
-      if (Math.abs(dphi) <= .0000000001) {
-        return phi;
-      }
-    }
-    Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");
-    return null;
-  }
-
-    
-};
-/* ======================================================================
-    projCode/tmerc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            TRANSVERSE MERCATOR
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Transverse Mercator projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-
-/**
-  Initialize Transverse Mercator projection
-*/
-
-Proj4js.Proj.tmerc = {
-  init : function() {
-    this.e0 = Proj4js.common.e0fn(this.es);
-    this.e1 = Proj4js.common.e1fn(this.es);
-    this.e2 = Proj4js.common.e2fn(this.es);
-    this.e3 = Proj4js.common.e3fn(this.es);
-    this.ml0 = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, this.lat0);
-  },
-
-  /**
-    Transverse Mercator Forward  - long/lat to x/y
-    long/lat in radians
-  */
-  forward : function(p) {
-    var lon = p.x;
-    var lat = p.y;
-
-    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0); // Delta longitude
-    var con;    // cone constant
-    var x, y;
-    var sin_phi=Math.sin(lat);
-    var cos_phi=Math.cos(lat);
-
-    if (this.sphere) {  /* spherical form */
-      var b = cos_phi * Math.sin(delta_lon);
-      if ((Math.abs(Math.abs(b) - 1.0)) < .0000000001)  {
-        Proj4js.reportError("tmerc:forward: Point projects into infinity");
-        return(93);
-      } else {
-        x = .5 * this.a * this.k0 * Math.log((1.0 + b)/(1.0 - b));
-        con = Math.acos(cos_phi * Math.cos(delta_lon)/Math.sqrt(1.0 - b*b));
-        if (lat < 0) con = - con;
-        y = this.a * this.k0 * (con - this.lat0);
-      }
-    } else {
-      var al  = cos_phi * delta_lon;
-      var als = Math.pow(al,2);
-      var c   = this.ep2 * Math.pow(cos_phi,2);
-      var tq  = Math.tan(lat);
-      var t   = Math.pow(tq,2);
-      con = 1.0 - this.es * Math.pow(sin_phi,2);
-      var n   = this.a / Math.sqrt(con);
-      var ml  = this.a * Proj4js.common.mlfn(this.e0, this.e1, this.e2, this.e3, lat);
-
-      x = this.k0 * n * al * (1.0 + als / 6.0 * (1.0 - t + c + als / 20.0 * (5.0 - 18.0 * t + Math.pow(t,2) + 72.0 * c - 58.0 * this.ep2))) + this.x0;
-      y = this.k0 * (ml - this.ml0 + n * tq * (als * (0.5 + als / 24.0 * (5.0 - t + 9.0 * c + 4.0 * Math.pow(c,2) + als / 30.0 * (61.0 - 58.0 * t + Math.pow(t,2) + 600.0 * c - 330.0 * this.ep2))))) + this.y0;
-
-    }
-    p.x = x; p.y = y;
-    return p;
-  }, // tmercFwd()
-
-  /**
-    Transverse Mercator Inverse  -  x/y to long/lat
-  */
-  inverse : function(p) {
-    var con, phi;  /* temporary angles       */
-    var delta_phi; /* difference between longitudes    */
-    var i;
-    var max_iter = 6;      /* maximun number of iterations */
-    var lat, lon;
-
-    if (this.sphere) {   /* spherical form */
-      var f = Math.exp(p.x/(this.a * this.k0));
-      var g = .5 * (f - 1/f);
-      var temp = this.lat0 + p.y/(this.a * this.k0);
-      var h = Math.cos(temp);
-      con = Math.sqrt((1.0 - h * h)/(1.0 + g * g));
-      lat = Proj4js.common.asinz(con);
-      if (temp < 0)
-        lat = -lat;
-      if ((g == 0) && (h == 0)) {
-        lon = this.long0;
-      } else {
-        lon = Proj4js.common.adjust_lon(Math.atan2(g,h) + this.long0);
-      }
-    } else {    // ellipsoidal form
-      var x = p.x - this.x0;
-      var y = p.y - this.y0;
-
-      con = (this.ml0 + y / this.k0) / this.a;
-      phi = con;
-      for (i=0;true;i++) {
-        delta_phi=((con + this.e1 * Math.sin(2.0*phi) - this.e2 * Math.sin(4.0*phi) + this.e3 * Math.sin(6.0*phi)) / this.e0) - phi;
-        phi += delta_phi;
-        if (Math.abs(delta_phi) <= Proj4js.common.EPSLN) break;
-        if (i >= max_iter) {
-          Proj4js.reportError("tmerc:inverse: Latitude failed to converge");
-          return(95);
-        }
-      } // for()
-      if (Math.abs(phi) < Proj4js.common.HALF_PI) {
-        // sincos(phi, &sin_phi, &cos_phi);
-        var sin_phi=Math.sin(phi);
-        var cos_phi=Math.cos(phi);
-        var tan_phi = Math.tan(phi);
-        var c = this.ep2 * Math.pow(cos_phi,2);
-        var cs = Math.pow(c,2);
-        var t = Math.pow(tan_phi,2);
-        var ts = Math.pow(t,2);
-        con = 1.0 - this.es * Math.pow(sin_phi,2);
-        var n = this.a / Math.sqrt(con);
-        var r = n * (1.0 - this.es) / con;
-        var d = x / (n * this.k0);
-        var ds = Math.pow(d,2);
-        lat = phi - (n * tan_phi * ds / r) * (0.5 - ds / 24.0 * (5.0 + 3.0 * t + 10.0 * c - 4.0 * cs - 9.0 * this.ep2 - ds / 30.0 * (61.0 + 90.0 * t + 298.0 * c + 45.0 * ts - 252.0 * this.ep2 - 3.0 * cs)));
-        lon = Proj4js.common.adjust_lon(this.long0 + (d * (1.0 - ds / 6.0 * (1.0 + 2.0 * t + c - ds / 20.0 * (5.0 - 2.0 * c + 28.0 * t - 3.0 * cs + 8.0 * this.ep2 + 24.0 * ts))) / cos_phi));
-      } else {
-        lat = Proj4js.common.HALF_PI * Proj4js.common.sign(y);
-        lon = this.long0;
-      }
-    }
-    p.x = lon;
-    p.y = lat;
-    return p;
-  } // tmercInv()
-};
-/* ======================================================================
-    defs/GOOGLE.js
-   ====================================================================== */
-
-Proj4js.defs["GOOGLE"]="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";
-Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];
-/* ======================================================================
-    projCode/gstmerc.js
-   ====================================================================== */
-
-Proj4js.Proj.gstmerc = {
-  init : function() {
-
-    // array of:  a, b, lon0, lat0, k0, x0, y0
-      var temp= this.b / this.a;
-      this.e= Math.sqrt(1.0 - temp*temp);
-      this.lc= this.long0;
-      this.rs= Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));
-      var sinz= Math.sin(this.lat0);
-      var pc= Math.asin(sinz/this.rs);
-      var sinzpc= Math.sin(pc);
-      this.cp= Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);
-      this.n2= this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);
-      this.xs= this.x0;
-      this.ys= this.y0-this.n2*pc;
-
-      if (!this.title) this.title = "Gauss Schreiber transverse mercator";
-    },
-
-
-    // forward equations--mapping lat,long to x,y
-    // -----------------------------------------------------------------
-    forward : function(p) {
-
-      var lon= p.x;
-      var lat= p.y;
-
-      var L= this.rs*(lon-this.lc);
-      var Ls= this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));
-      var lat1= Math.asin(Math.sin(L)/Proj4js.common.cosh(Ls));
-      var Ls1= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
-      p.x= this.xs+(this.n2*Ls1);
-      p.y= this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));
-      return p;
-    },
-
-  // inverse equations--mapping x,y to lat/long
-  // -----------------------------------------------------------------
-  inverse : function(p) {
-
-    var x= p.x;
-    var y= p.y;
-
-    var L= Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));
-    var lat1= Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));
-    var LC= Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));
-    p.x= this.lc+L/this.rs;
-    p.y= Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);
-    return p;
-  }
-
-};
-/* ======================================================================
-    projCode/ortho.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                             ORTHOGRAPHIC 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Orthographic projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-Proj4js.Proj.ortho = {
-
-  /* Initialize the Orthographic projection
-    -------------------------------------*/
-  init: function(def) {
-    //double temp;			/* temporary variable		*/
-
-    /* Place parameters in static storage for common use
-      -------------------------------------------------*/;
-    this.sin_p14=Math.sin(this.lat0);
-    this.cos_p14=Math.cos(this.lat0);	
-  },
-
-
-  /* Orthographic forward equations--mapping lat,long to x,y
-    ---------------------------------------------------*/
-  forward: function(p) {
-    var sinphi, cosphi;	/* sin and cos value				*/
-    var dlon;		/* delta longitude value			*/
-    var coslon;		/* cos of longitude				*/
-    var ksp;		/* scale factor					*/
-    var g;		
-    var lon=p.x;
-    var lat=p.y;	
-    /* Forward equations
-      -----------------*/
-    dlon = Proj4js.common.adjust_lon(lon - this.long0);
-
-    sinphi=Math.sin(lat);
-    cosphi=Math.cos(lat);	
-
-    coslon = Math.cos(dlon);
-    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
-    ksp = 1.0;
-    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
-      var x = this.a * ksp * cosphi * Math.sin(dlon);
-      var y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
-    } else {
-      Proj4js.reportError("orthoFwdPointError");
-    }
-    p.x=x;
-    p.y=y;
-    return p;
-  },
-
-
-  inverse: function(p) {
-    var rh;		/* height above ellipsoid			*/
-    var z;		/* angle					*/
-    var sinz,cosz;	/* sin of z and cos of z			*/
-    var temp;
-    var con;
-    var lon , lat;
-    /* Inverse equations
-      -----------------*/
-    p.x -= this.x0;
-    p.y -= this.y0;
-    rh = Math.sqrt(p.x * p.x + p.y * p.y);
-    if (rh > this.a + .0000001) {
-      Proj4js.reportError("orthoInvDataError");
-    }
-    z = Proj4js.common.asinz(rh / this.a);
-
-    sinz=Math.sin(z);
-    cosz=Math.cos(z);
-
-    lon = this.long0;
-    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
-      lat = this.lat0; 
-    }
-    lat = Proj4js.common.asinz(cosz * this.sin_p14 + (p.y * sinz * this.cos_p14)/rh);
-    con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
-    if (Math.abs(con) <= Proj4js.common.EPSLN) {
-       if (this.lat0 >= 0) {
-          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
-       } else {
-          lon = Proj4js.common.adjust_lon(this.long0 -Math.atan2(-p.x, p.y));
-       }
-    }
-    con = cosz - this.sin_p14 * Math.sin(lat);
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }
-};
-
-
-/* ======================================================================
-    projCode/somerc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                       SWISS OBLIQUE MERCATOR
-
-PURPOSE:	Swiss projection.
-WARNING:  X and Y are inverted (weird) in the swiss coordinate system. Not
-   here, since we want X to be horizontal and Y vertical.
-
-ALGORITHM REFERENCES
-1. "Formules et constantes pour le Calcul pour la
- projection cylindrique conforme à axe oblique et pour la transformation entre
- des systèmes de référence".
- http://www.swisstopo.admin.ch/internet/swisstopo/fr/home/topics/survey/sys/refsys/switzerland.parsysrelated1.31216.downloadList.77004.DownloadFile.tmp/swissprojectionfr.pdf
-
-*******************************************************************************/
-
-Proj4js.Proj.somerc = {
-
-  init: function() {
-    var phy0 = this.lat0;
-    this.lambda0 = this.long0;
-    var sinPhy0 = Math.sin(phy0);
-    var semiMajorAxis = this.a;
-    var invF = this.rf;
-    var flattening = 1 / invF;
-    var e2 = 2 * flattening - Math.pow(flattening, 2);
-    var e = this.e = Math.sqrt(e2);
-    this.R = this.k0 * semiMajorAxis * Math.sqrt(1 - e2) / (1 - e2 * Math.pow(sinPhy0, 2.0));
-    this.alpha = Math.sqrt(1 + e2 / (1 - e2) * Math.pow(Math.cos(phy0), 4.0));
-    this.b0 = Math.asin(sinPhy0 / this.alpha);
-    this.K = Math.log(Math.tan(Math.PI / 4.0 + this.b0 / 2.0))
-            - this.alpha
-            * Math.log(Math.tan(Math.PI / 4.0 + phy0 / 2.0))
-            + this.alpha
-            * e / 2
-            * Math.log((1 + e * sinPhy0)
-            / (1 - e * sinPhy0));
-  },
-
-
-  forward: function(p) {
-    var Sa1 = Math.log(Math.tan(Math.PI / 4.0 - p.y / 2.0));
-    var Sa2 = this.e / 2.0
-            * Math.log((1 + this.e * Math.sin(p.y))
-            / (1 - this.e * Math.sin(p.y)));
-    var S = -this.alpha * (Sa1 + Sa2) + this.K;
-
-        // spheric latitude
-    var b = 2.0 * (Math.atan(Math.exp(S)) - Math.PI / 4.0);
-
-        // spheric longitude
-    var I = this.alpha * (p.x - this.lambda0);
-
-        // psoeudo equatorial rotation
-    var rotI = Math.atan(Math.sin(I)
-            / (Math.sin(this.b0) * Math.tan(b) +
-               Math.cos(this.b0) * Math.cos(I)));
-
-    var rotB = Math.asin(Math.cos(this.b0) * Math.sin(b) -
-                         Math.sin(this.b0) * Math.cos(b) * Math.cos(I));
-
-    p.y = this.R / 2.0
-            * Math.log((1 + Math.sin(rotB)) / (1 - Math.sin(rotB)))
-            + this.y0;
-    p.x = this.R * rotI + this.x0;
-    return p;
-  },
-
-  inverse: function(p) {
-    var Y = p.x - this.x0;
-    var X = p.y - this.y0;
-
-    var rotI = Y / this.R;
-    var rotB = 2 * (Math.atan(Math.exp(X / this.R)) - Math.PI / 4.0);
-
-    var b = Math.asin(Math.cos(this.b0) * Math.sin(rotB)
-            + Math.sin(this.b0) * Math.cos(rotB) * Math.cos(rotI));
-    var I = Math.atan(Math.sin(rotI)
-            / (Math.cos(this.b0) * Math.cos(rotI) - Math.sin(this.b0)
-            * Math.tan(rotB)));
-
-    var lambda = this.lambda0 + I / this.alpha;
-
-    var S = 0.0;
-    var phy = b;
-    var prevPhy = -1000.0;
-    var iteration = 0;
-    while (Math.abs(phy - prevPhy) > 0.0000001)
-    {
-      if (++iteration > 20)
-      {
-        Proj4js.reportError("omercFwdInfinity");
-        return;
-      }
-      //S = Math.log(Math.tan(Math.PI / 4.0 + phy / 2.0));
-      S = 1.0
-              / this.alpha
-              * (Math.log(Math.tan(Math.PI / 4.0 + b / 2.0)) - this.K)
-              + this.e
-              * Math.log(Math.tan(Math.PI / 4.0
-              + Math.asin(this.e * Math.sin(phy))
-              / 2.0));
-      prevPhy = phy;
-      phy = 2.0 * Math.atan(Math.exp(S)) - Math.PI / 2.0;
-    }
-
-    p.x = lambda;
-    p.y = phy;
-    return p;
-  }
-};
-/* ======================================================================
-    projCode/stere.js
-   ====================================================================== */
-
-
-// Initialize the Stereographic projection
-
-Proj4js.Proj.stere = {
-  ssfn_: function(phit, sinphi, eccen) {
-  	sinphi *= eccen;
-  	return (Math.tan (.5 * (Proj4js.common.HALF_PI + phit)) * Math.pow((1. - sinphi) / (1. + sinphi), .5 * eccen));
-  },
-  TOL:	1.e-8,
-  NITER:	8,
-  CONV:	1.e-10,
-  S_POLE:	0,
-  N_POLE:	1,
-  OBLIQ:	2,
-  EQUIT:	3,
-
-  init : function() {
-  	this.phits = this.lat_ts ? this.lat_ts : Proj4js.common.HALF_PI;
-    var t = Math.abs(this.lat0);
-  	if ((Math.abs(t) - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
-  		this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
-  	} else {
-  		this.mode = t > Proj4js.common.EPSLN ? this.OBLIQ : this.EQUIT;
-    }
-  	this.phits = Math.abs(this.phits);
-  	if (this.es) {
-  		var X;
-
-  		switch (this.mode) {
-  		case this.N_POLE:
-  		case this.S_POLE:
-  			if (Math.abs(this.phits - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
-  				this.akm1 = 2. * this.k0 / Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));
-  			} else {
-          t = Math.sin(this.phits);
-  				this.akm1 = Math.cos(this.phits) / Proj4js.common.tsfnz(this.e, this.phits, t);
-  				t *= this.e;
-  				this.akm1 /= Math.sqrt(1. - t * t);
-  			}
-  			break;
-  		case this.EQUIT:
-  			this.akm1 = 2. * this.k0;
-  			break;
-  		case this.OBLIQ:
-  			t = Math.sin(this.lat0);
-  			X = 2. * Math.atan(this.ssfn_(this.lat0, t, this.e)) - Proj4js.common.HALF_PI;
-  			t *= this.e;
-  			this.akm1 = 2. * this.k0 * Math.cos(this.lat0) / Math.sqrt(1. - t * t);
-  			this.sinX1 = Math.sin(X);
-  			this.cosX1 = Math.cos(X);
-  			break;
-  		}
-  	} else {
-  		switch (this.mode) {
-  		case this.OBLIQ:
-  			this.sinph0 = Math.sin(this.lat0);
-  			this.cosph0 = Math.cos(this.lat0);
-  		case this.EQUIT:
-  			this.akm1 = 2. * this.k0;
-  			break;
-  		case this.S_POLE:
-  		case this.N_POLE:
-  			this.akm1 = Math.abs(this.phits - Proj4js.common.HALF_PI) >= Proj4js.common.EPSLN ?
-  			   Math.cos(this.phits) / Math.tan(Proj4js.common.FORTPI - .5 * this.phits) :
-  			   2. * this.k0 ;
-  			break;
-  		}
-  	}
-  }, 
-
-// Stereographic forward equations--mapping lat,long to x,y
-  forward: function(p) {
-    var lon = p.x;
-    lon = Proj4js.common.adjust_lon(lon - this.long0);
-    var lat = p.y;
-    var x, y;
-    
-    if (this.sphere) {
-    	var  sinphi, cosphi, coslam, sinlam;
-
-    	sinphi = Math.sin(lat);
-    	cosphi = Math.cos(lat);
-    	coslam = Math.cos(lon);
-    	sinlam = Math.sin(lon);
-    	switch (this.mode) {
-    	case this.EQUIT:
-    		y = 1. + cosphi * coslam;
-    		if (y <= Proj4js.common.EPSLN) {
-          F_ERROR;
-        }
-        y = this.akm1 / y;
-    		x = y * cosphi * sinlam;
-        y *= sinphi;
-    		break;
-    	case this.OBLIQ:
-    		y = 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
-    		if (y <= Proj4js.common.EPSLN) {
-          F_ERROR;
-        }
-        y = this.akm1 / y;
-    		x = y * cosphi * sinlam;
-    		y *= this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
-    		break;
-    	case this.N_POLE:
-    		coslam = -coslam;
-    		lat = -lat;
-        //Note  no break here so it conitnues through S_POLE
-    	case this.S_POLE:
-    		if (Math.abs(lat - Proj4js.common.HALF_PI) < this.TOL) {
-          F_ERROR;
-        }
-        y = this.akm1 * Math.tan(Proj4js.common.FORTPI + .5 * lat);
-    		x = sinlam * y;
-    		y *= coslam;
-    		break;
-    	}
-    } else {
-    	coslam = Math.cos(lon);
-    	sinlam = Math.sin(lon);
-    	sinphi = Math.sin(lat);
-    	if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
-        X = 2. * Math.atan(this.ssfn_(lat, sinphi, this.e));
-    		sinX = Math.sin(X - Proj4js.common.HALF_PI);
-    		cosX = Math.cos(X);
-    	}
-    	switch (this.mode) {
-    	case this.OBLIQ:
-    		A = this.akm1 / (this.cosX1 * (1. + this.sinX1 * sinX + this.cosX1 * cosX * coslam));
-    		y = A * (this.cosX1 * sinX - this.sinX1 * cosX * coslam);
-    		x = A * cosX;
-    		break;
-    	case this.EQUIT:
-    		A = 2. * this.akm1 / (1. + cosX * coslam);
-    		y = A * sinX;
-    		x = A * cosX;
-    		break;
-    	case this.S_POLE:
-    		lat = -lat;
-    		coslam = - coslam;
-    		sinphi = -sinphi;
-    	case this.N_POLE:
-    		x = this.akm1 * Proj4js.common.tsfnz(this.e, lat, sinphi);
-    		y = - x * coslam;
-    		break;
-    	}
-    	x = x * sinlam;
-    }
-    p.x = x*this.a + this.x0;
-    p.y = y*this.a + this.y0;
-    return p;
-  },
-
-
-//* Stereographic inverse equations--mapping x,y to lat/long
-  inverse: function(p) {
-    var x = (p.x - this.x0)/this.a;   /* descale and de-offset */
-    var y = (p.y - this.y0)/this.a;
-    var lon, lat;
-
-    var cosphi, sinphi, tp=0.0, phi_l=0.0, rho, halfe=0.0, pi2=0.0;
-    var i;
-
-    if (this.sphere) {
-    	var  c, rh, sinc, cosc;
-
-      rh = Math.sqrt(x*x + y*y);
-      c = 2. * Math.atan(rh / this.akm1);
-    	sinc = Math.sin(c);
-    	cosc = Math.cos(c);
-    	lon = 0.;
-    	switch (this.mode) {
-    	case this.EQUIT:
-    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
-    			lat = 0.;
-    		} else {
-    			lat = Math.asin(y * sinc / rh);
-        }
-    		if (cosc != 0. || x != 0.) lon = Math.atan2(x * sinc, cosc * rh);
-    		break;
-    	case this.OBLIQ:
-    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
-    			lat = this.phi0;
-    		} else {
-    			lat = Math.asin(cosc * sinph0 + y * sinc * cosph0 / rh);
-        }
-        c = cosc - sinph0 * Math.sin(lat);
-    		if (c != 0. || x != 0.) {
-    			lon = Math.atan2(x * sinc * cosph0, c * rh);
-        }
-    		break;
-    	case this.N_POLE:
-    		y = -y;
-    	case this.S_POLE:
-    		if (Math.abs(rh) <= Proj4js.common.EPSLN) {
-    			lat = this.phi0;
-    		} else {
-    			lat = Math.asin(this.mode == this.S_POLE ? -cosc : cosc);
-        }
-    		lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
-    		break;
-    	}
-    } else {
-    	rho = Math.sqrt(x*x + y*y);
-    	switch (this.mode) {
-    	case this.OBLIQ:
-    	case this.EQUIT:
-        tp = 2. * Math.atan2(rho * this.cosX1 , this.akm1);
-    		cosphi = Math.cos(tp);
-    		sinphi = Math.sin(tp);
-        if( rho == 0.0 ) {
-    		  phi_l = Math.asin(cosphi * this.sinX1);
-        } else {
-    		  phi_l = Math.asin(cosphi * this.sinX1 + (y * sinphi * this.cosX1 / rho));
-        }
-
-    		tp = Math.tan(.5 * (Proj4js.common.HALF_PI + phi_l));
-    		x *= sinphi;
-    		y = rho * this.cosX1 * cosphi - y * this.sinX1* sinphi;
-    		pi2 = Proj4js.common.HALF_PI;
-    		halfe = .5 * this.e;
-    		break;
-    	case this.N_POLE:
-    		y = -y;
-    	case this.S_POLE:
-        tp = - rho / this.akm1;
-    		phi_l = Proj4js.common.HALF_PI - 2. * Math.atan(tp);
-    		pi2 = -Proj4js.common.HALF_PI;
-    		halfe = -.5 * this.e;
-    		break;
-    	}
-    	for (i = this.NITER; i--; phi_l = lat) { //check this
-    		sinphi = this.e * Math.sin(phi_l);
-    		lat = 2. * Math.atan(tp * Math.pow((1.+sinphi)/(1.-sinphi), halfe)) - pi2;
-    		if (Math.abs(phi_l - lat) < this.CONV) {
-    			if (this.mode == this.S_POLE) lat = -lat;
-    			lon = (x == 0. && y == 0.) ? 0. : Math.atan2(x, y);
-          p.x = Proj4js.common.adjust_lon(lon + this.long0);
-          p.y = lat;
-    			return p;
-    		}
-    	}
-    }
-  }
-}; 
-/* ======================================================================
-    projCode/nzmg.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            NEW ZEALAND MAP GRID
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the New Zealand Map Grid projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-
-ALGORITHM REFERENCES
-
-1.  Department of Land and Survey Technical Circular 1973/32
-      http://www.linz.govt.nz/docs/miscellaneous/nz-map-definition.pdf
-
-2.  OSG Technical Report 4.1
-      http://www.linz.govt.nz/docs/miscellaneous/nzmg.pdf
-
-
-IMPLEMENTATION NOTES
-
-The two references use different symbols for the calculated values. This
-implementation uses the variable names similar to the symbols in reference [1].
-
-The alogrithm uses different units for delta latitude and delta longitude.
-The delta latitude is assumed to be in units of seconds of arc x 10^-5.
-The delta longitude is the usual radians. Look out for these conversions.
-
-The algorithm is described using complex arithmetic. There were three
-options:
-   * find and use a Javascript library for complex arithmetic
-   * write my own complex library
-   * expand the complex arithmetic by hand to simple arithmetic
-
-This implementation has expanded the complex multiplication operations
-into parallel simple arithmetic operations for the real and imaginary parts.
-The imaginary part is way over to the right of the display; this probably
-violates every coding standard in the world, but, to me, it makes it much
-more obvious what is going on.
-
-The following complex operations are used:
-   - addition
-   - multiplication
-   - division
-   - complex number raised to integer power
-   - summation
-
-A summary of complex arithmetic operations:
-   (from http://en.wikipedia.org/wiki/Complex_arithmetic)
-   addition:       (a + bi) + (c + di) = (a + c) + (b + d)i
-   subtraction:    (a + bi) - (c + di) = (a - c) + (b - d)i
-   multiplication: (a + bi) x (c + di) = (ac - bd) + (bc + ad)i
-   division:       (a + bi) / (c + di) = [(ac + bd)/(cc + dd)] + [(bc - ad)/(cc + dd)]i
-
-The algorithm needs to calculate summations of simple and complex numbers. This is
-implemented using a for-loop, pre-loading the summed value to zero.
-
-The algorithm needs to calculate theta^2, theta^3, etc while doing a summation.
-There are three possible implementations:
-   - use Math.pow in the summation loop - except for complex numbers
-   - precalculate the values before running the loop
-   - calculate theta^n = theta^(n-1) * theta during the loop
-This implementation uses the third option for both real and complex arithmetic.
-
-For example
-   psi_n = 1;
-   sum = 0;
-   for (n = 1; n <=6; n++) {
-      psi_n1 = psi_n * psi;       // calculate psi^(n+1)
-      psi_n = psi_n1;
-      sum = sum + A[n] * psi_n;
-   }
-
-
-TEST VECTORS
-
-NZMG E, N:         2487100.638      6751049.719     metres
-NZGD49 long, lat:      172.739194       -34.444066  degrees
-
-NZMG E, N:         2486533.395      6077263.661     metres
-NZGD49 long, lat:      172.723106       -40.512409  degrees
-
-NZMG E, N:         2216746.425      5388508.765     metres
-NZGD49 long, lat:      169.172062       -46.651295  degrees
-
-Note that these test vectors convert from NZMG metres to lat/long referenced
-to NZGD49, not the more usual WGS84. The difference is about 70m N/S and about
-10m E/W.
-
-These test vectors are provided in reference [1]. Many more test
-vectors are available in
-   http://www.linz.govt.nz/docs/topography/topographicdata/placenamesdatabase/nznamesmar08.zip
-which is a catalog of names on the 260-series maps.
-
-
-EPSG CODES
-
-NZMG     EPSG:27200
-NZGD49   EPSG:4272
-
-http://spatialreference.org/ defines these as
-  Proj4js.defs["EPSG:4272"] = "+proj=longlat +ellps=intl +datum=nzgd49 +no_defs ";
-  Proj4js.defs["EPSG:27200"] = "+proj=nzmg +lat_0=-41 +lon_0=173 +x_0=2510000 +y_0=6023150 +ellps=intl +datum=nzgd49 +units=m +no_defs ";
-
-
-LICENSE
-  Copyright: Stephen Irons 2008
-  Released under terms of the LGPL as per: http://www.gnu.org/copyleft/lesser.html
-
-*******************************************************************************/
-
-
-/**
-  Initialize New Zealand Map Grip projection
-*/
-
-Proj4js.Proj.nzmg = {
-
-  /**
-   * iterations: Number of iterations to refine inverse transform.
-   *     0 -> km accuracy
-   *     1 -> m accuracy -- suitable for most mapping applications
-   *     2 -> mm accuracy
-   */
-  iterations: 1,
-
-  init : function() {
-    this.A = new Array();
-    this.A[1]  = +0.6399175073;
-    this.A[2]  = -0.1358797613;
-    this.A[3]  = +0.063294409;
-    this.A[4]  = -0.02526853;
-    this.A[5]  = +0.0117879;
-    this.A[6]  = -0.0055161;
-    this.A[7]  = +0.0026906;
-    this.A[8]  = -0.001333;
-    this.A[9]  = +0.00067;
-    this.A[10] = -0.00034;
-
-    this.B_re = new Array();        this.B_im = new Array();
-    this.B_re[1] = +0.7557853228;   this.B_im[1] =  0.0;
-    this.B_re[2] = +0.249204646;    this.B_im[2] = +0.003371507;
-    this.B_re[3] = -0.001541739;    this.B_im[3] = +0.041058560;
-    this.B_re[4] = -0.10162907;     this.B_im[4] = +0.01727609;
-    this.B_re[5] = -0.26623489;     this.B_im[5] = -0.36249218;
-    this.B_re[6] = -0.6870983;      this.B_im[6] = -1.1651967;
-
-    this.C_re = new Array();        this.C_im = new Array();
-    this.C_re[1] = +1.3231270439;   this.C_im[1] =  0.0;
-    this.C_re[2] = -0.577245789;    this.C_im[2] = -0.007809598;
-    this.C_re[3] = +0.508307513;    this.C_im[3] = -0.112208952;
-    this.C_re[4] = -0.15094762;     this.C_im[4] = +0.18200602;
-    this.C_re[5] = +1.01418179;     this.C_im[5] = +1.64497696;
-    this.C_re[6] = +1.9660549;      this.C_im[6] = +2.5127645;
-
-    this.D = new Array();
-    this.D[1] = +1.5627014243;
-    this.D[2] = +0.5185406398;
-    this.D[3] = -0.03333098;
-    this.D[4] = -0.1052906;
-    this.D[5] = -0.0368594;
-    this.D[6] = +0.007317;
-    this.D[7] = +0.01220;
-    this.D[8] = +0.00394;
-    this.D[9] = -0.0013;
-  },
-
-  /**
-    New Zealand Map Grid Forward  - long/lat to x/y
-    long/lat in radians
-  */
-  forward : function(p) {
-    var lon = p.x;
-    var lat = p.y;
-
-    var delta_lat = lat - this.lat0;
-    var delta_lon = lon - this.long0;
-
-    // 1. Calculate d_phi and d_psi    ...                          // and d_lambda
-    // For this algorithm, delta_latitude is in seconds of arc x 10-5, so we need to scale to those units. Longitude is radians.
-    var d_phi = delta_lat / Proj4js.common.SEC_TO_RAD * 1E-5;       var d_lambda = delta_lon;
-    var d_phi_n = 1;  // d_phi^0
-
-    var d_psi = 0;
-    for (n = 1; n <= 10; n++) {
-      d_phi_n = d_phi_n * d_phi;
-      d_psi = d_psi + this.A[n] * d_phi_n;
-    }
-
-    // 2. Calculate theta
-    var th_re = d_psi;                                              var th_im = d_lambda;
-
-    // 3. Calculate z
-    var th_n_re = 1;                                                var th_n_im = 0;  // theta^0
-    var th_n_re1;                                                   var th_n_im1;
-
-    var z_re = 0;                                                   var z_im = 0;
-    for (n = 1; n <= 6; n++) {
-      th_n_re1 = th_n_re*th_re - th_n_im*th_im;                     th_n_im1 = th_n_im*th_re + th_n_re*th_im;
-      th_n_re = th_n_re1;                                           th_n_im = th_n_im1;
-      z_re = z_re + this.B_re[n]*th_n_re - this.B_im[n]*th_n_im;    z_im = z_im + this.B_im[n]*th_n_re + this.B_re[n]*th_n_im;
-    }
-
-    // 4. Calculate easting and northing
-    x = (z_im * this.a) + this.x0;
-    y = (z_re * this.a) + this.y0;
-
-    p.x = x; p.y = y;
-
-    return p;
-  },
-
-
-  /**
-    New Zealand Map Grid Inverse  -  x/y to long/lat
-  */
-  inverse : function(p) {
-
-    var x = p.x;
-    var y = p.y;
-
-    var delta_x = x - this.x0;
-    var delta_y = y - this.y0;
-
-    // 1. Calculate z
-    var z_re = delta_y / this.a;                                              var z_im = delta_x / this.a;
-
-    // 2a. Calculate theta - first approximation gives km accuracy
-    var z_n_re = 1;                                                           var z_n_im = 0;  // z^0
-    var z_n_re1;                                                              var z_n_im1;
-
-    var th_re = 0;                                                            var th_im = 0;
-    for (n = 1; n <= 6; n++) {
-      z_n_re1 = z_n_re*z_re - z_n_im*z_im;                                    z_n_im1 = z_n_im*z_re + z_n_re*z_im;
-      z_n_re = z_n_re1;                                                       z_n_im = z_n_im1;
-      th_re = th_re + this.C_re[n]*z_n_re - this.C_im[n]*z_n_im;              th_im = th_im + this.C_im[n]*z_n_re + this.C_re[n]*z_n_im;
-    }
-
-    // 2b. Iterate to refine the accuracy of the calculation
-    //        0 iterations gives km accuracy
-    //        1 iteration gives m accuracy -- good enough for most mapping applications
-    //        2 iterations bives mm accuracy
-    for (i = 0; i < this.iterations; i++) {
-       var th_n_re = th_re;                                                      var th_n_im = th_im;
-       var th_n_re1;                                                             var th_n_im1;
-
-       var num_re = z_re;                                                        var num_im = z_im;
-       for (n = 2; n <= 6; n++) {
-         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
-         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
-         num_re = num_re + (n-1)*(this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);  num_im = num_im + (n-1)*(this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
-       }
-
-       th_n_re = 1;                                                              th_n_im = 0;
-       var den_re = this.B_re[1];                                                var den_im = this.B_im[1];
-       for (n = 2; n <= 6; n++) {
-         th_n_re1 = th_n_re*th_re - th_n_im*th_im;                               th_n_im1 = th_n_im*th_re + th_n_re*th_im;
-         th_n_re = th_n_re1;                                                     th_n_im = th_n_im1;
-         den_re = den_re + n * (this.B_re[n]*th_n_re - this.B_im[n]*th_n_im);    den_im = den_im + n * (this.B_im[n]*th_n_re + this.B_re[n]*th_n_im);
-       }
-
-       // Complex division
-       var den2 = den_re*den_re + den_im*den_im;
-       th_re = (num_re*den_re + num_im*den_im) / den2;                           th_im = (num_im*den_re - num_re*den_im) / den2;
-    }
-
-    // 3. Calculate d_phi              ...                                    // and d_lambda
-    var d_psi = th_re;                                                        var d_lambda = th_im;
-    var d_psi_n = 1;  // d_psi^0
-
-    var d_phi = 0;
-    for (n = 1; n <= 9; n++) {
-       d_psi_n = d_psi_n * d_psi;
-       d_phi = d_phi + this.D[n] * d_psi_n;
-    }
-
-    // 4. Calculate latitude and longitude
-    // d_phi is calcuated in second of arc * 10^-5, so we need to scale back to radians. d_lambda is in radians.
-    var lat = this.lat0 + (d_phi * Proj4js.common.SEC_TO_RAD * 1E5);
-    var lon = this.long0 +  d_lambda;
-
-    p.x = lon;
-    p.y = lat;
-
-    return p;
-  }
-};
-/* ======================================================================
-    projCode/mill.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                    MILLER CYLINDRICAL 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Miller Cylindrical projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE            
-----------              ----           
-T. Mittan		March, 1993
-
-This function was adapted from the Lambert Azimuthal Equal Area projection
-code (FORTRAN) in the General Cartographic Transformation Package software
-which is available from the U.S. Geological Survey National Mapping Division.
- 
-ALGORITHM REFERENCES
-
-1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
-    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
-
-2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-3.  "Software Documentation for GCTP General Cartographic Transformation
-    Package", U.S. Geological Survey National Mapping Division, May 1982.
-*******************************************************************************/
-
-Proj4js.Proj.mill = {
-
-/* Initialize the Miller Cylindrical projection
-  -------------------------------------------*/
-  init: function() {
-    //no-op
-  },
-
-
-  /* Miller Cylindrical forward equations--mapping lat,long to x,y
-    ------------------------------------------------------------*/
-  forward: function(p) {
-    var lon=p.x;
-    var lat=p.y;
-    /* Forward equations
-      -----------------*/
-    var dlon = Proj4js.common.adjust_lon(lon -this.long0);
-    var x = this.x0 + this.a * dlon;
-    var y = this.y0 + this.a * Math.log(Math.tan((Proj4js.common.PI / 4.0) + (lat / 2.5))) * 1.25;
-
-    p.x=x;
-    p.y=y;
-    return p;
-  },//millFwd()
-
-  /* Miller Cylindrical inverse equations--mapping x,y to lat/long
-    ------------------------------------------------------------*/
-  inverse: function(p) {
-    p.x -= this.x0;
-    p.y -= this.y0;
-
-    var lon = Proj4js.common.adjust_lon(this.long0 + p.x /this.a);
-    var lat = 2.5 * (Math.atan(Math.exp(0.8*p.y/this.a)) - Proj4js.common.PI / 4.0);
-
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }//millInv()
-};
-/* ======================================================================
-    projCode/gnom.js
-   ====================================================================== */
-
-/*****************************************************************************
-NAME                             GNOMONIC
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Gnomonic Projection.
-                Implementation based on the existing sterea and ortho
-                implementations.
-
-PROGRAMMER              DATE
-----------              ----
-Richard Marsden         November 2009
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
-    Projections", University of Chicago Press 1993
-
-2.  Wolfram Mathworld "Gnomonic Projection"
-    http://mathworld.wolfram.com/GnomonicProjection.html
-    Accessed: 12th November 2009
-******************************************************************************/
-
-Proj4js.Proj.gnom = {
-
-  /* Initialize the Gnomonic projection
-    -------------------------------------*/
-  init: function(def) {
-
-    /* Place parameters in static storage for common use
-      -------------------------------------------------*/
-    this.sin_p14=Math.sin(this.lat0);
-    this.cos_p14=Math.cos(this.lat0);
-    // Approximation for projecting points to the horizon (infinity)
-    this.infinity_dist = 1000 * this.a;
-    this.rc = 1;
-  },
-
-
-  /* Gnomonic forward equations--mapping lat,long to x,y
-    ---------------------------------------------------*/
-  forward: function(p) {
-    var sinphi, cosphi;	/* sin and cos value				*/
-    var dlon;		/* delta longitude value			*/
-    var coslon;		/* cos of longitude				*/
-    var ksp;		/* scale factor					*/
-    var g;		
-    var lon=p.x;
-    var lat=p.y;	
-    /* Forward equations
-      -----------------*/
-    dlon = Proj4js.common.adjust_lon(lon - this.long0);
-
-    sinphi=Math.sin(lat);
-    cosphi=Math.cos(lat);	
-
-    coslon = Math.cos(dlon);
-    g = this.sin_p14 * sinphi + this.cos_p14 * cosphi * coslon;
-    ksp = 1.0;
-    if ((g > 0) || (Math.abs(g) <= Proj4js.common.EPSLN)) {
-      x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon) / g;
-      y = this.y0 + this.a * ksp * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon) / g;
-    } else {
-      Proj4js.reportError("orthoFwdPointError");
-
-      // Point is in the opposing hemisphere and is unprojectable
-      // We still need to return a reasonable point, so we project 
-      // to infinity, on a bearing 
-      // equivalent to the northern hemisphere equivalent
-      // This is a reasonable approximation for short shapes and lines that 
-      // straddle the horizon.
-
-      x = this.x0 + this.infinity_dist * cosphi * Math.sin(dlon);
-      y = this.y0 + this.infinity_dist * (this.cos_p14 * sinphi - this.sin_p14 * cosphi * coslon);
-
-    }
-    p.x=x;
-    p.y=y;
-    return p;
-  },
-
-
-  inverse: function(p) {
-    var rh;		/* Rho */
-    var z;		/* angle */
-    var sinc, cosc;
-    var c;
-    var lon , lat;
-
-    /* Inverse equations
-      -----------------*/
-    p.x = (p.x - this.x0) / this.a;
-    p.y = (p.y - this.y0) / this.a;
-
-    p.x /= this.k0;
-    p.y /= this.k0;
-
-    if ( (rh = Math.sqrt(p.x * p.x + p.y * p.y)) ) {
-      c = Math.atan2(rh, this.rc);
-      sinc = Math.sin(c);
-      cosc = Math.cos(c);
-
-      lat = Proj4js.common.asinz(cosc*this.sin_p14 + (p.y*sinc*this.cos_p14) / rh);
-      lon = Math.atan2(p.x*sinc, rh*this.cos_p14*cosc - p.y*this.sin_p14*sinc);
-      lon = Proj4js.common.adjust_lon(this.long0+lon);
-    } else {
-      lat = this.phic0;
-      lon = 0.0;
-    }
- 
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }
-};
-
-
-/* ======================================================================
-    projCode/sinu.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                  		SINUSOIDAL
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Sinusoidal projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE            
-----------              ----           
-D. Steinwand, EROS      May, 1991     
-
-This function was adapted from the Sinusoidal projection code (FORTRAN) in the 
-General Cartographic Transformation Package software which is available from 
-the U.S. Geological Survey National Mapping Division.
- 
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  "Software Documentation for GCTP General Cartographic Transformation
-    Package", U.S. Geological Survey National Mapping Division, May 1982.
-*******************************************************************************/
-
-Proj4js.Proj.sinu = {
-
-	/* Initialize the Sinusoidal projection
-	  ------------------------------------*/
-	init: function() {
-		/* Place parameters in static storage for common use
-		  -------------------------------------------------*/
-		this.R = 6370997.0; //Radius of earth
-	},
-
-	/* Sinusoidal forward equations--mapping lat,long to x,y
-	-----------------------------------------------------*/
-	forward: function(p) {
-		var x,y,delta_lon;	
-		var lon=p.x;
-		var lat=p.y;	
-		/* Forward equations
-		-----------------*/
-		delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
-		x = this.R * delta_lon * Math.cos(lat) + this.x0;
-		y = this.R * lat + this.y0;
-
-		p.x=x;
-		p.y=y;	
-		return p;
-	},
-
-	inverse: function(p) {
-		var lat,temp,lon;	
-
-		/* Inverse equations
-		  -----------------*/
-		p.x -= this.x0;
-		p.y -= this.y0;
-		lat = p.y / this.R;
-		if (Math.abs(lat) > Proj4js.common.HALF_PI) {
-		    Proj4js.reportError("sinu:Inv:DataError");
-		}
-		temp = Math.abs(lat) - Proj4js.common.HALF_PI;
-		if (Math.abs(temp) > Proj4js.common.EPSLN) {
-			temp = this.long0+ p.x / (this.R *Math.cos(lat));
-			lon = Proj4js.common.adjust_lon(temp);
-		} else {
-			lon = this.long0;
-		}
-		  
-		p.x=lon;
-		p.y=lat;
-		return p;
-	}
-};
-
-
-/* ======================================================================
-    projCode/vandg.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                    VAN DER GRINTEN 
-
-PURPOSE:	Transforms input Easting and Northing to longitude and
-		latitude for the Van der Grinten projection.  The
-		Easting and Northing must be in meters.  The longitude
-		and latitude values will be returned in radians.
-
-PROGRAMMER              DATE            
-----------              ----           
-T. Mittan		March, 1993
-
-This function was adapted from the Van Der Grinten projection code
-(FORTRAN) in the General Cartographic Transformation Package software
-which is available from the U.S. Geological Survey National Mapping Division.
- 
-ALGORITHM REFERENCES
-
-1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
-    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
-
-2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-3.  "Software Documentation for GCTP General Cartographic Transformation
-    Package", U.S. Geological Survey National Mapping Division, May 1982.
-*******************************************************************************/
-
-Proj4js.Proj.vandg = {
-
-/* Initialize the Van Der Grinten projection
-  ----------------------------------------*/
-	init: function() {
-		this.R = 6370997.0; //Radius of earth
-	},
-
-	forward: function(p) {
-
-		var lon=p.x;
-		var lat=p.y;	
-
-		/* Forward equations
-		-----------------*/
-		var dlon = Proj4js.common.adjust_lon(lon - this.long0);
-		var x,y;
-
-		if (Math.abs(lat) <= Proj4js.common.EPSLN) {
-			x = this.x0  + this.R * dlon;
-			y = this.y0;
-		}
-		var theta = Proj4js.common.asinz(2.0 * Math.abs(lat / Proj4js.common.PI));
-		if ((Math.abs(dlon) <= Proj4js.common.EPSLN) || (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN)) {
-			x = this.x0;
-			if (lat >= 0) {
-				y = this.y0 + Proj4js.common.PI * this.R * Math.tan(.5 * theta);
-			} else {
-				y = this.y0 + Proj4js.common.PI * this.R * - Math.tan(.5 * theta);
-			}
-			//  return(OK);
-		}
-		var al = .5 * Math.abs((Proj4js.common.PI / dlon) - (dlon / Proj4js.common.PI));
-		var asq = al * al;
-		var sinth = Math.sin(theta);
-		var costh = Math.cos(theta);
-
-		var g = costh / (sinth + costh - 1.0);
-		var gsq = g * g;
-		var m = g * (2.0 / sinth - 1.0);
-		var msq = m * m;
-		var con = Proj4js.common.PI * this.R * (al * (g - msq) + Math.sqrt(asq * (g - msq) * (g - msq) - (msq + asq) * (gsq - msq))) / (msq + asq);
-		if (dlon < 0) {
-		 con = -con;
-		}
-		x = this.x0 + con;
-		con = Math.abs(con / (Proj4js.common.PI * this.R));
-		if (lat >= 0) {
-		 y = this.y0 + Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
-		} else {
-		 y = this.y0 - Proj4js.common.PI * this.R * Math.sqrt(1.0 - con * con - 2.0 * al * con);
-		}
-		p.x = x;
-		p.y = y;
-		return p;
-	},
-
-/* Van Der Grinten inverse equations--mapping x,y to lat/long
-  ---------------------------------------------------------*/
-	inverse: function(p) {
-		var dlon;
-		var xx,yy,xys,c1,c2,c3;
-		var al,asq;
-		var a1;
-		var m1;
-		var con;
-		var th1;
-		var d;
-
-		/* inverse equations
-		-----------------*/
-		p.x -= this.x0;
-		p.y -= this.y0;
-		con = Proj4js.common.PI * this.R;
-		xx = p.x / con;
-		yy =p.y / con;
-		xys = xx * xx + yy * yy;
-		c1 = -Math.abs(yy) * (1.0 + xys);
-		c2 = c1 - 2.0 * yy * yy + xx * xx;
-		c3 = -2.0 * c1 + 1.0 + 2.0 * yy * yy + xys * xys;
-		d = yy * yy / c3 + (2.0 * c2 * c2 * c2 / c3 / c3 / c3 - 9.0 * c1 * c2 / c3 /c3) / 27.0;
-		a1 = (c1 - c2 * c2 / 3.0 / c3) / c3;
-		m1 = 2.0 * Math.sqrt( -a1 / 3.0);
-		con = ((3.0 * d) / a1) / m1;
-		if (Math.abs(con) > 1.0) {
-			if (con >= 0.0) {
-				con = 1.0;
-			} else {
-				con = -1.0;
-			}
-		}
-		th1 = Math.acos(con) / 3.0;
-		if (p.y >= 0) {
-			lat = (-m1 *Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
-		} else {
-			lat = -(-m1 * Math.cos(th1 + Proj4js.common.PI / 3.0) - c2 / 3.0 / c3) * Proj4js.common.PI;
-		}
-
-		if (Math.abs(xx) < Proj4js.common.EPSLN) {
-			lon = this.long0;
-		}
-		lon = Proj4js.common.adjust_lon(this.long0 + Proj4js.common.PI * (xys - 1.0 + Math.sqrt(1.0 + 2.0 * (xx * xx - yy * yy) + xys * xys)) / 2.0 / xx);
-
-		p.x=lon;
-		p.y=lat;
-		return p;
-	}
-};
-/* ======================================================================
-    projCode/cea.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                    LAMBERT CYLINDRICAL EQUAL AREA
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Lambert Cylindrical Equal Area projection.
-                This class of projection includes the Behrmann and 
-                Gall-Peters Projections.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE            
-----------              ----
-R. Marsden              August 2009
-Winwaed Software Tech LLC, http://www.winwaed.com
-
-This function was adapted from the Miller Cylindrical Projection in the Proj4JS
-library.
-
-Note: This implementation assumes a Spherical Earth. The (commented) code 
-has been included for the ellipsoidal forward transform, but derivation of 
-the ellispoidal inverse transform is beyond me. Note that most of the 
-Proj4JS implementations do NOT currently support ellipsoidal figures. 
-Therefore this is not seen as a problem - especially this lack of support 
-is explicitly stated here.
- 
-ALGORITHM REFERENCES
-
-1.  "Cartographic Projection Procedures for the UNIX Environment - 
-     A User's Manual" by Gerald I. Evenden, USGS Open File Report 90-284
-    and Release 4 Interim Reports (2003)
-
-2.  Snyder, John P., "Flattening the Earth - Two Thousand Years of Map 
-    Projections", Univ. Chicago Press, 1993
-*******************************************************************************/
-
-Proj4js.Proj.cea = {
-
-/* Initialize the Cylindrical Equal Area projection
-  -------------------------------------------*/
-  init: function() {
-    //no-op
-  },
-
-
-  /* Cylindrical Equal Area forward equations--mapping lat,long to x,y
-    ------------------------------------------------------------*/
-  forward: function(p) {
-    var lon=p.x;
-    var lat=p.y;
-    /* Forward equations
-      -----------------*/
-    dlon = Proj4js.common.adjust_lon(lon -this.long0);
-    var x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
-    var y = this.y0 + this.a * Math.sin(lat) / Math.cos(this.lat_ts);
-   /* Elliptical Forward Transform
-      Not implemented due to a lack of a matchign inverse function
-    {
-      var Sin_Lat = Math.sin(lat);
-      var Rn = this.a * (Math.sqrt(1.0e0 - this.es * Sin_Lat * Sin_Lat ));
-      x = this.x0 + this.a * dlon * Math.cos(this.lat_ts);
-      y = this.y0 + Rn * Math.sin(lat) / Math.cos(this.lat_ts);
-    }
-   */
-
-
-    p.x=x;
-    p.y=y;
-    return p;
-  },//ceaFwd()
-
-  /* Cylindrical Equal Area inverse equations--mapping x,y to lat/long
-    ------------------------------------------------------------*/
-  inverse: function(p) {
-    p.x -= this.x0;
-    p.y -= this.y0;
-
-    var lon = Proj4js.common.adjust_lon( this.long0 + (p.x / this.a) / Math.cos(this.lat_ts) );
-
-    var lat = Math.asin( (p.y/this.a) * Math.cos(this.lat_ts) );
-
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }//ceaInv()
-};
-/* ======================================================================
-    projCode/eqc.js
-   ====================================================================== */
-
-/* similar to equi.js FIXME proj4 uses eqc */
-Proj4js.Proj.eqc = {
-  init : function() {
-
-      if(!this.x0) this.x0=0;
-      if(!this.y0) this.y0=0;
-      if(!this.lat0) this.lat0=0;
-      if(!this.long0) this.long0=0;
-      if(!this.lat_ts) this.lat_ts=0;
-      if (!this.title) this.title = "Equidistant Cylindrical (Plate Carre)";
-
-      this.rc= Math.cos(this.lat_ts);
-    },
-
-
-    // forward equations--mapping lat,long to x,y
-    // -----------------------------------------------------------------
-    forward : function(p) {
-
-      var lon= p.x;
-      var lat= p.y;
-
-      var dlon = Proj4js.common.adjust_lon(lon - this.long0);
-      var dlat = Proj4js.common.adjust_lat(lat - this.lat0 );
-      p.x= this.x0 + (this.a*dlon*this.rc);
-      p.y= this.y0 + (this.a*dlat        );
-      return p;
-    },
-
-  // inverse equations--mapping x,y to lat/long
-  // -----------------------------------------------------------------
-  inverse : function(p) {
-
-    var x= p.x;
-    var y= p.y;
-
-    p.x= Proj4js.common.adjust_lon(this.long0 + ((x - this.x0)/(this.a*this.rc)));
-    p.y= Proj4js.common.adjust_lat(this.lat0  + ((y - this.y0)/(this.a        )));
-    return p;
-  }
-
-};
-/* ======================================================================
-    projCode/cass.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            CASSINI
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Cassini projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-    Ported from PROJ.4.
-
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-*******************************************************************************/
-
-
-//Proj4js.defs["EPSG:28191"] = "+proj=cass +lat_0=31.73409694444445 +lon_0=35.21208055555556 +x_0=170251.555 +y_0=126867.909 +a=6378300.789 +b=6356566.435 +towgs84=-275.722,94.7824,340.894,-8.001,-4.42,-11.821,1 +units=m +no_defs";
-
-// Initialize the Cassini projection
-// -----------------------------------------------------------------
-
-Proj4js.Proj.cass = {
-  init : function() {
-    if (!this.sphere) {
-      this.en = this.pj_enfn(this.es)
-      this.m0 = this.pj_mlfn(this.lat0, Math.sin(this.lat0), Math.cos(this.lat0), this.en);
-    }
-  },
-
-  C1:	.16666666666666666666,
-  C2:	.00833333333333333333,
-  C3:	.04166666666666666666,
-  C4:	.33333333333333333333,
-  C5:	.06666666666666666666,
-
-
-/* Cassini forward equations--mapping lat,long to x,y
-  -----------------------------------------------------------------------*/
-  forward: function(p) {
-
-    /* Forward equations
-      -----------------*/
-    var x,y;
-    var lam=p.x;
-    var phi=p.y;
-    lam = Proj4js.common.adjust_lon(lam - this.long0);
-    
-    if (this.sphere) {
-      x = Math.asin(Math.cos(phi) * Math.sin(lam));
-      y = Math.atan2(Math.tan(phi) , Math.cos(lam)) - this.phi0;
-    } else {
-        //ellipsoid
-      this.n = Math.sin(phi);
-      this.c = Math.cos(phi);
-      y = this.pj_mlfn(phi, this.n, this.c, this.en);
-      this.n = 1./Math.sqrt(1. - this.es * this.n * this.n);
-      this.tn = Math.tan(phi); 
-      this.t = this.tn * this.tn;
-      this.a1 = lam * this.c;
-      this.c *= this.es * this.c / (1 - this.es);
-      this.a2 = this.a1 * this.a1;
-      x = this.n * this.a1 * (1. - this.a2 * this.t * (this.C1 - (8. - this.t + 8. * this.c) * this.a2 * this.C2));
-      y -= this.m0 - this.n * this.tn * this.a2 * (.5 + (5. - this.t + 6. * this.c) * this.a2 * this.C3);
-    }
-    
-    p.x = this.a*x + this.x0;
-    p.y = this.a*y + this.y0;
-    return p;
-  },//cassFwd()
-
-/* Inverse equations
-  -----------------*/
-  inverse: function(p) {
-    p.x -= this.x0;
-    p.y -= this.y0;
-    var x = p.x/this.a;
-    var y = p.y/this.a;
-    
-    if (this.sphere) {
-      this.dd = y + this.lat0;
-      phi = Math.asin(Math.sin(this.dd) * Math.cos(x));
-      lam = Math.atan2(Math.tan(x), Math.cos(this.dd));
-    } else {
-      /* ellipsoid */
-      ph1 = this.pj_inv_mlfn(this.m0 + y, this.es, this.en);
-      this.tn = Math.tan(ph1); 
-      this.t = this.tn * this.tn;
-      this.n = Math.sin(ph1);
-      this.r = 1. / (1. - this.es * this.n * this.n);
-      this.n = Math.sqrt(this.r);
-      this.r *= (1. - this.es) * this.n;
-      this.dd = x / this.n;
-      this.d2 = this.dd * this.dd;
-      phi = ph1 - (this.n * this.tn / this.r) * this.d2 * (.5 - (1. + 3. * this.t) * this.d2 * this.C3);
-      lam = this.dd * (1. + this.t * this.d2 * (-this.C4 + (1. + 3. * this.t) * this.d2 * this.C5)) / Math.cos(ph1);
-    }
-    p.x = Proj4js.common.adjust_lon(this.long0+lam);
-    p.y = phi;
-    return p;
-  },//lamazInv()
-
-
-  //code from the PROJ.4 pj_mlfn.c file;  this may be useful for other projections
-  pj_enfn: function(es) {
-    en = new Array();
-    en[0] = this.C00 - es * (this.C02 + es * (this.C04 + es * (this.C06 + es * this.C08)));
-    en[1] = es * (this.C22 - es * (this.C04 + es * (this.C06 + es * this.C08)));
-    var t = es * es;
-    en[2] = t * (this.C44 - es * (this.C46 + es * this.C48));
-    t *= es;
-    en[3] = t * (this.C66 - es * this.C68);
-    en[4] = t * es * this.C88;
-    return en;
-  },
-  
-  pj_mlfn: function(phi, sphi, cphi, en) {
-    cphi *= sphi;
-    sphi *= sphi;
-    return(en[0] * phi - cphi * (en[1] + sphi*(en[2]+ sphi*(en[3] + sphi*en[4]))));
-  },
-  
-  pj_inv_mlfn: function(arg, es, en) {
-    k = 1./(1.-es);
-    phi = arg;
-    for (i = Proj4js.common.MAX_ITER; i ; --i) { /* rarely goes over 2 iterations */
-      s = Math.sin(phi);
-      t = 1. - es * s * s;
-      //t = this.pj_mlfn(phi, s, Math.cos(phi), en) - arg;
-      //phi -= t * (t * Math.sqrt(t)) * k;
-      t = (this.pj_mlfn(phi, s, Math.cos(phi), en) - arg) * (t * Math.sqrt(t)) * k;
-      phi -= t;
-      if (Math.abs(t) < Proj4js.common.EPSLN)
-        return phi;
-    }
-    Proj4js.reportError("cass:pj_inv_mlfn: Convergence error");
-    return phi;
-  },
-
-/* meridinal distance for ellipsoid and inverse
-**	8th degree - accurate to < 1e-5 meters when used in conjuction
-**		with typical major axis values.
-**	Inverse determines phi to EPS (1e-11) radians, about 1e-6 seconds.
-*/
-  C00: 1.0,
-  C02: .25,
-  C04: .046875,
-  C06: .01953125,
-  C08: .01068115234375,
-  C22: .75,
-  C44: .46875,
-  C46: .01302083333333333333,
-  C48: .00712076822916666666,
-  C66: .36458333333333333333,
-  C68: .00569661458333333333,
-  C88: .3076171875
-
-}
-/* ======================================================================
-    projCode/gauss.js
-   ====================================================================== */
-
-
-Proj4js.Proj.gauss = {
-
-  init : function() {
-    sphi = Math.sin(this.lat0);
-    cphi = Math.cos(this.lat0);  
-    cphi *= cphi;
-    this.rc = Math.sqrt(1.0 - this.es) / (1.0 - this.es * sphi * sphi);
-    this.C = Math.sqrt(1.0 + this.es * cphi * cphi / (1.0 - this.es));
-    this.phic0 = Math.asin(sphi / this.C);
-    this.ratexp = 0.5 * this.C * this.e;
-    this.K = Math.tan(0.5 * this.phic0 + Proj4js.common.FORTPI) / (Math.pow(Math.tan(0.5*this.lat0 + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e*sphi, this.ratexp));
-  },
-
-  forward : function(p) {
-    var lon = p.x;
-    var lat = p.y;
-
-    p.y = 2.0 * Math.atan( this.K * Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI), this.C) * Proj4js.common.srat(this.e * Math.sin(lat), this.ratexp) ) - Proj4js.common.HALF_PI;
-    p.x = this.C * lon;
-    return p;
-  },
-
-  inverse : function(p) {
-    var DEL_TOL = 1e-14;
-    var lon = p.x / this.C;
-    var lat = p.y;
-    num = Math.pow(Math.tan(0.5 * lat + Proj4js.common.FORTPI)/this.K, 1./this.C);
-    for (var i = Proj4js.common.MAX_ITER; i>0; --i) {
-      lat = 2.0 * Math.atan(num * Proj4js.common.srat(this.e * Math.sin(p.y), -0.5 * this.e)) - Proj4js.common.HALF_PI;
-      if (Math.abs(lat - p.y) < DEL_TOL) break;
-      p.y = lat;
-    }	
-    /* convergence failed */
-    if (!i) {
-      Proj4js.reportError("gauss:inverse:convergence failed");
-      return null;
-    }
-    p.x = lon;
-    p.y = lat;
-    return p;
-  }
-};
-
-/* ======================================================================
-    projCode/omerc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                       OBLIQUE MERCATOR (HOTINE) 
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Oblique Mercator projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-T. Mittan		Mar, 1993
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-*******************************************************************************/
-
-Proj4js.Proj.omerc = {
-
-  /* Initialize the Oblique Mercator  projection
-    ------------------------------------------*/
-  init: function() {
-    if (!this.mode) this.mode=0;
-    if (!this.lon1)   {this.lon1=0;this.mode=1;}
-    if (!this.lon2)   this.lon2=0;
-    if (!this.lat2)    this.lat2=0;
-
-    /* Place parameters in static storage for common use
-      -------------------------------------------------*/
-    var temp = this.b/ this.a;
-    var es = 1.0 - Math.pow(temp,2);
-    var e = Math.sqrt(es);
-
-    this.sin_p20=Math.sin(this.lat0);
-    this.cos_p20=Math.cos(this.lat0);
-
-    this.con = 1.0 - this.es * this.sin_p20 * this.sin_p20;
-    this.com = Math.sqrt(1.0 - es);
-    this.bl = Math.sqrt(1.0 + this.es * Math.pow(this.cos_p20,4.0)/(1.0 - es));
-    this.al = this.a * this.bl * this.k0 * this.com / this.con;
-    if (Math.abs(this.lat0) < Proj4js.common.EPSLN) {
-       this.ts = 1.0;
-       this.d = 1.0;
-       this.el = 1.0;
-    } else {
-       this.ts = Proj4js.common.tsfnz(this.e,this.lat0,this.sin_p20);
-       this.con = Math.sqrt(this.con);
-       this.d = this.bl * this.com / (this.cos_p20 * this.con);
-       if ((this.d * this.d - 1.0) > 0.0) {
-          if (this.lat0 >= 0.0) {
-             this.f = this.d + Math.sqrt(this.d * this.d - 1.0);
-          } else {
-             this.f = this.d - Math.sqrt(this.d * this.d - 1.0);
-          }
-       } else {
-         this.f = this.d;
-       }
-       this.el = this.f * Math.pow(this.ts,this.bl);
-    }
-
-    //this.longc=52.60353916666667;
-
-    if (this.mode != 0) {
-       this.g = .5 * (this.f - 1.0/this.f);
-       this.gama = Proj4js.common.asinz(Math.sin(this.alpha) / this.d);
-       this.longc= this.longc - Proj4js.common.asinz(this.g * Math.tan(this.gama))/this.bl;
-
-       /* Report parameters common to format B
-       -------------------------------------*/
-       //genrpt(azimuth * R2D,"Azimuth of Central Line:    ");
-       //cenlon(lon_origin);
-      // cenlat(lat_origin);
-
-       this.con = Math.abs(this.lat0);
-       if ((this.con > Proj4js.common.EPSLN) && (Math.abs(this.con - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN)) {
-            this.singam=Math.sin(this.gama);
-            this.cosgam=Math.cos(this.gama);
-
-            this.sinaz=Math.sin(this.alpha);
-            this.cosaz=Math.cos(this.alpha);
-
-            if (this.lat0>= 0) {
-               this.u =  (this.al / this.bl) * Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
-            } else {
-               this.u =  -(this.al / this.bl) *Math.atan(Math.sqrt(this.d*this.d - 1.0)/this.cosaz);
-            }
-          } else {
-            Proj4js.reportError("omerc:Init:DataError");
-          }
-       } else {
-       this.sinphi =Math. sin(this.at1);
-       this.ts1 = Proj4js.common.tsfnz(this.e,this.lat1,this.sinphi);
-       this.sinphi = Math.sin(this.lat2);
-       this.ts2 = Proj4js.common.tsfnz(this.e,this.lat2,this.sinphi);
-       this.h = Math.pow(this.ts1,this.bl);
-       this.l = Math.pow(this.ts2,this.bl);
-       this.f = this.el/this.h;
-       this.g = .5 * (this.f - 1.0/this.f);
-       this.j = (this.el * this.el - this.l * this.h)/(this.el * this.el + this.l * this.h);
-       this.p = (this.l - this.h) / (this.l + this.h);
-       this.dlon = this.lon1 - this.lon2;
-       if (this.dlon < -Proj4js.common.PI) this.lon2 = this.lon2 - 2.0 * Proj4js.common.PI;
-       if (this.dlon > Proj4js.common.PI) this.lon2 = this.lon2 + 2.0 * Proj4js.common.PI;
-       this.dlon = this.lon1 - this.lon2;
-       this.longc = .5 * (this.lon1 + this.lon2) -Math.atan(this.j * Math.tan(.5 * this.bl * this.dlon)/this.p)/this.bl;
-       this.dlon  = Proj4js.common.adjust_lon(this.lon1 - this.longc);
-       this.gama = Math.atan(Math.sin(this.bl * this.dlon)/this.g);
-       this.alpha = Proj4js.common.asinz(this.d * Math.sin(this.gama));
-
-       /* Report parameters common to format A
-       -------------------------------------*/
-
-       if (Math.abs(this.lat1 - this.lat2) <= Proj4js.common.EPSLN) {
-          Proj4js.reportError("omercInitDataError");
-          //return(202);
-       } else {
-          this.con = Math.abs(this.lat1);
-       }
-       if ((this.con <= Proj4js.common.EPSLN) || (Math.abs(this.con - HALF_PI) <= Proj4js.common.EPSLN)) {
-           Proj4js.reportError("omercInitDataError");
-                //return(202);
-       } else {
-         if (Math.abs(Math.abs(this.lat0) - Proj4js.common.HALF_PI) <= Proj4js.common.EPSLN) {
-            Proj4js.reportError("omercInitDataError");
-            //return(202);
-         }
-       }
-
-       this.singam=Math.sin(this.gam);
-       this.cosgam=Math.cos(this.gam);
-
-       this.sinaz=Math.sin(this.alpha);
-       this.cosaz=Math.cos(this.alpha);  
-
-
-       if (this.lat0 >= 0) {
-          this.u =  (this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
-       } else {
-          this.u = -(this.al/this.bl) * Math.atan(Math.sqrt(this.d * this.d - 1.0)/this.cosaz);
-       }
-     }
-  },
-
-
-  /* Oblique Mercator forward equations--mapping lat,long to x,y
-    ----------------------------------------------------------*/
-  forward: function(p) {
-    var theta;		/* angle					*/
-    var sin_phi, cos_phi;/* sin and cos value				*/
-    var b;		/* temporary values				*/
-    var c, t, tq;	/* temporary values				*/
-    var con, n, ml;	/* cone constant, small m			*/
-    var q,us,vl;
-    var ul,vs;
-    var s;
-    var dlon;
-    var ts1;
-
-    var lon=p.x;
-    var lat=p.y;
-    /* Forward equations
-      -----------------*/
-    sin_phi = Math.sin(lat);
-    dlon = Proj4js.common.adjust_lon(lon - this.longc);
-    vl = Math.sin(this.bl * dlon);
-    if (Math.abs(Math.abs(lat) - Proj4js.common.HALF_PI) > Proj4js.common.EPSLN) {
-       ts1 = Proj4js.common.tsfnz(this.e,lat,sin_phi);
-       q = this.el / (Math.pow(ts1,this.bl));
-       s = .5 * (q - 1.0 / q);
-       t = .5 * (q + 1.0/ q);
-       ul = (s * this.singam - vl * this.cosgam) / t;
-       con = Math.cos(this.bl * dlon);
-       if (Math.abs(con) < .0000001) {
-          us = this.al * this.bl * dlon;
-       } else {
-          us = this.al * Math.atan((s * this.cosgam + vl * this.singam) / con)/this.bl;
-          if (con < 0) us = us + Proj4js.common.PI * this.al / this.bl;
-       }
-    } else {
-       if (lat >= 0) {
-          ul = this.singam;
-       } else {
-          ul = -this.singam;
-       }
-       us = this.al * lat / this.bl;
-    }
-    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN) {
-       //alert("Point projects into infinity","omer-for");
-       Proj4js.reportError("omercFwdInfinity");
-       //return(205);
-    }
-    vs = .5 * this.al * Math.log((1.0 - ul)/(1.0 + ul)) / this.bl;
-    us = us - this.u;
-    var x = this.x0 + vs * this.cosaz + us * this.sinaz;
-    var y = this.y0 + us * this.cosaz - vs * this.sinaz;
-
-    p.x=x;
-    p.y=y;
-    return p;
-  },
-
-  inverse: function(p) {
-    var delta_lon;	/* Delta longitude (Given longitude - center 	*/
-    var theta;		/* angle					*/
-    var delta_theta;	/* adjusted longitude				*/
-    var sin_phi, cos_phi;/* sin and cos value				*/
-    var b;		/* temporary values				*/
-    var c, t, tq;	/* temporary values				*/
-    var con, n, ml;	/* cone constant, small m			*/
-    var vs,us,q,s,ts1;
-    var vl,ul,bs;
-    var dlon;
-    var  flag;
-
-    /* Inverse equations
-      -----------------*/
-    p.x -= this.x0;
-    p.y -= this.y0;
-    flag = 0;
-    vs = p.x * this.cosaz - p.y * this.sinaz;
-    us = p.y * this.cosaz + p.x * this.sinaz;
-    us = us + this.u;
-    q = Math.exp(-this.bl * vs / this.al);
-    s = .5 * (q - 1.0/q);
-    t = .5 * (q + 1.0/q);
-    vl = Math.sin(this.bl * us / this.al);
-    ul = (vl * this.cosgam + s * this.singam)/t;
-    if (Math.abs(Math.abs(ul) - 1.0) <= Proj4js.common.EPSLN)
-       {
-       lon = this.longc;
-       if (ul >= 0.0) {
-          lat = Proj4js.common.HALF_PI;
-       } else {
-         lat = -Proj4js.common.HALF_PI;
-       }
-    } else {
-       con = 1.0 / this.bl;
-       ts1 =Math.pow((this.el / Math.sqrt((1.0 + ul) / (1.0 - ul))),con);
-       lat = Proj4js.common.phi2z(this.e,ts1);
-       //if (flag != 0)
-          //return(flag);
-       //~ con = Math.cos(this.bl * us /al);
-       theta = this.longc - Math.atan2((s * this.cosgam - vl * this.singam) , con)/this.bl;
-       lon = Proj4js.common.adjust_lon(theta);
-    }
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }
-};
-/* ======================================================================
-    projCode/lcc.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            LAMBERT CONFORMAL CONIC
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Lambert Conformal Conic projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-2.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-*******************************************************************************/
-
-
-//<2104> +proj=lcc +lat_1=10.16666666666667 +lat_0=10.16666666666667 +lon_0=-71.60561777777777 +k_0=1 +x0=-17044 +x0=-23139.97 +ellps=intl +units=m +no_defs  no_defs
-
-// Initialize the Lambert Conformal conic projection
-// -----------------------------------------------------------------
-
-//Proj4js.Proj.lcc = Class.create();
-Proj4js.Proj.lcc = {
-  init : function() {
-
-    // array of:  r_maj,r_min,lat1,lat2,c_lon,c_lat,false_east,false_north
-    //double c_lat;                   /* center latitude                      */
-    //double c_lon;                   /* center longitude                     */
-    //double lat1;                    /* first standard parallel              */
-    //double lat2;                    /* second standard parallel             */
-    //double r_maj;                   /* major axis                           */
-    //double r_min;                   /* minor axis                           */
-    //double false_east;              /* x offset in meters                   */
-    //double false_north;             /* y offset in meters                   */
-
-      if (!this.lat2){this.lat2=this.lat0;}//if lat2 is not defined
-      if (!this.k0) this.k0 = 1.0;
-
-    // Standard Parallels cannot be equal and on opposite sides of the equator
-      if (Math.abs(this.lat1+this.lat2) < Proj4js.common.EPSLN) {
-        Proj4js.reportError("lcc:init: Equal Latitudes");
-        return;
-      }
-
-      var temp = this.b / this.a;
-      this.e = Math.sqrt(1.0 - temp*temp);
-
-      var sin1 = Math.sin(this.lat1);
-      var cos1 = Math.cos(this.lat1);
-      var ms1 = Proj4js.common.msfnz(this.e, sin1, cos1);
-      var ts1 = Proj4js.common.tsfnz(this.e, this.lat1, sin1);
-
-      var sin2 = Math.sin(this.lat2);
-      var cos2 = Math.cos(this.lat2);
-      var ms2 = Proj4js.common.msfnz(this.e, sin2, cos2);
-      var ts2 = Proj4js.common.tsfnz(this.e, this.lat2, sin2);
-
-      var ts0 = Proj4js.common.tsfnz(this.e, this.lat0, Math.sin(this.lat0));
-
-      if (Math.abs(this.lat1 - this.lat2) > Proj4js.common.EPSLN) {
-        this.ns = Math.log(ms1/ms2)/Math.log(ts1/ts2);
-      } else {
-        this.ns = sin1;
-      }
-      this.f0 = ms1 / (this.ns * Math.pow(ts1, this.ns));
-      this.rh = this.a * this.f0 * Math.pow(ts0, this.ns);
-      if (!this.title) this.title = "Lambert Conformal Conic";
-    },
-
-
-    // Lambert Conformal conic forward equations--mapping lat,long to x,y
-    // -----------------------------------------------------------------
-    forward : function(p) {
-
-      var lon = p.x;
-      var lat = p.y;
-
-    // convert to radians
-      if ( lat <= 90.0 && lat >= -90.0 && lon <= 180.0 && lon >= -180.0) {
-        //lon = lon * Proj4js.common.D2R;
-        //lat = lat * Proj4js.common.D2R;
-      } else {
-        Proj4js.reportError("lcc:forward: llInputOutOfRange: "+ lon +" : " + lat);
-        return null;
-      }
-
-      var con  = Math.abs( Math.abs(lat) - Proj4js.common.HALF_PI);
-      var ts, rh1;
-      if (con > Proj4js.common.EPSLN) {
-        ts = Proj4js.common.tsfnz(this.e, lat, Math.sin(lat) );
-        rh1 = this.a * this.f0 * Math.pow(ts, this.ns);
-      } else {
-        con = lat * this.ns;
-        if (con <= 0) {
-          Proj4js.reportError("lcc:forward: No Projection");
-          return null;
-        }
-        rh1 = 0;
-      }
-      var theta = this.ns * Proj4js.common.adjust_lon(lon - this.long0);
-      p.x = this.k0 * (rh1 * Math.sin(theta)) + this.x0;
-      p.y = this.k0 * (this.rh - rh1 * Math.cos(theta)) + this.y0;
-
-      return p;
-    },
-
-  // Lambert Conformal Conic inverse equations--mapping x,y to lat/long
-  // -----------------------------------------------------------------
-  inverse : function(p) {
-
-    var rh1, con, ts;
-    var lat, lon;
-    var x = (p.x - this.x0)/this.k0;
-    var y = (this.rh - (p.y - this.y0)/this.k0);
-    if (this.ns > 0) {
-      rh1 = Math.sqrt (x * x + y * y);
-      con = 1.0;
-    } else {
-      rh1 = -Math.sqrt (x * x + y * y);
-      con = -1.0;
-    }
-    var theta = 0.0;
-    if (rh1 != 0) {
-      theta = Math.atan2((con * x),(con * y));
-    }
-    if ((rh1 != 0) || (this.ns > 0.0)) {
-      con = 1.0/this.ns;
-      ts = Math.pow((rh1/(this.a * this.f0)), con);
-      lat = Proj4js.common.phi2z(this.e, ts);
-      if (lat == -9999) return null;
-    } else {
-      lat = -Proj4js.common.HALF_PI;
-    }
-    lon = Proj4js.common.adjust_lon(theta/this.ns + this.long0);
-
-    p.x = lon;
-    p.y = lat;
-    return p;
-  }
-};
-
-
-
-
-/* ======================================================================
-    projCode/laea.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                  LAMBERT AZIMUTHAL EQUAL-AREA
- 
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the Lambert Azimuthal Equal-Area projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE            
-----------              ----           
-D. Steinwand, EROS      March, 1991   
-
-This function was adapted from the Lambert Azimuthal Equal Area projection
-code (FORTRAN) in the General Cartographic Transformation Package software
-which is available from the U.S. Geological Survey National Mapping Division.
- 
-ALGORITHM REFERENCES
-
-1.  "New Equal-Area Map Projections for Noncircular Regions", John P. Snyder,
-    The American Cartographer, Vol 15, No. 4, October 1988, pp. 341-355.
-
-2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-
-3.  "Software Documentation for GCTP General Cartographic Transformation
-    Package", U.S. Geological Survey National Mapping Division, May 1982.
-*******************************************************************************/
-
-Proj4js.Proj.laea = {
-  S_POLE: 1,
-  N_POLE: 2,
-  EQUIT: 3,
-  OBLIQ: 4,
-
-
-/* Initialize the Lambert Azimuthal Equal Area projection
-  ------------------------------------------------------*/
-  init: function() {
-    var t = Math.abs(this.lat0);
-    if (Math.abs(t - Proj4js.common.HALF_PI) < Proj4js.common.EPSLN) {
-      this.mode = this.lat0 < 0. ? this.S_POLE : this.N_POLE;
-    } else if (Math.abs(t) < Proj4js.common.EPSLN) {
-      this.mode = this.EQUIT;
-    } else {
-      this.mode = this.OBLIQ;
-    }
-    if (this.es > 0) {
-      var sinphi;
-  
-      this.qp = Proj4js.common.qsfnz(this.e, 1.0);
-      this.mmf = .5 / (1. - this.es);
-      this.apa = this.authset(this.es);
-      switch (this.mode) {
-        case this.N_POLE:
-        case this.S_POLE:
-          this.dd = 1.;
-          break;
-        case this.EQUIT:
-          this.rq = Math.sqrt(.5 * this.qp);
-          this.dd = 1. / this.rq;
-          this.xmf = 1.;
-          this.ymf = .5 * this.qp;
-          break;
-        case this.OBLIQ:
-          this.rq = Math.sqrt(.5 * this.qp);
-          sinphi = Math.sin(this.lat0);
-          this.sinb1 = Proj4js.common.qsfnz(this.e, sinphi) / this.qp;
-          this.cosb1 = Math.sqrt(1. - this.sinb1 * this.sinb1);
-          this.dd = Math.cos(this.lat0) / (Math.sqrt(1. - this.es * sinphi * sinphi) * this.rq * this.cosb1);
-          this.ymf = (this.xmf = this.rq) / this.dd;
-          this.xmf *= this.dd;
-          break;
-      }
-    } else {
-      if (this.mode == this.OBLIQ) {
-        this.sinph0 = Math.sin(this.lat0);
-        this.cosph0 = Math.cos(this.lat0);
-      }
-    }
-  },
-
-/* Lambert Azimuthal Equal Area forward equations--mapping lat,long to x,y
-  -----------------------------------------------------------------------*/
-  forward: function(p) {
-
-    /* Forward equations
-      -----------------*/
-    var x,y;
-    var lam=p.x;
-    var phi=p.y;
-    lam = Proj4js.common.adjust_lon(lam - this.long0);
-    
-    if (this.sphere) {
-        var coslam, cosphi, sinphi;
-      
-        sinphi = Math.sin(phi);
-        cosphi = Math.cos(phi);
-        coslam = Math.cos(lam);
-        switch (this.mode) {
-          case this.EQUIT:
-            y = (this.mode == this.EQUIT) ? 1. + cosphi * coslam : 1. + this.sinph0 * sinphi + this.cosph0 * cosphi * coslam;
-            if (y <= Proj4js.common.EPSLN) {
-              Proj4js.reportError("laea:fwd:y less than eps");
-              return null;
-            }
-            y = Math.sqrt(2. / y);
-            x = y * cosphi * Math.sin(lam);
-            y *= (this.mode == this.EQUIT) ? sinphi : this.cosph0 * sinphi - this.sinph0 * cosphi * coslam;
-            break;
-          case this.N_POLE:
-            coslam = -coslam;
-          case this.S_POLE:
-            if (Math.abs(phi + this.phi0) < Proj4js.common.EPSLN) {
-              Proj4js.reportError("laea:fwd:phi < eps");
-              return null;
-            }
-            y = Proj4js.common.FORTPI - phi * .5;
-            y = 2. * ((this.mode == this.S_POLE) ? Math.cos(y) : Math.sin(y));
-            x = y * Math.sin(lam);
-            y *= coslam;
-            break;
-        }
-    } else {
-        var coslam, sinlam, sinphi, q, sinb=0.0, cosb=0.0, b=0.0;
-      
-        coslam = Math.cos(lam);
-        sinlam = Math.sin(lam);
-        sinphi = Math.sin(phi);
-        q = Proj4js.common.qsfnz(this.e, sinphi);
-        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
-          sinb = q / this.qp;
-          cosb = Math.sqrt(1. - sinb * sinb);
-        }
-        switch (this.mode) {
-          case this.OBLIQ:
-            b = 1. + this.sinb1 * sinb + this.cosb1 * cosb * coslam;
-            break;
-          case this.EQUIT:
-            b = 1. + cosb * coslam;
-            break;
-          case this.N_POLE:
-            b = Proj4js.common.HALF_PI + phi;
-            q = this.qp - q;
-            break;
-          case this.S_POLE:
-            b = phi - Proj4js.common.HALF_PI;
-            q = this.qp + q;
-            break;
-        }
-        if (Math.abs(b) < Proj4js.common.EPSLN) {
-            Proj4js.reportError("laea:fwd:b < eps");
-            return null;
-        }
-        switch (this.mode) {
-          case this.OBLIQ:
-          case this.EQUIT:
-            b = Math.sqrt(2. / b);
-            if (this.mode == this.OBLIQ) {
-              y = this.ymf * b * (this.cosb1 * sinb - this.sinb1 * cosb * coslam);
-            } else {
-              y = (b = Math.sqrt(2. / (1. + cosb * coslam))) * sinb * this.ymf;
-            }
-            x = this.xmf * b * cosb * sinlam;
-            break;
-          case this.N_POLE:
-          case this.S_POLE:
-            if (q >= 0.) {
-              x = (b = Math.sqrt(q)) * sinlam;
-              y = coslam * ((this.mode == this.S_POLE) ? b : -b);
-            } else {
-              x = y = 0.;
-            }
-            break;
-        }
-    }
-
-    //v 1.0
-    /*
-    var sin_lat=Math.sin(lat);
-    var cos_lat=Math.cos(lat);
-
-    var sin_delta_lon=Math.sin(delta_lon);
-    var cos_delta_lon=Math.cos(delta_lon);
-
-    var g =this.sin_lat_o * sin_lat +this.cos_lat_o * cos_lat * cos_delta_lon;
-    if (g == -1.0) {
-      Proj4js.reportError("laea:fwd:Point projects to a circle of radius "+ 2.0 * R);
-      return null;
-    }
-    var ksp = this.a * Math.sqrt(2.0 / (1.0 + g));
-    var x = ksp * cos_lat * sin_delta_lon + this.x0;
-    var y = ksp * (this.cos_lat_o * sin_lat - this.sin_lat_o * cos_lat * cos_delta_lon) + this.y0;
-    */
-    p.x = this.a*x + this.x0;
-    p.y = this.a*y + this.y0;
-    return p;
-  },//lamazFwd()
-
-/* Inverse equations
-  -----------------*/
-  inverse: function(p) {
-    p.x -= this.x0;
-    p.y -= this.y0;
-    var x = p.x/this.a;
-    var y = p.y/this.a;
-    
-    if (this.sphere) {
-        var  cosz=0.0, rh, sinz=0.0;
-      
-        rh = Math.sqrt(x*x + y*y);
-        var phi = rh * .5;
-        if (phi > 1.) {
-          Proj4js.reportError("laea:Inv:DataError");
-          return null;
-        }
-        phi = 2. * Math.asin(phi);
-        if (this.mode == this.OBLIQ || this.mode == this.EQUIT) {
-          sinz = Math.sin(phi);
-          cosz = Math.cos(phi);
-        }
-        switch (this.mode) {
-        case this.EQUIT:
-          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? 0. : Math.asin(y * sinz / rh);
-          x *= sinz;
-          y = cosz * rh;
-          break;
-        case this.OBLIQ:
-          phi = (Math.abs(rh) <= Proj4js.common.EPSLN) ? this.phi0 : Math.asin(cosz * sinph0 + y * sinz * cosph0 / rh);
-          x *= sinz * cosph0;
-          y = (cosz - Math.sin(phi) * sinph0) * rh;
-          break;
-        case this.N_POLE:
-          y = -y;
-          phi = Proj4js.common.HALF_PI - phi;
-          break;
-        case this.S_POLE:
-          phi -= Proj4js.common.HALF_PI;
-          break;
-        }
-        lam = (y == 0. && (this.mode == this.EQUIT || this.mode == this.OBLIQ)) ? 0. : Math.atan2(x, y);
-    } else {
-        var cCe, sCe, q, rho, ab=0.0;
-      
-        switch (this.mode) {
-          case this.EQUIT:
-          case this.OBLIQ:
-            x /= this.dd;
-            y *=  this.dd;
-            rho = Math.sqrt(x*x + y*y);
-            if (rho < Proj4js.common.EPSLN) {
-              p.x = 0.;
-              p.y = this.phi0;
-              return p;
-            }
-            sCe = 2. * Math.asin(.5 * rho / this.rq);
-            cCe = Math.cos(sCe);
-            x *= (sCe = Math.sin(sCe));
-            if (this.mode == this.OBLIQ) {
-              ab = cCe * this.sinb1 + y * sCe * this.cosb1 / rho
-              q = this.qp * ab;
-              y = rho * this.cosb1 * cCe - y * this.sinb1 * sCe;
-            } else {
-              ab = y * sCe / rho;
-              q = this.qp * ab;
-              y = rho * cCe;
-            }
-            break;
-          case this.N_POLE:
-            y = -y;
-          case this.S_POLE:
-            q = (x * x + y * y);
-            if (!q ) {
-              p.x = 0.;
-              p.y = this.phi0;
-              return p;
-            }
-            /*
-            q = this.qp - q;
-            */
-            ab = 1. - q / this.qp;
-            if (this.mode == this.S_POLE) {
-              ab = - ab;
-            }
-            break;
-        }
-        lam = Math.atan2(x, y);
-        phi = this.authlat(Math.asin(ab), this.apa);
-    }
-
-    /*
-    var Rh = Math.Math.sqrt(p.x *p.x +p.y * p.y);
-    var temp = Rh / (2.0 * this.a);
-
-    if (temp > 1) {
-      Proj4js.reportError("laea:Inv:DataError");
-      return null;
-    }
-
-    var z = 2.0 * Proj4js.common.asinz(temp);
-    var sin_z=Math.sin(z);
-    var cos_z=Math.cos(z);
-
-    var lon =this.long0;
-    if (Math.abs(Rh) > Proj4js.common.EPSLN) {
-       var lat = Proj4js.common.asinz(this.sin_lat_o * cos_z +this. cos_lat_o * sin_z *p.y / Rh);
-       var temp =Math.abs(this.lat0) - Proj4js.common.HALF_PI;
-       if (Math.abs(temp) > Proj4js.common.EPSLN) {
-          temp = cos_z -this.sin_lat_o * Math.sin(lat);
-          if(temp!=0.0) lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x*sin_z*this.cos_lat_o,temp*Rh));
-       } else if (this.lat0 < 0.0) {
-          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x,p.y));
-       } else {
-          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x, -p.y));
-       }
-    } else {
-      lat = this.lat0;
-    }
-    */
-    //return(OK);
-    p.x = Proj4js.common.adjust_lon(this.long0+lam);
-    p.y = phi;
-    return p;
-  },//lamazInv()
-  
-/* determine latitude from authalic latitude */
-  P00: .33333333333333333333,
-  P01: .17222222222222222222,
-  P02: .10257936507936507936,
-  P10: .06388888888888888888,
-  P11: .06640211640211640211,
-  P20: .01641501294219154443,
-  
-  authset: function(es) {
-    var t;
-    var APA = new Array();
-    APA[0] = es * this.P00;
-    t = es * es;
-    APA[0] += t * this.P01;
-    APA[1] = t * this.P10;
-    t *= es;
-    APA[0] += t * this.P02;
-    APA[1] += t * this.P11;
-    APA[2] = t * this.P20;
-    return APA;
-  },
-  
-  authlat: function(beta, APA) {
-    var t = beta+beta;
-    return(beta + APA[0] * Math.sin(t) + APA[1] * Math.sin(t+t) + APA[2] * Math.sin(t+t+t));
-  }
-  
-};
-
-
-
-/* ======================================================================
-    projCode/aeqd.js
-   ====================================================================== */
-
-Proj4js.Proj.aeqd = {
-
-  init : function() {
-    this.sin_p12=Math.sin(this.lat0);
-    this.cos_p12=Math.cos(this.lat0);
-  },
-
-  forward: function(p) {
-    var lon=p.x;
-    var lat=p.y;
-    var ksp;
-
-    var sinphi=Math.sin(p.y);
-    var cosphi=Math.cos(p.y); 
-    var dlon = Proj4js.common.adjust_lon(lon - this.long0);
-    var coslon = Math.cos(dlon);
-    var g = this.sin_p12 * sinphi + this.cos_p12 * cosphi * coslon;
-    if (Math.abs(Math.abs(g) - 1.0) < Proj4js.common.EPSLN) {
-       ksp = 1.0;
-       if (g < 0.0) {
-         Proj4js.reportError("aeqd:Fwd:PointError");
-         return;
-       }
-    } else {
-       var z = Math.acos(g);
-       ksp = z/Math.sin(z);
-    }
-    p.x = this.x0 + this.a * ksp * cosphi * Math.sin(dlon);
-    p.y = this.y0 + this.a * ksp * (this.cos_p12 * sinphi - this.sin_p12 * cosphi * coslon);
-    return p;
-  },
-
-  inverse: function(p){
-    p.x -= this.x0;
-    p.y -= this.y0;
-
-    var rh = Math.sqrt(p.x * p.x + p.y *p.y);
-    if (rh > (2.0 * Proj4js.common.HALF_PI * this.a)) {
-       Proj4js.reportError("aeqdInvDataError");
-       return;
-    }
-    var z = rh / this.a;
-
-    var sinz=Math.sin(z);
-    var cosz=Math.cos(z);
-
-    var lon = this.long0;
-    var lat;
-    if (Math.abs(rh) <= Proj4js.common.EPSLN) {
-      lat = this.lat0;
-    } else {
-      lat = Proj4js.common.asinz(cosz * this.sin_p12 + (p.y * sinz * this.cos_p12) / rh);
-      var con = Math.abs(this.lat0) - Proj4js.common.HALF_PI;
-      if (Math.abs(con) <= Proj4js.common.EPSLN) {
-        if (lat0 >= 0.0) {
-          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2(p.x , -p.y));
-        } else {
-          lon = Proj4js.common.adjust_lon(this.long0 - Math.atan2(-p.x , p.y));
-        }
-      } else {
-        con = cosz - this.sin_p12 * Math.sin(lat);
-        if ((Math.abs(con) < Proj4js.common.EPSLN) && (Math.abs(p.x) < Proj4js.common.EPSLN)) {
-           //no-op, just keep the lon value as is
-        } else {
-          var temp = Math.atan2((p.x * sinz * this.cos_p12), (con * rh));
-          lon = Proj4js.common.adjust_lon(this.long0 + Math.atan2((p.x * sinz * this.cos_p12), (con * rh)));
-        }
-      }
-    }
-
-    p.x = lon;
-    p.y = lat;
-    return p;
-  } 
-};
-/* ======================================================================
-    projCode/moll.js
-   ====================================================================== */
-
-/*******************************************************************************
-NAME                            MOLLWEIDE
-
-PURPOSE:	Transforms input longitude and latitude to Easting and
-		Northing for the MOllweide projection.  The
-		longitude and latitude must be in radians.  The Easting
-		and Northing values will be returned in meters.
-
-PROGRAMMER              DATE
-----------              ----
-D. Steinwand, EROS      May, 1991;  Updated Sept, 1992; Updated Feb, 1993
-S. Nelson, EDC		Jun, 2993;	Made corrections in precision and
-					number of iterations.
-
-ALGORITHM REFERENCES
-
-1.  Snyder, John P. and Voxland, Philip M., "An Album of Map Projections",
-    U.S. Geological Survey Professional Paper 1453 , United State Government
-    Printing Office, Washington D.C., 1989.
-
-2.  Snyder, John P., "Map Projections--A Working Manual", U.S. Geological
-    Survey Professional Paper 1395 (Supersedes USGS Bulletin 1532), United
-    State Government Printing Office, Washington D.C., 1987.
-*******************************************************************************/
-
-Proj4js.Proj.moll = {
-
-  /* Initialize the Mollweide projection
-    ------------------------------------*/
-  init: function(){
-    //no-op
-  },
-
-  /* Mollweide forward equations--mapping lat,long to x,y
-    ----------------------------------------------------*/
-  forward: function(p) {
-
-    /* Forward equations
-      -----------------*/
-    var lon=p.x;
-    var lat=p.y;
-
-    var delta_lon = Proj4js.common.adjust_lon(lon - this.long0);
-    var theta = lat;
-    var con = Proj4js.common.PI * Math.sin(lat);
-
-    /* Iterate using the Newton-Raphson method to find theta
-      -----------------------------------------------------*/
-    for (var i=0;true;i++) {
-       var delta_theta = -(theta + Math.sin(theta) - con)/ (1.0 + Math.cos(theta));
-       theta += delta_theta;
-       if (Math.abs(delta_theta) < Proj4js.common.EPSLN) break;
-       if (i >= 50) {
-          Proj4js.reportError("moll:Fwd:IterationError");
-         //return(241);
-       }
-    }
-    theta /= 2.0;
-
-    /* If the latitude is 90 deg, force the x coordinate to be "0 + false easting"
-       this is done here because of precision problems with "cos(theta)"
-       --------------------------------------------------------------------------*/
-    if (Proj4js.common.PI/2 - Math.abs(lat) < Proj4js.common.EPSLN) delta_lon =0;
-    var x = 0.900316316158 * this.a * delta_lon * Math.cos(theta) + this.x0;
-    var y = 1.4142135623731 * this.a * Math.sin(theta) + this.y0;
-
-    p.x=x;
-    p.y=y;
-    return p;
-  },
-
-  inverse: function(p){
-    var theta;
-    var arg;
-
-    /* Inverse equations
-      -----------------*/
-    p.x-= this.x0;
-    //~ p.y -= this.y0;
-    var arg = p.y /  (1.4142135623731 * this.a);
-
-    /* Because of division by zero problems, 'arg' can not be 1.0.  Therefore
-       a number very close to one is used instead.
-       -------------------------------------------------------------------*/
-    if(Math.abs(arg) > 0.999999999999) arg=0.999999999999;
-    var theta =Math.asin(arg);
-    var lon = Proj4js.common.adjust_lon(this.long0 + (p.x / (0.900316316158 * this.a * Math.cos(theta))));
-    if(lon < (-Proj4js.common.PI)) lon= -Proj4js.common.PI;
-    if(lon > Proj4js.common.PI) lon= Proj4js.common.PI;
-    arg = (2.0 * theta + Math.sin(2.0 * theta)) / Proj4js.common.PI;
-    if(Math.abs(arg) > 1.0)arg=1.0;
-    var lat = Math.asin(arg);
-    //return(OK);
-
-    p.x=lon;
-    p.y=lat;
-    return p;
-  }
-};
-

Deleted: trunk/lib/proj4js-compressed.js
===================================================================
--- trunk/lib/proj4js-compressed.js	2011-04-14 19:43:29 UTC (rev 2370)
+++ trunk/lib/proj4js-compressed.js	2011-04-15 18:46:31 UTC (rev 2371)
@@ -1,224 +0,0 @@
-/*
-  proj4js.js -- Javascript reprojection library. 
-  
-  Authors:      Mike Adair madairATdmsolutions.ca
-                Richard Greenwood richATgreenwoodmap.com
-                Didier Richard didier.richardATign.fr
-                Stephen Irons
-  License:      LGPL as per: http://www.gnu.org/copyleft/lesser.html 
-                Note: This program is an almost direct port of the C library
-                Proj4.
-*/
-Proj4js={defaultDatum:'WGS84',transform:function(source,dest,point){if(!source.readyToUse){this.reportError("Proj4js initialization for:"+source.srsCode+" not yet complete");return point;}
-if(!dest.readyToUse){this.reportError("Proj4js initialization for:"+dest.srsCode+" not yet complete");return point;}
-if((source.srsProjNumber=="900913"&&dest.datumCode!="WGS84")||(dest.srsProjNumber=="900913"&&source.datumCode!="WGS84")){var wgs84=Proj4js.WGS84;this.transform(source,wgs84,point);source=wgs84;}
-if(source.projName=="longlat"){point.x*=Proj4js.common.D2R;point.y*=Proj4js.common.D2R;}else{if(source.to_meter){point.x*=source.to_meter;point.y*=source.to_meter;}
-source.inverse(point);}
-if(source.from_greenwich){point.x+=source.from_greenwich;}
-point=this.datum_transform(source.datum,dest.datum,point);if(dest.from_greenwich){point.x-=dest.from_greenwich;}
-if(dest.projName=="longlat"){point.x*=Proj4js.common.R2D;point.y*=Proj4js.common.R2D;}else{dest.forward(point);if(dest.to_meter){point.x/=dest.to_meter;point.y/=dest.to_meter;}}
-return point;},datum_transform:function(source,dest,point){if(source.compare_datums(dest)){return point;}
-if(source.datum_type==Proj4js.common.PJD_NODATUM||dest.datum_type==Proj4js.common.PJD_NODATUM){return point;}
-if(source.datum_type==Proj4js.common.PJD_GRIDSHIFT)
-{alert("ERROR: Grid shift transformations are not implemented yet.");}
-if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
-{alert("ERROR: Grid shift transformations are not implemented yet.");}
-if(source.es!=dest.es||source.a!=dest.a||source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM||dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM)
-{source.geodetic_to_geocentric(point);if(source.datum_type==Proj4js.common.PJD_3PARAM||source.datum_type==Proj4js.common.PJD_7PARAM){source.geocentric_to_wgs84(point);}
-if(dest.datum_type==Proj4js.common.PJD_3PARAM||dest.datum_type==Proj4js.common.PJD_7PARAM){dest.geocentric_from_wgs84(point);}
-dest.geocentric_to_geodetic(point);}
-if(dest.datum_type==Proj4js.common.PJD_GRIDSHIFT)
-{alert("ERROR: Grid shift transformations are not implemented yet.");}
-return point;},reportError:function(msg){},extend:function(destination,source){destination=destination||{};if(source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}}
-return destination;},Class:function(){var Class=function(){this.initialize.apply(this,arguments);};var extended={};var parent;for(var i=0;i<arguments.length;++i){if(typeof arguments[i]=="function"){parent=arguments[i].prototype;}else{parent=arguments[i];}
-Proj4js.extend(extended,parent);}
-Class.prototype=extended;return Class;},bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},scriptName:"proj4js-compressed.js",defsLookupService:'http://spatialreference.org/ref',libPath:null,getScriptLocation:function(){if(this.libPath)return this.libPath;var scriptName=this.scriptName;var scriptNameLen=scriptName.length;var scripts=document.getElementsByTagName('script');for(var i=0;i<scripts.length;i++){var src=scripts[i].getAttribute('src');if(src){var index=src.lastIndexOf(scriptName);if((index>-1)&&(index+scriptNameLen==src.length)){this.libPath=src.slice(0,-scriptNameLen);break;}}}
-return this.libPath||"";},loadScript:function(url,onload,onfail,loadCheck){var script=document.createElement('script');script.defer=false;script.type="text/javascript";script.id=url;script.src=url;script.onload=onload;script.onerror=onfail;script.loadCheck=loadCheck;if(/MSIE/.test(navigator.userAgent)){script.onreadystatechange=this.checkReadyState;}
-document.getElementsByTagName('head')[0].appendChild(script);},checkReadyState:function(){if(this.readyState=='loaded'){if(!this.loadCheck()){this.onerror();}else{this.onload();}}}};Proj4js.Proj=Proj4js.Class({readyToUse:false,title:null,projName:null,units:null,datum:null,x0:0,y0:0,localCS:false,initialize:function(srsCode){this.srsCodeInput=srsCode;if((srsCode.indexOf('GEOGCS')>=0)||(srsCode.indexOf('GEOCCS')>=0)||(srsCode.indexOf('PROJCS')>=0)||(srsCode.indexOf('LOCAL_CS')>=0)){this.parseWKT(srsCode);this.datum=new Proj4js.datum(this);this.loadProjCode(this.projName);return;}
-if(srsCode.indexOf('urn:')==0){var urn=srsCode.split(':');if((urn[1]=='ogc'||urn[1]=='x-ogc')&&(urn[2]=='def')&&(urn[3]=='crs')){srsCode=urn[4]+':'+urn[urn.length-1];}}else if(srsCode.indexOf('http://')==0){var url=srsCode.split('#');if(url[0].match(/epsg.org/)){srsCode='EPSG:'+url[1];}else if(url[0].match(/RIG.xml/)){srsCode='IGNF:'+url[1];}}
-this.srsCode=srsCode.toUpperCase();if(this.srsCode.indexOf("EPSG")==0){this.srsCode=this.srsCode;this.srsAuth='epsg';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("IGNF")==0){this.srsCode=this.srsCode;this.srsAuth='IGNF';this.srsProjNumber=this.srsCode.substring(5);}else if(this.srsCode.indexOf("CRS")==0){this.srsCode=this.srsCode;this.srsAuth='CRS';this.srsProjNumber=this.srsCode.substring(4);}else{this.srsAuth='';this.srsProjNumber=this.srsCode;}
-this.loadProjDefinition();},loadProjDefinition:function(){if(Proj4js.defs[this.srsCode]){this.defsLoaded();return;}
-var url=Proj4js.getScriptLocation()+'defs/'+this.srsAuth.toUpperCase()+this.srsProjNumber+'.js';Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.loadFromService,this),Proj4js.bind(this.checkDefsLoaded,this));},loadFromService:function(){var url=Proj4js.defsLookupService+'/'+this.srsAuth+'/'+this.srsProjNumber+'/proj4js/';Proj4js.loadScript(url,Proj4js.bind(this.defsLoaded,this),Proj4js.bind(this.defsFailed,this),Proj4js.bind(this.checkDefsLoaded,this));},defsLoaded:function(){this.parseDefs();this.loadProjCode(this.projName);},checkDefsLoaded:function(){if(Proj4js.defs[this.srsCode]){return true;}else{return false;}},defsFailed:function(){Proj4js.reportError('failed to load projection definition for: '+this.srsCode);Proj4js.defs[this.srsCode]=Proj4js.defs['WGS84'];this.defsLoaded();},loadProjCode:function(projName){if(Proj4js.Proj[projName]){this.initTransforms();return;}
-var url=Proj4js.getScriptLocation()+'projCode/'+projName+'.js';Proj4js.loadScript(url,Proj4js.bind(this.loadProjCodeSuccess,this,projName),Proj4js.bind(this.loadProjCodeFailure,this,projName),Proj4js.bind(this.checkCodeLoaded,this,projName));},loadProjCodeSuccess:function(projName){if(Proj4js.Proj[projName].dependsOn){this.loadProjCode(Proj4js.Proj[projName].dependsOn);}else{this.initTransforms();}},loadProjCodeFailure:function(projName){Proj4js.reportError("failed to find projection file for: "+projName);},checkCodeLoaded:function(projName){if(Proj4js.Proj[projName]){return true;}else{return false;}},initTransforms:function(){Proj4js.extend(this,Proj4js.Proj[this.projName]);this.init();this.readyToUse=true;},wktRE:/^(\w+)\[(.*)\]$/,parseWKT:function(wkt){var wktMatch=wkt.match(this.wktRE);if(!wktMatch)return;var wktObject=wktMatch[1];var wktContent=wktMatch[2];var wktTemp=wktContent.split(",");var wktName=wktTemp.shift();wktName=wktName.replace(/^\"/,"");wktName=wktName.rep
 lace(/\"$/,"");var wktArray=new Array();var bkCount=0;var obj="";for(var i=0;i<wktTemp.length;++i){var token=wktTemp[i];for(var j=0;j<token.length;++j){if(token.charAt(j)=="[")++bkCount;if(token.charAt(j)=="]")--bkCount;}
-obj+=token;if(bkCount===0){wktArray.push(obj);obj="";}else{obj+=",";}}
-switch(wktObject){case'LOCAL_CS':this.projName='identity'
-this.localCS=true;this.srsCode=wktName;break;case'GEOGCS':this.projName='longlat'
-this.geocsCode=wktName;if(!this.srsCode)this.srsCode=wktName;break;case'PROJCS':this.srsCode=wktName;break;case'GEOCCS':break;case'PROJECTION':this.projName=Proj4js.wktProjections[wktName]
-break;case'DATUM':this.datumName=wktName;break;case'LOCAL_DATUM':this.datumCode='none';break;case'SPHEROID':this.ellps=wktName;this.a=parseFloat(wktArray.shift());this.rf=parseFloat(wktArray.shift());break;case'PRIMEM':this.from_greenwich=parseFloat(wktArray.shift());break;case'UNIT':this.units=wktName;this.unitsPerMeter=parseFloat(wktArray.shift());break;case'PARAMETER':var name=wktName;var value=parseFloat(parseFloat(wktArray.shift()));switch(name){case'false_easting':this.x0=value;break;case'false_northing':this.y0=value;break;case'scale_factor':this.k0=value;break;case'central_meridian':this.long0=value;break;case'latitude_of_origin':this.lat0=value;break;case'more_here':break;default:break;}
-break;case'TOWGS84':this.datum_params=wktArray;break;case'MORE_HERE':break;default:break;}
-for(var i=0;i<wktArray.length;++i){this.parseWKT(wktArray[i]);}},parseDefs:function(){this.defData=Proj4js.defs[this.srsCode];var paramName,paramVal;if(!this.defData){return;}
-var paramArray=this.defData.split("+");for(var prop=0;prop<paramArray.length;prop++){var property=paramArray[prop].split("=");paramName=property[0].toLowerCase();paramVal=property[1];switch(paramName.replace(/\s/gi,"")){case"":break;case"title":this.title=paramVal;break;case"proj":this.projName=paramVal.replace(/\s/gi,"");break;case"units":this.units=paramVal.replace(/\s/gi,"");break;case"datum":this.datumCode=paramVal.replace(/\s/gi,"");break;case"nadgrids":this.nagrids=paramVal.replace(/\s/gi,"");break;case"ellps":this.ellps=paramVal.replace(/\s/gi,"");break;case"a":this.a=parseFloat(paramVal);break;case"b":this.b=parseFloat(paramVal);break;case"rf":this.rf=parseFloat(paramVal);break;case"lat_0":this.lat0=paramVal*Proj4js.common.D2R;break;case"lat_1":this.lat1=paramVal*Proj4js.common.D2R;break;case"lat_2":this.lat2=paramVal*Proj4js.common.D2R;break;case"lat_ts":this.lat_ts=paramVal*Proj4js.common.D2R;break;case"lon_0":this.long0=paramVal*Proj4js.common.D2R;break;case"alpha
 ":this.alpha=parseFloat(paramVal)*Proj4js.common.D2R;break;case"lonc":this.longc=paramVal*Proj4js.common.D2R;break;case"x_0":this.x0=parseFloat(paramVal);break;case"y_0":this.y0=parseFloat(paramVal);break;case"k_0":this.k0=parseFloat(paramVal);break;case"k":this.k0=parseFloat(paramVal);break;case"r_a":this.R_A=true;break;case"zone":this.zone=parseInt(paramVal);break;case"south":this.utmSouth=true;break;case"towgs84":this.datum_params=paramVal.split(",");break;case"to_meter":this.to_meter=parseFloat(paramVal);break;case"from_greenwich":this.from_greenwich=paramVal*Proj4js.common.D2R;break;case"pm":paramVal=paramVal.replace(/\s/gi,"");this.from_greenwich=Proj4js.PrimeMeridian[paramVal]?Proj4js.PrimeMeridian[paramVal]:parseFloat(paramVal);this.from_greenwich*=Proj4js.common.D2R;break;case"no_defs":break;default:}}
-this.deriveConstants();},deriveConstants:function(){if(this.nagrids=='@null')this.datumCode='none';if(this.datumCode&&this.datumCode!='none'){var datumDef=Proj4js.Datum[this.datumCode];if(datumDef){this.datum_params=datumDef.towgs84?datumDef.towgs84.split(','):null;this.ellps=datumDef.ellipse;this.datumName=datumDef.datumName?datumDef.datumName:this.datumCode;}}
-if(!this.a){var ellipse=Proj4js.Ellipsoid[this.ellps]?Proj4js.Ellipsoid[this.ellps]:Proj4js.Ellipsoid['WGS84'];Proj4js.extend(this,ellipse);}
-if(this.rf&&!this.b)this.b=(1.0-1.0/this.rf)*this.a;if(Math.abs(this.a-this.b)<Proj4js.common.EPSLN){this.sphere=true;this.b=this.a;}
-this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=(this.a2-this.b2)/this.a2;this.e=Math.sqrt(this.es);if(this.R_A){this.a*=1.-this.es*(Proj4js.common.SIXTH+this.es*(Proj4js.common.RA4+this.es*Proj4js.common.RA6));this.a2=this.a*this.a;this.b2=this.b*this.b;this.es=0.;}
-this.ep2=(this.a2-this.b2)/this.b2;if(!this.k0)this.k0=1.0;this.datum=new Proj4js.datum(this);}});Proj4js.Proj.longlat={init:function(){},forward:function(pt){return pt;},inverse:function(pt){return pt;}};Proj4js.Proj.identity=Proj4js.Proj.longlat;Proj4js.defs={'WGS84':"+title=long/lat:WGS84 +proj=longlat +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4326':"+title=long/lat:WGS84 +proj=longlat +a=6378137.0 +b=6356752.31424518 +ellps=WGS84 +datum=WGS84 +units=degrees",'EPSG:4269':"+title=long/lat:NAD83 +proj=longlat +a=6378137.0 +b=6356752.31414036 +ellps=GRS80 +datum=NAD83 +units=degrees",'EPSG:3785':"+title= Google Mercator +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs"};Proj4js.defs['GOOGLE']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:900913']=Proj4js.defs['EPSG:3785'];Proj4js.defs['EPSG:102113']=Proj4js.defs['EPSG:3785'];Proj4js.common={PI:3.141592653589793238,HALF_PI:1.570796326794896619,TWO_PI:6.28318
 5307179586477,FORTPI:0.78539816339744833,R2D:57.29577951308232088,D2R:0.01745329251994329577,SEC_TO_RAD:4.84813681109535993589914102357e-6,EPSLN:1.0e-10,MAX_ITER:20,COS_67P5:0.38268343236508977,AD_C:1.0026000,PJD_UNKNOWN:0,PJD_3PARAM:1,PJD_7PARAM:2,PJD_GRIDSHIFT:3,PJD_WGS84:4,PJD_NODATUM:5,SRS_WGS84_SEMIMAJOR:6378137.0,SIXTH:.1666666666666666667,RA4:.04722222222222222222,RA6:.02215608465608465608,RV4:.06944444444444444444,RV6:.04243827160493827160,msfnz:function(eccent,sinphi,cosphi){var con=eccent*sinphi;return cosphi/(Math.sqrt(1.0-con*con));},tsfnz:function(eccent,phi,sinphi){var con=eccent*sinphi;var com=.5*eccent;con=Math.pow(((1.0-con)/(1.0+con)),com);return(Math.tan(.5*(this.HALF_PI-phi))/con);},phi2z:function(eccent,ts){var eccnth=.5*eccent;var con,dphi;var phi=this.HALF_PI-2*Math.atan(ts);for(var i=0;i<=15;i++){con=eccent*Math.sin(phi);dphi=this.HALF_PI-2*Math.atan(ts*(Math.pow(((1.0-con)/(1.0+con)),eccnth)))-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001)return phi;}
-alert("phi2z has NoConvergence");return(-9999);},qsfnz:function(eccent,sinphi){var con;if(eccent>1.0e-7){con=eccent*sinphi;return((1.0-eccent*eccent)*(sinphi/(1.0-con*con)-(.5/eccent)*Math.log((1.0-con)/(1.0+con))));}else{return(2.0*sinphi);}},asinz:function(x){if(Math.abs(x)>1.0){x=(x>1.0)?1.0:-1.0;}
-return Math.asin(x);},e0fn:function(x){return(1.0-0.25*x*(1.0+x/16.0*(3.0+1.25*x)));},e1fn:function(x){return(0.375*x*(1.0+0.25*x*(1.0+0.46875*x)));},e2fn:function(x){return(0.05859375*x*x*(1.0+0.75*x));},e3fn:function(x){return(x*x*x*(35.0/3072.0));},mlfn:function(e0,e1,e2,e3,phi){return(e0*phi-e1*Math.sin(2.0*phi)+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi));},srat:function(esinp,exp){return(Math.pow((1.0-esinp)/(1.0+esinp),exp));},sign:function(x){if(x<0.0)return(-1);else return(1);},adjust_lon:function(x){x=(Math.abs(x)<this.PI)?x:(x-(this.sign(x)*this.TWO_PI));return x;},adjust_lat:function(x){x=(Math.abs(x)<this.HALF_PI)?x:(x-(this.sign(x)*this.PI));return x;},latiso:function(eccent,phi,sinphi){if(Math.abs(phi)>this.HALF_PI)return+Number.NaN;if(phi==this.HALF_PI)return Number.POSITIVE_INFINITY;if(phi==-1.0*this.HALF_PI)return-1.0*Number.POSITIVE_INFINITY;var con=eccent*sinphi;return Math.log(Math.tan((this.HALF_PI+phi)/2.0))+eccent*Math.log((1.0-con)/(1.0+con))/2.0;},fL:
 function(x,L){return 2.0*Math.atan(x*Math.exp(L))-this.HALF_PI;},invlatiso:function(eccent,ts){var phi=this.fL(1.0,ts);var Iphi=0.0;var con=0.0;do{Iphi=phi;con=eccent*Math.sin(Iphi);phi=this.fL(Math.exp(eccent*Math.log((1.0+con)/(1.0-con))/2.0),ts)}while(Math.abs(phi-Iphi)>1.0e-12);return phi;},sinh:function(x)
-{var r=Math.exp(x);r=(r-1.0/r)/2.0;return r;},cosh:function(x)
-{var r=Math.exp(x);r=(r+1.0/r)/2.0;return r;},tanh:function(x)
-{var r=Math.exp(x);r=(r-1.0/r)/(r+1.0/r);return r;},asinh:function(x)
-{var s=(x>=0?1.0:-1.0);return s*(Math.log(Math.abs(x)+Math.sqrt(x*x+1.0)));},acosh:function(x)
-{return 2.0*Math.log(Math.sqrt((x+1.0)/2.0)+Math.sqrt((x-1.0)/2.0));},atanh:function(x)
-{return Math.log((x-1.0)/(x+1.0))/2.0;},gN:function(a,e,sinphi)
-{var temp=e*sinphi;return a/Math.sqrt(1.0-temp*temp);}};Proj4js.datum=Proj4js.Class({initialize:function(proj){this.datum_type=Proj4js.common.PJD_WGS84;if(proj.datumCode&&proj.datumCode=='none'){this.datum_type=Proj4js.common.PJD_NODATUM;}
-if(proj&&proj.datum_params){for(var i=0;i<proj.datum_params.length;i++){proj.datum_params[i]=parseFloat(proj.datum_params[i]);}
-if(proj.datum_params[0]!=0||proj.datum_params[1]!=0||proj.datum_params[2]!=0){this.datum_type=Proj4js.common.PJD_3PARAM;}
-if(proj.datum_params.length>3){if(proj.datum_params[3]!=0||proj.datum_params[4]!=0||proj.datum_params[5]!=0||proj.datum_params[6]!=0){this.datum_type=Proj4js.common.PJD_7PARAM;proj.datum_params[3]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[4]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[5]*=Proj4js.common.SEC_TO_RAD;proj.datum_params[6]=(proj.datum_params[6]/1000000.0)+1.0;}}}
-if(proj){this.a=proj.a;this.b=proj.b;this.es=proj.es;this.ep2=proj.ep2;this.datum_params=proj.datum_params;}},compare_datums:function(dest){if(this.datum_type!=dest.datum_type){return false;}else if(this.a!=dest.a||Math.abs(this.es-dest.es)>0.000000000050){return false;}else if(this.datum_type==Proj4js.common.PJD_3PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]);}else if(this.datum_type==Proj4js.common.PJD_7PARAM){return(this.datum_params[0]==dest.datum_params[0]&&this.datum_params[1]==dest.datum_params[1]&&this.datum_params[2]==dest.datum_params[2]&&this.datum_params[3]==dest.datum_params[3]&&this.datum_params[4]==dest.datum_params[4]&&this.datum_params[5]==dest.datum_params[5]&&this.datum_params[6]==dest.datum_params[6]);}else if(this.datum_type==Proj4js.common.PJD_GRIDSHIFT){return strcmp(pj_param(this.params,"snadgrids").s,pj_param(dest.params,"snadgrids").s)==0;}else{return 
 true;}},geodetic_to_geocentric:function(p){var Longitude=p.x;var Latitude=p.y;var Height=p.z?p.z:0;var X;var Y;var Z;var Error_Code=0;var Rn;var Sin_Lat;var Sin2_Lat;var Cos_Lat;if(Latitude<-Proj4js.common.HALF_PI&&Latitude>-1.001*Proj4js.common.HALF_PI){Latitude=-Proj4js.common.HALF_PI;}else if(Latitude>Proj4js.common.HALF_PI&&Latitude<1.001*Proj4js.common.HALF_PI){Latitude=Proj4js.common.HALF_PI;}else if((Latitude<-Proj4js.common.HALF_PI)||(Latitude>Proj4js.common.HALF_PI)){Proj4js.reportError('geocent:lat out of range:'+Latitude);return null;}
-if(Longitude>Proj4js.common.PI)Longitude-=(2*Proj4js.common.PI);Sin_Lat=Math.sin(Latitude);Cos_Lat=Math.cos(Latitude);Sin2_Lat=Sin_Lat*Sin_Lat;Rn=this.a/(Math.sqrt(1.0e0-this.es*Sin2_Lat));X=(Rn+Height)*Cos_Lat*Math.cos(Longitude);Y=(Rn+Height)*Cos_Lat*Math.sin(Longitude);Z=((Rn*(1-this.es))+Height)*Sin_Lat;p.x=X;p.y=Y;p.z=Z;return Error_Code;},geocentric_to_geodetic:function(p){var genau=1.E-12;var genau2=(genau*genau);var maxiter=30;var P;var RR;var CT;var ST;var RX;var RK;var RN;var CPHI0;var SPHI0;var CPHI;var SPHI;var SDPHI;var At_Pole;var iter;var X=p.x;var Y=p.y;var Z=p.z?p.z:0.0;var Longitude;var Latitude;var Height;At_Pole=false;P=Math.sqrt(X*X+Y*Y);RR=Math.sqrt(X*X+Y*Y+Z*Z);if(P/this.a<genau){At_Pole=true;Longitude=0.0;if(RR/this.a<genau){Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}else{Longitude=Math.atan2(Y,X);}
-CT=Z/RR;ST=P/RR;RX=1.0/Math.sqrt(1.0-this.es*(2.0-this.es)*ST*ST);CPHI0=ST*(1.0-this.es)*RX;SPHI0=CT*RX;iter=0;do
-{iter++;RN=this.a/Math.sqrt(1.0-this.es*SPHI0*SPHI0);Height=P*CPHI0+Z*SPHI0-RN*(1.0-this.es*SPHI0*SPHI0);RK=this.es*RN/(RN+Height);RX=1.0/Math.sqrt(1.0-RK*(2.0-RK)*ST*ST);CPHI=ST*(1.0-RK)*RX;SPHI=CT*RX;SDPHI=SPHI*CPHI0-CPHI*SPHI0;CPHI0=CPHI;SPHI0=SPHI;}
-while(SDPHI*SDPHI>genau2&&iter<maxiter);Latitude=Math.atan(SPHI/Math.abs(CPHI));p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_geodetic_noniter:function(p){var X=p.x;var Y=p.y;var Z=p.z?p.z:0;var Longitude;var Latitude;var Height;var W;var W2;var T0;var T1;var S0;var S1;var Sin_B0;var Sin3_B0;var Cos_B0;var Sin_p1;var Cos_p1;var Rn;var Sum;var At_Pole;X=parseFloat(X);Y=parseFloat(Y);Z=parseFloat(Z);At_Pole=false;if(X!=0.0)
-{Longitude=Math.atan2(Y,X);}
-else
-{if(Y>0)
-{Longitude=Proj4js.common.HALF_PI;}
-else if(Y<0)
-{Longitude=-Proj4js.common.HALF_PI;}
-else
-{At_Pole=true;Longitude=0.0;if(Z>0.0)
-{Latitude=Proj4js.common.HALF_PI;}
-else if(Z<0.0)
-{Latitude=-Proj4js.common.HALF_PI;}
-else
-{Latitude=Proj4js.common.HALF_PI;Height=-this.b;return;}}}
-W2=X*X+Y*Y;W=Math.sqrt(W2);T0=Z*Proj4js.common.AD_C;S0=Math.sqrt(T0*T0+W2);Sin_B0=T0/S0;Cos_B0=W/S0;Sin3_B0=Sin_B0*Sin_B0*Sin_B0;T1=Z+this.b*this.ep2*Sin3_B0;Sum=W-this.a*this.es*Cos_B0*Cos_B0*Cos_B0;S1=Math.sqrt(T1*T1+Sum*Sum);Sin_p1=T1/S1;Cos_p1=Sum/S1;Rn=this.a/Math.sqrt(1.0-this.es*Sin_p1*Sin_p1);if(Cos_p1>=Proj4js.common.COS_67P5)
-{Height=W/Cos_p1-Rn;}
-else if(Cos_p1<=-Proj4js.common.COS_67P5)
-{Height=W/-Cos_p1-Rn;}
-else
-{Height=Z/Sin_p1+Rn*(this.es-1.0);}
-if(At_Pole==false)
-{Latitude=Math.atan(Sin_p1/Cos_p1);}
-p.x=Longitude;p.y=Latitude;p.z=Height;return p;},geocentric_to_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
-{p.x+=this.datum_params[0];p.y+=this.datum_params[1];p.z+=this.datum_params[2];}
-else if(this.datum_type==Proj4js.common.PJD_7PARAM)
-{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_out=M_BF*(p.x-Rz_BF*p.y+Ry_BF*p.z)+Dx_BF;var y_out=M_BF*(Rz_BF*p.x+p.y-Rx_BF*p.z)+Dy_BF;var z_out=M_BF*(-Ry_BF*p.x+Rx_BF*p.y+p.z)+Dz_BF;p.x=x_out;p.y=y_out;p.z=z_out;}},geocentric_from_wgs84:function(p){if(this.datum_type==Proj4js.common.PJD_3PARAM)
-{p.x-=this.datum_params[0];p.y-=this.datum_params[1];p.z-=this.datum_params[2];}
-else if(this.datum_type==Proj4js.common.PJD_7PARAM)
-{var Dx_BF=this.datum_params[0];var Dy_BF=this.datum_params[1];var Dz_BF=this.datum_params[2];var Rx_BF=this.datum_params[3];var Ry_BF=this.datum_params[4];var Rz_BF=this.datum_params[5];var M_BF=this.datum_params[6];var x_tmp=(p.x-Dx_BF)/M_BF;var y_tmp=(p.y-Dy_BF)/M_BF;var z_tmp=(p.z-Dz_BF)/M_BF;p.x=x_tmp+Rz_BF*y_tmp-Ry_BF*z_tmp;p.y=-Rz_BF*x_tmp+y_tmp+Rx_BF*z_tmp;p.z=Ry_BF*x_tmp-Rx_BF*y_tmp+z_tmp;}}});Proj4js.Point=Proj4js.Class({initialize:function(x,y,z){if(typeof x=='object'){this.x=x[0];this.y=x[1];this.z=x[2]||0.0;}else if(typeof x=='string'){var coords=x.split(',');this.x=parseFloat(coords[0]);this.y=parseFloat(coords[1]);this.z=parseFloat(coords[2])||0.0;}else{this.x=x;this.y=y;this.z=z||0.0;}},clone:function(){return new Proj4js.Point(this.x,this.y,this.z);},toString:function(){return("x="+this.x+",y="+this.y);},toShortString:function(){return(this.x+", "+this.y);}});Proj4js.PrimeMeridian={"greenwich":0.0,"lisbon":-9.131906111111,"paris":2.337229166667,"bogota":-74.
 080916666667,"madrid":-3.687938888889,"rome":12.452333333333,"bern":7.439583333333,"jakarta":106.807719444444,"ferro":-17.666666666667,"brussels":4.367975,"stockholm":18.058277777778,"athens":23.7163375,"oslo":10.722916666667};Proj4js.Ellipsoid={"MERIT":{a:6378137.0,rf:298.257,ellipseName:"MERIT 1983"},"SGS85":{a:6378136.0,rf:298.257,ellipseName:"Soviet Geodetic System 85"},"GRS80":{a:6378137.0,rf:298.257222101,ellipseName:"GRS 1980(IUGG, 1980)"},"IAU76":{a:6378140.0,rf:298.257,ellipseName:"IAU 1976"},"airy":{a:6377563.396,b:6356256.910,ellipseName:"Airy 1830"},"APL4.":{a:6378137,rf:298.25,ellipseName:"Appl. Physics. 1965"},"NWL9D":{a:6378145.0,rf:298.25,ellipseName:"Naval Weapons Lab., 1965"},"mod_airy":{a:6377340.189,b:6356034.446,ellipseName:"Modified Airy"},"andrae":{a:6377104.43,rf:300.0,ellipseName:"Andrae 1876 (Den., Iclnd.)"},"aust_SA":{a:6378160.0,rf:298.25,ellipseName:"Australian Natl & S. Amer. 1969"},"GRS67":{a:6378160.0,rf:298.2471674270,ellipseName:"GRS 67(IUGG
  1967)"},"bessel":{a:6377397.155,rf:299.1528128,ellipseName:"Bessel 1841"},"bess_nam":{a:6377483.865,rf:299.1528128,ellipseName:"Bessel 1841 (Namibia)"},"clrk66":{a:6378206.4,b:6356583.8,ellipseName:"Clarke 1866"},"clrk80":{a:6378249.145,rf:293.4663,ellipseName:"Clarke 1880 mod."},"CPM":{a:6375738.7,rf:334.29,ellipseName:"Comm. des Poids et Mesures 1799"},"delmbr":{a:6376428.0,rf:311.5,ellipseName:"Delambre 1810 (Belgium)"},"engelis":{a:6378136.05,rf:298.2566,ellipseName:"Engelis 1985"},"evrst30":{a:6377276.345,rf:300.8017,ellipseName:"Everest 1830"},"evrst48":{a:6377304.063,rf:300.8017,ellipseName:"Everest 1948"},"evrst56":{a:6377301.243,rf:300.8017,ellipseName:"Everest 1956"},"evrst69":{a:6377295.664,rf:300.8017,ellipseName:"Everest 1969"},"evrstSS":{a:6377298.556,rf:300.8017,ellipseName:"Everest (Sabah & Sarawak)"},"fschr60":{a:6378166.0,rf:298.3,ellipseName:"Fischer (Mercury Datum) 1960"},"fschr60m":{a:6378155.0,rf:298.3,ellipseName:"Fischer 1960"},"fschr68":{a:6378150.0
 ,rf:298.3,ellipseName:"Fischer 1968"},"helmert":{a:6378200.0,rf:298.3,ellipseName:"Helmert 1906"},"hough":{a:6378270.0,rf:297.0,ellipseName:"Hough"},"intl":{a:6378388.0,rf:297.0,ellipseName:"International 1909 (Hayford)"},"kaula":{a:6378163.0,rf:298.24,ellipseName:"Kaula 1961"},"lerch":{a:6378139.0,rf:298.257,ellipseName:"Lerch 1979"},"mprts":{a:6397300.0,rf:191.0,ellipseName:"Maupertius 1738"},"new_intl":{a:6378157.5,b:6356772.2,ellipseName:"New International 1967"},"plessis":{a:6376523.0,rf:6355863.0,ellipseName:"Plessis 1817 (France)"},"krass":{a:6378245.0,rf:298.3,ellipseName:"Krassovsky, 1942"},"SEasia":{a:6378155.0,b:6356773.3205,ellipseName:"Southeast Asia"},"walbeck":{a:6376896.0,b:6355834.8467,ellipseName:"Walbeck"},"WGS60":{a:6378165.0,rf:298.3,ellipseName:"WGS 60"},"WGS66":{a:6378145.0,rf:298.25,ellipseName:"WGS 66"},"WGS72":{a:6378135.0,rf:298.26,ellipseName:"WGS 72"},"WGS84":{a:6378137.0,rf:298.257223563,ellipseName:"WGS 84"},"sphere":{a:6370997.0,b:6370997.0,el
 lipseName:"Normal Sphere (r=6370997)"}};Proj4js.Datum={"WGS84":{towgs84:"0,0,0",ellipse:"WGS84",datumName:"WGS84"},"GGRS87":{towgs84:"-199.87,74.79,246.62",ellipse:"GRS80",datumName:"Greek_Geodetic_Reference_System_1987"},"NAD83":{towgs84:"0,0,0",ellipse:"GRS80",datumName:"North_American_Datum_1983"},"NAD27":{nadgrids:"@conus, at alaska, at ntv2_0.gsb, at ntv1_can.dat",ellipse:"clrk66",datumName:"North_American_Datum_1927"},"potsdam":{towgs84:"606.0,23.0,413.0",ellipse:"bessel",datumName:"Potsdam Rauenberg 1950 DHDN"},"carthage":{towgs84:"-263.0,6.0,431.0",ellipse:"clark80",datumName:"Carthage 1934 Tunisia"},"hermannskogel":{towgs84:"653.0,-212.0,449.0",ellipse:"bessel",datumName:"Hermannskogel"},"ire65":{towgs84:"482.530,-130.596,564.557,-1.042,-0.214,-0.631,8.15",ellipse:"mod_airy",datumName:"Ireland 1965"},"nzgd49":{towgs84:"59.47,-5.04,187.44,0.47,-0.1,1.024,-4.5993",ellipse:"intl",datumName:"New Zealand Geodetic Datum 1949"},"OSGB36":{towgs84:"446.448,-125.157,542.060,0.1502,0.2
 470,0.8421,-20.4894",ellipse:"airy",datumName:"Airy 1830"}};Proj4js.WGS84=new Proj4js.Proj('WGS84');Proj4js.Datum['OSB36']=Proj4js.Datum['OSGB36'];Proj4js.wktProjections={"Lambert Tangential Conformal Conic Projection":"lcc","Mercator":"merc","Transverse_Mercator":"tmerc","Transverse Mercator":"tmerc","Lambert Azimuthal Equal Area":"laea","Universal Transverse Mercator System":"utm"};Proj4js.Proj.aea={init:function(){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("aeaInitEqualLatitudes");return;}
-this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e3=Math.sqrt(this.es);this.sin_po=Math.sin(this.lat1);this.cos_po=Math.cos(this.lat1);this.t1=this.sin_po;this.con=this.sin_po;this.ms1=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs1=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat2);this.cos_po=Math.cos(this.lat2);this.t2=this.sin_po;this.ms2=Proj4js.common.msfnz(this.e3,this.sin_po,this.cos_po);this.qs2=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);this.sin_po=Math.sin(this.lat0);this.cos_po=Math.cos(this.lat0);this.t3=this.sin_po;this.qs0=Proj4js.common.qsfnz(this.e3,this.sin_po,this.cos_po);if(Math.abs(this.lat1-this.lat2)>Proj4js.common.EPSLN){this.ns0=(this.ms1*this.ms1-this.ms2*this.ms2)/(this.qs2-this.qs1);}else{this.ns0=this.con;}
-this.c=this.ms1*this.ms1+this.ns0*this.qs1;this.rh=this.a*Math.sqrt(this.c-this.ns0*this.qs0)/this.ns0;},forward:function(p){var lon=p.x;var lat=p.y;this.sin_phi=Math.sin(lat);this.cos_phi=Math.cos(lat);var qs=Proj4js.common.qsfnz(this.e3,this.sin_phi,this.cos_phi);var rh1=this.a*Math.sqrt(this.c-this.ns0*qs)/this.ns0;var theta=this.ns0*Proj4js.common.adjust_lon(lon-this.long0);var x=rh1*Math.sin(theta)+this.x0;var y=this.rh-rh1*Math.cos(theta)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var rh1,qs,con,theta,lon,lat;p.x-=this.x0;p.y=this.rh-p.y+this.y0;if(this.ns0>=0){rh1=Math.sqrt(p.x*p.x+p.y*p.y);con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
-theta=0.0;if(rh1!=0.0){theta=Math.atan2(con*p.x,con*p.y);}
-con=rh1*this.ns0/this.a;qs=(this.c-con*con)/this.ns0;if(this.e3>=1e-10){con=1-.5*(1.0-this.es)*Math.log((1.0-this.e3)/(1.0+this.e3))/this.e3;if(Math.abs(Math.abs(con)-Math.abs(qs))>.0000000001){lat=this.phi1z(this.e3,qs);}else{if(qs>=0){lat=.5*PI;}else{lat=-.5*PI;}}}else{lat=this.phi1z(e3,qs);}
-lon=Proj4js.common.adjust_lon(theta/this.ns0+this.long0);p.x=lon;p.y=lat;return p;},phi1z:function(eccent,qs){var con,com,dphi;var phi=Proj4js.common.asinz(.5*qs);if(eccent<Proj4js.common.EPSLN)return phi;var eccnts=eccent*eccent;for(var i=1;i<=25;i++){sinphi=Math.sin(phi);cosphi=Math.cos(phi);con=eccent*sinphi;com=1.0-con*con;dphi=.5*com*com/cosphi*(qs/(1.0-eccnts)-sinphi/com+.5/eccent*Math.log((1.0-con)/(1.0+con)));phi=phi+dphi;if(Math.abs(dphi)<=1e-7)return phi;}
-Proj4js.reportError("aea:phi1z:Convergence error");return null;}};Proj4js.Proj.sterea={dependsOn:'gauss',init:function(){Proj4js.Proj['gauss'].init.apply(this);if(!this.rc){Proj4js.reportError("sterea:init:E_ERROR_0");return;}
-this.sinc0=Math.sin(this.phic0);this.cosc0=Math.cos(this.phic0);this.R2=2.0*this.rc;if(!this.title)this.title="Oblique Stereographic Alternative";},forward:function(p){p.x=Proj4js.common.adjust_lon(p.x-this.long0);Proj4js.Proj['gauss'].forward.apply(this,[p]);sinc=Math.sin(p.y);cosc=Math.cos(p.y);cosl=Math.cos(p.x);k=this.k0*this.R2/(1.0+this.sinc0*sinc+this.cosc0*cosc*cosl);p.x=k*cosc*Math.sin(p.x);p.y=k*(this.cosc0*sinc-this.sinc0*cosc*cosl);p.x=this.a*p.x+this.x0;p.y=this.a*p.y+this.y0;return p;},inverse:function(p){var lon,lat;p.x=(p.x-this.x0)/this.a;p.y=(p.y-this.y0)/this.a;p.x/=this.k0;p.y/=this.k0;if((rho=Math.sqrt(p.x*p.x+p.y*p.y))){c=2.0*Math.atan2(rho,this.R2);sinc=Math.sin(c);cosc=Math.cos(c);lat=Math.asin(cosc*this.sinc0+p.y*sinc*this.cosc0/rho);lon=Math.atan2(p.x*sinc,rho*this.cosc0*cosc-p.y*this.sinc0*sinc);}else{lat=this.phic0;lon=0.;}
-p.x=lon;p.y=lat;Proj4js.Proj['gauss'].inverse.apply(this,[p]);p.x=Proj4js.common.adjust_lon(p.x+this.long0);return p;}};function phi4z(eccent,e0,e1,e2,e3,a,b,c,phi){var sinphi,sin2ph,tanph,ml,mlp,con1,con2,con3,dphi,i;phi=a;for(i=1;i<=15;i++){sinphi=Math.sin(phi);tanphi=Math.tan(phi);c=tanphi*Math.sqrt(1.0-eccent*sinphi*sinphi);sin2ph=Math.sin(2.0*phi);ml=e0*phi-e1*sin2ph+e2*Math.sin(4.0*phi)-e3*Math.sin(6.0*phi);mlp=e0-2.0*e1*Math.cos(2.0*phi)+4.0*e2*Math.cos(4.0*phi)-6.0*e3*Math.cos(6.0*phi);con1=2.0*ml+c*(ml*ml+b)-2.0*a*(c*ml+1.0);con2=eccent*sin2ph*(ml*ml+b-2.0*a*ml)/(2.0*c);con3=2.0*(a-ml)*(c*mlp-2.0/sin2ph)-2.0*mlp;dphi=con1/(con2+con3);phi+=dphi;if(Math.abs(dphi)<=.0000000001)return(phi);}
-Proj4js.reportError("phi4z: No convergence");return null;}
-function e4fn(x){var con,com;con=1.0+x;com=1.0-x;return(Math.sqrt((Math.pow(con,con))*(Math.pow(com,com))));}
-Proj4js.Proj.poly={init:function(){var temp;if(this.lat0=0)this.lat0=90;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var sinphi,cosphi;var al;var c;var con,ml;var ms;var x,y;var lon=p.x;var lat=p.y;con=Proj4js.common.adjust_lon(lon-this.long0);if(Math.abs(lat)<=.0000001){x=this.x0+this.a*con;y=this.y0-this.a*this.ml0;}else{sinphi=Math.sin(lat);cosphi=Math.cos(lat);ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);ms=Proj4js.common.msfnz(this.e,sinphi,cosphi);con=sinphi;x=this.x0+this.a*ms*Math.sin(con)/sinphi;y=this.y0+this.a*(ml-this.ml0+ms*(1.0-Math.cos(con))/sinphi);}
-p.x=x;p.y=y;return p;},inverse:function(p){var sin_phi,cos_phi;var al;var b;var c;var con,ml;var iflg;var lon,lat;p.x-=this.x0;p.y-=this.y0;al=this.ml0+p.y/this.a;iflg=0;if(Math.abs(al)<=.0000001){lon=p.x/this.a+this.long0;lat=0.0;}else{b=al*al+(p.x/this.a)*(p.x/this.a);iflg=phi4z(this.es,this.e0,this.e1,this.e2,this.e3,this.al,b,c,lat);if(iflg!=1)return(iflg);lon=Proj4js.common.adjust_lon((Proj4js.common.asinz(p.x*c/this.a)/Math.sin(lat))+this.long0);}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.equi={init:function(){if(!this.x0)this.x0=0;if(!this.y0)this.y0=0;if(!this.lat0)this.lat0=0;if(!this.long0)this.long0=0;},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon*Math.cos(this.lat0);var y=this.y0+this.a*lat;this.t1=x;this.t2=Math.cos(this.lat0);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lat=p.y/this.a;if(Math.abs(lat)>Proj4js.common.HALF_PI){Proj4js.reportError("equi:Inv:DataError");}
-var lon=Proj4js.common.adjust_lon(this.long0+p.x/(this.a*Math.cos(this.lat0)));p.x=lon;p.y=lat;}};Proj4js.Proj.merc={init:function(){if(this.lat_ts){if(this.sphere){this.k0=Math.cos(this.lat_ts);}else{this.k0=Proj4js.common.msfnz(this.es,Math.sin(this.lat_ts),Math.cos(this.lat_ts));}}},forward:function(p){var lon=p.x;var lat=p.y;if(lat*Proj4js.common.R2D>90.0&&lat*Proj4js.common.R2D<-90.0&&lon*Proj4js.common.R2D>180.0&&lon*Proj4js.common.R2D<-180.0){Proj4js.reportError("merc:forward: llInputOutOfRange: "+lon+" : "+lat);return null;}
-var x,y;if(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN){Proj4js.reportError("merc:forward: ll2mAtPoles");return null;}else{if(this.sphere){x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0+this.a*this.k0*Math.log(Math.tan(Proj4js.common.FORTPI+0.5*lat));}else{var sinphi=Math.sin(lat);var ts=Proj4js.common.tsfnz(this.e,lat,sinphi);x=this.x0+this.a*this.k0*Proj4js.common.adjust_lon(lon-this.long0);y=this.y0-this.a*this.k0*Math.log(ts);}
-p.x=x;p.y=y;return p;}},inverse:function(p){var x=p.x-this.x0;var y=p.y-this.y0;var lon,lat;if(this.sphere){lat=Proj4js.common.HALF_PI-2.0*Math.atan(Math.exp(-y/this.a*this.k0));}else{var ts=Math.exp(-y/(this.a*this.k0));lat=Proj4js.common.phi2z(this.e,ts);if(lat==-9999){Proj4js.reportError("merc:inverse: lat = -9999");return null;}}
-lon=Proj4js.common.adjust_lon(this.long0+x/(this.a*this.k0));p.x=lon;p.y=lat;return p;}};Proj4js.Proj.utm={dependsOn:'tmerc',init:function(){if(!this.zone){Proj4js.reportError("utm:init: zone must be specified for UTM");return;}
-this.lat0=0.0;this.long0=((6*Math.abs(this.zone))-183)*Proj4js.common.D2R;this.x0=500000.0;this.y0=this.utmSouth?10000000.0:0.0;this.k0=0.9996;Proj4js.Proj['tmerc'].init.apply(this);this.forward=Proj4js.Proj['tmerc'].forward;this.inverse=Proj4js.Proj['tmerc'].inverse;}};Proj4js.Proj.eqdc={init:function(){if(!this.mode)this.mode=0;this.temp=this.b/this.a;this.es=1.0-Math.pow(this.temp,2);this.e=Math.sqrt(this.es);this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.sinphi=Math.sin(this.lat1);this.cosphi=Math.cos(this.lat1);this.ms1=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml1=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat1);if(this.mode!=0){if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("eqdc:Init:EqualLatitudes");}
-this.sinphi=Math.sin(this.lat2);this.cosphi=Math.cos(this.lat2);this.ms2=Proj4js.common.msfnz(this.e,this.sinphi,this.cosphi);this.ml2=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat2);if(Math.abs(this.lat1-this.lat2)>=Proj4js.common.EPSLN){this.ns=(this.ms1-this.ms2)/(this.ml2-this.ml1);}else{this.ns=this.sinphi;}}else{this.ns=this.sinphi;}
-this.g=this.ml1+this.ms1/this.ns;this.ml0=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);this.rh=this.a*(this.g-this.ml0);},forward:function(p){var lon=p.x;var lat=p.y;var ml=Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);var rh1=this.a*(this.g-ml);var theta=this.ns*Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+rh1*Math.sin(theta);var y=this.y0+this.rh-rh1*Math.cos(theta);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y=this.rh-p.y+this.y0;var con,rh1;if(this.ns>=0){var rh1=Math.sqrt(p.x*p.x+p.y*p.y);var con=1.0;}else{rh1=-Math.sqrt(p.x*p.x+p.y*p.y);con=-1.0;}
-var theta=0.0;if(rh1!=0.0)theta=Math.atan2(con*p.x,con*p.y);var ml=this.g-rh1/this.a;var lat=this.phi3z(ml,this.e0,this.e1,this.e2,this.e3);var lon=Proj4js.common.adjust_lon(this.long0+theta/this.ns);p.x=lon;p.y=lat;return p;},phi3z:function(ml,e0,e1,e2,e3){var phi;var dphi;phi=ml;for(var i=0;i<15;i++){dphi=(ml+e1*Math.sin(2.0*phi)-e2*Math.sin(4.0*phi)+e3*Math.sin(6.0*phi))/e0-phi;phi+=dphi;if(Math.abs(dphi)<=.0000000001){return phi;}}
-Proj4js.reportError("PHI3Z-CONV:Latitude failed to converge after 15 iterations");return null;}};Proj4js.Proj.tmerc={init:function(){this.e0=Proj4js.common.e0fn(this.es);this.e1=Proj4js.common.e1fn(this.es);this.e2=Proj4js.common.e2fn(this.es);this.e3=Proj4js.common.e3fn(this.es);this.ml0=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,this.lat0);},forward:function(p){var lon=p.x;var lat=p.y;var delta_lon=Proj4js.common.adjust_lon(lon-this.long0);var con;var x,y;var sin_phi=Math.sin(lat);var cos_phi=Math.cos(lat);if(this.sphere){var b=cos_phi*Math.sin(delta_lon);if((Math.abs(Math.abs(b)-1.0))<.0000000001){Proj4js.reportError("tmerc:forward: Point projects into infinity");return(93);}else{x=.5*this.a*this.k0*Math.log((1.0+b)/(1.0-b));con=Math.acos(cos_phi*Math.cos(delta_lon)/Math.sqrt(1.0-b*b));if(lat<0)con=-con;y=this.a*this.k0*(con-this.lat0);}}else{var al=cos_phi*delta_lon;var als=Math.pow(al,2);var c=this.ep2*Math.pow(cos_phi,2);var tq=Math.tan(lat);var t=Math.
 pow(tq,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var ml=this.a*Proj4js.common.mlfn(this.e0,this.e1,this.e2,this.e3,lat);x=this.k0*n*al*(1.0+als/6.0*(1.0-t+c+als/20.0*(5.0-18.0*t+Math.pow(t,2)+72.0*c-58.0*this.ep2)))+this.x0;y=this.k0*(ml-this.ml0+n*tq*(als*(0.5+als/24.0*(5.0-t+9.0*c+4.0*Math.pow(c,2)+als/30.0*(61.0-58.0*t+Math.pow(t,2)+600.0*c-330.0*this.ep2)))))+this.y0;}
-p.x=x;p.y=y;return p;},inverse:function(p){var con,phi;var delta_phi;var i;var max_iter=6;var lat,lon;if(this.sphere){var f=Math.exp(p.x/(this.a*this.k0));var g=.5*(f-1/f);var temp=this.lat0+p.y/(this.a*this.k0);var h=Math.cos(temp);con=Math.sqrt((1.0-h*h)/(1.0+g*g));lat=Proj4js.common.asinz(con);if(temp<0)
-lat=-lat;if((g==0)&&(h==0)){lon=this.long0;}else{lon=Proj4js.common.adjust_lon(Math.atan2(g,h)+this.long0);}}else{var x=p.x-this.x0;var y=p.y-this.y0;con=(this.ml0+y/this.k0)/this.a;phi=con;for(i=0;true;i++){delta_phi=((con+this.e1*Math.sin(2.0*phi)-this.e2*Math.sin(4.0*phi)+this.e3*Math.sin(6.0*phi))/this.e0)-phi;phi+=delta_phi;if(Math.abs(delta_phi)<=Proj4js.common.EPSLN)break;if(i>=max_iter){Proj4js.reportError("tmerc:inverse: Latitude failed to converge");return(95);}}
-if(Math.abs(phi)<Proj4js.common.HALF_PI){var sin_phi=Math.sin(phi);var cos_phi=Math.cos(phi);var tan_phi=Math.tan(phi);var c=this.ep2*Math.pow(cos_phi,2);var cs=Math.pow(c,2);var t=Math.pow(tan_phi,2);var ts=Math.pow(t,2);con=1.0-this.es*Math.pow(sin_phi,2);var n=this.a/Math.sqrt(con);var r=n*(1.0-this.es)/con;var d=x/(n*this.k0);var ds=Math.pow(d,2);lat=phi-(n*tan_phi*ds/r)*(0.5-ds/24.0*(5.0+3.0*t+10.0*c-4.0*cs-9.0*this.ep2-ds/30.0*(61.0+90.0*t+298.0*c+45.0*ts-252.0*this.ep2-3.0*cs)));lon=Proj4js.common.adjust_lon(this.long0+(d*(1.0-ds/6.0*(1.0+2.0*t+c-ds/20.0*(5.0-2.0*c+28.0*t-3.0*cs+8.0*this.ep2+24.0*ts)))/cos_phi));}else{lat=Proj4js.common.HALF_PI*Proj4js.common.sign(y);lon=this.long0;}}
-p.x=lon;p.y=lat;return p;}};Proj4js.defs["GOOGLE"]="+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +no_defs";Proj4js.defs["EPSG:900913"]=Proj4js.defs["GOOGLE"];Proj4js.Proj.gstmerc={init:function(){var temp=this.b/this.a;this.e=Math.sqrt(1.0-temp*temp);this.lc=this.long0;this.rs=Math.sqrt(1.0+this.e*this.e*Math.pow(Math.cos(this.lat0),4.0)/(1.0-this.e*this.e));var sinz=Math.sin(this.lat0);var pc=Math.asin(sinz/this.rs);var sinzpc=Math.sin(pc);this.cp=Proj4js.common.latiso(0.0,pc,sinzpc)-this.rs*Proj4js.common.latiso(this.e,this.lat0,sinz);this.n2=this.k0*this.a*Math.sqrt(1.0-this.e*this.e)/(1.0-this.e*this.e*sinz*sinz);this.xs=this.x0;this.ys=this.y0-this.n2*pc;if(!this.title)this.title="Gauss Schreiber transverse mercator";},forward:function(p){var lon=p.x;var lat=p.y;var L=this.rs*(lon-this.lc);var Ls=this.cp+(this.rs*Proj4js.common.latiso(this.e,lat,Math.sin(lat)));var lat1=Math.asin(Math.sin(L)/Proj4js.common.cosh(
 Ls));var Ls1=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.xs+(this.n2*Ls1);p.y=this.ys+(this.n2*Math.atan(Proj4js.common.sinh(Ls)/Math.cos(L)));return p;},inverse:function(p){var x=p.x;var y=p.y;var L=Math.atan(Proj4js.common.sinh((x-this.xs)/this.n2)/Math.cos((y-this.ys)/this.n2));var lat1=Math.asin(Math.sin((y-this.ys)/this.n2)/Proj4js.common.cosh((x-this.xs)/this.n2));var LC=Proj4js.common.latiso(0.0,lat1,Math.sin(lat1));p.x=this.lc+L/this.rs;p.y=Proj4js.common.invlatiso(this.e,(LC-this.cp)/this.rs);return p;}};Proj4js.Proj.ortho={init:function(def){;this.sin_p14=Math.sin(this.lat0);this.cos_p14=Math.cos(this.lat0);},forward:function(p){var sinphi,cosphi;var dlon;var coslon;var ksp;var g;var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslon=Math.cos(dlon);g=this.sin_p14*sinphi+this.cos_p14*cosphi*coslon;ksp=1.0;if((g>0)||(Math.abs(g)<=Proj4js.common.EPSLN)){var x=this.a*ksp*cosphi*Math.sin(dlon);
 var y=this.y0+this.a*ksp*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon);}else{Proj4js.reportError("orthoFwdPointError");}
-p.x=x;p.y=y;return p;},inverse:function(p){var rh;var z;var sinz,cosz;var temp;var con;var lon,lat;p.x-=this.x0;p.y-=this.y0;rh=Math.sqrt(p.x*p.x+p.y*p.y);if(rh>this.a+.0000001){Proj4js.reportError("orthoInvDataError");}
-z=Proj4js.common.asinz(rh/this.a);sinz=Math.sin(z);cosz=Math.cos(z);lon=this.long0;if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.lat0;}
-lat=Proj4js.common.asinz(cosz*this.sin_p14+(p.y*sinz*this.cos_p14)/rh);con=Math.abs(this.lat0)-Proj4js.common.HALF_PI;if(Math.abs(con)<=Proj4js.common.EPSLN){if(this.lat0>=0){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x,-p.y));}else{lon=Proj4js.common.adjust_lon(this.long0-Math.atan2(-p.x,p.y));}}
-con=cosz-this.sin_p14*Math.sin(lat);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.somerc={init:function(){var phy0=this.lat0;this.lambda0=this.long0;var sinPhy0=Math.sin(phy0);var semiMajorAxis=this.a;var invF=this.rf;var flattening=1/invF;var e2=2*flattening-Math.pow(flattening,2);var e=this.e=Math.sqrt(e2);this.R=this.k0*semiMajorAxis*Math.sqrt(1-e2)/(1-e2*Math.pow(sinPhy0,2.0));this.alpha=Math.sqrt(1+e2/(1-e2)*Math.pow(Math.cos(phy0),4.0));this.b0=Math.asin(sinPhy0/this.alpha);this.K=Math.log(Math.tan(Math.PI/4.0+this.b0/2.0))
--this.alpha*Math.log(Math.tan(Math.PI/4.0+phy0/2.0))
-+this.alpha*e/2*Math.log((1+e*sinPhy0)/(1-e*sinPhy0));},forward:function(p){var Sa1=Math.log(Math.tan(Math.PI/4.0-p.y/2.0));var Sa2=this.e/2.0*Math.log((1+this.e*Math.sin(p.y))/(1-this.e*Math.sin(p.y)));var S=-this.alpha*(Sa1+Sa2)+this.K;var b=2.0*(Math.atan(Math.exp(S))-Math.PI/4.0);var I=this.alpha*(p.x-this.lambda0);var rotI=Math.atan(Math.sin(I)/(Math.sin(this.b0)*Math.tan(b)+
-Math.cos(this.b0)*Math.cos(I)));var rotB=Math.asin(Math.cos(this.b0)*Math.sin(b)-
-Math.sin(this.b0)*Math.cos(b)*Math.cos(I));p.y=this.R/2.0*Math.log((1+Math.sin(rotB))/(1-Math.sin(rotB)))
-+this.y0;p.x=this.R*rotI+this.x0;return p;},inverse:function(p){var Y=p.x-this.x0;var X=p.y-this.y0;var rotI=Y/this.R;var rotB=2*(Math.atan(Math.exp(X/this.R))-Math.PI/4.0);var b=Math.asin(Math.cos(this.b0)*Math.sin(rotB)
-+Math.sin(this.b0)*Math.cos(rotB)*Math.cos(rotI));var I=Math.atan(Math.sin(rotI)/(Math.cos(this.b0)*Math.cos(rotI)-Math.sin(this.b0)*Math.tan(rotB)));var lambda=this.lambda0+I/this.alpha;var S=0.0;var phy=b;var prevPhy=-1000.0;var iteration=0;while(Math.abs(phy-prevPhy)>0.0000001)
-{if(++iteration>20)
-{Proj4js.reportError("omercFwdInfinity");return;}
-S=1.0/this.alpha*(Math.log(Math.tan(Math.PI/4.0+b/2.0))-this.K)
-+this.e*Math.log(Math.tan(Math.PI/4.0
-+Math.asin(this.e*Math.sin(phy))/2.0));prevPhy=phy;phy=2.0*Math.atan(Math.exp(S))-Math.PI/2.0;}
-p.x=lambda;p.y=phy;return p;}};Proj4js.Proj.stere={ssfn_:function(phit,sinphi,eccen){sinphi*=eccen;return(Math.tan(.5*(Proj4js.common.HALF_PI+phit))*Math.pow((1.-sinphi)/(1.+sinphi),.5*eccen));},TOL:1.e-8,NITER:8,CONV:1.e-10,S_POLE:0,N_POLE:1,OBLIQ:2,EQUIT:3,init:function(){this.phits=this.lat_ts?this.lat_ts:Proj4js.common.HALF_PI;var t=Math.abs(this.lat0);if((Math.abs(t)-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.mode=this.lat0<0.?this.S_POLE:this.N_POLE;}else{this.mode=t>Proj4js.common.EPSLN?this.OBLIQ:this.EQUIT;}
-this.phits=Math.abs(this.phits);if(this.es){var X;switch(this.mode){case this.N_POLE:case this.S_POLE:if(Math.abs(this.phits-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.akm1=2.*this.k0/Math.sqrt(Math.pow(1+this.e,1+this.e)*Math.pow(1-this.e,1-this.e));}else{t=Math.sin(this.phits);this.akm1=Math.cos(this.phits)/Proj4js.common.tsfnz(this.e,this.phits,t);t*=this.e;this.akm1/=Math.sqrt(1.-t*t);}
-break;case this.EQUIT:this.akm1=2.*this.k0;break;case this.OBLIQ:t=Math.sin(this.lat0);X=2.*Math.atan(this.ssfn_(this.lat0,t,this.e))-Proj4js.common.HALF_PI;t*=this.e;this.akm1=2.*this.k0*Math.cos(this.lat0)/Math.sqrt(1.-t*t);this.sinX1=Math.sin(X);this.cosX1=Math.cos(X);break;}}else{switch(this.mode){case this.OBLIQ:this.sinph0=Math.sin(this.lat0);this.cosph0=Math.cos(this.lat0);case this.EQUIT:this.akm1=2.*this.k0;break;case this.S_POLE:case this.N_POLE:this.akm1=Math.abs(this.phits-Proj4js.common.HALF_PI)>=Proj4js.common.EPSLN?Math.cos(this.phits)/Math.tan(Proj4js.common.FORTPI-.5*this.phits):2.*this.k0;break;}}},forward:function(p){var lon=p.x;lon=Proj4js.common.adjust_lon(lon-this.long0);var lat=p.y;var x,y;if(this.sphere){var sinphi,cosphi,coslam,sinlam;sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslam=Math.cos(lon);sinlam=Math.sin(lon);switch(this.mode){case this.EQUIT:y=1.+cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
-y=this.akm1/y;x=y*cosphi*sinlam;y*=sinphi;break;case this.OBLIQ:y=1.+this.sinph0*sinphi+this.cosph0*cosphi*coslam;if(y<=Proj4js.common.EPSLN){F_ERROR;}
-y=this.akm1/y;x=y*cosphi*sinlam;y*=this.cosph0*sinphi-this.sinph0*cosphi*coslam;break;case this.N_POLE:coslam=-coslam;lat=-lat;case this.S_POLE:if(Math.abs(lat-Proj4js.common.HALF_PI)<this.TOL){F_ERROR;}
-y=this.akm1*Math.tan(Proj4js.common.FORTPI+.5*lat);x=sinlam*y;y*=coslam;break;}}else{coslam=Math.cos(lon);sinlam=Math.sin(lon);sinphi=Math.sin(lat);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){X=2.*Math.atan(this.ssfn_(lat,sinphi,this.e));sinX=Math.sin(X-Proj4js.common.HALF_PI);cosX=Math.cos(X);}
-switch(this.mode){case this.OBLIQ:A=this.akm1/(this.cosX1*(1.+this.sinX1*sinX+this.cosX1*cosX*coslam));y=A*(this.cosX1*sinX-this.sinX1*cosX*coslam);x=A*cosX;break;case this.EQUIT:A=2.*this.akm1/(1.+cosX*coslam);y=A*sinX;x=A*cosX;break;case this.S_POLE:lat=-lat;coslam=-coslam;sinphi=-sinphi;case this.N_POLE:x=this.akm1*Proj4js.common.tsfnz(this.e,lat,sinphi);y=-x*coslam;break;}
-x=x*sinlam;}
-p.x=x*this.a+this.x0;p.y=y*this.a+this.y0;return p;},inverse:function(p){var x=(p.x-this.x0)/this.a;var y=(p.y-this.y0)/this.a;var lon,lat;var cosphi,sinphi,tp=0.0,phi_l=0.0,rho,halfe=0.0,pi2=0.0;var i;if(this.sphere){var c,rh,sinc,cosc;rh=Math.sqrt(x*x+y*y);c=2.*Math.atan(rh/this.akm1);sinc=Math.sin(c);cosc=Math.cos(c);lon=0.;switch(this.mode){case this.EQUIT:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=0.;}else{lat=Math.asin(y*sinc/rh);}
-if(cosc!=0.||x!=0.)lon=Math.atan2(x*sinc,cosc*rh);break;case this.OBLIQ:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.phi0;}else{lat=Math.asin(cosc*sinph0+y*sinc*cosph0/rh);}
-c=cosc-sinph0*Math.sin(lat);if(c!=0.||x!=0.){lon=Math.atan2(x*sinc*cosph0,c*rh);}
-break;case this.N_POLE:y=-y;case this.S_POLE:if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.phi0;}else{lat=Math.asin(this.mode==this.S_POLE?-cosc:cosc);}
-lon=(x==0.&&y==0.)?0.:Math.atan2(x,y);break;}}else{rho=Math.sqrt(x*x+y*y);switch(this.mode){case this.OBLIQ:case this.EQUIT:tp=2.*Math.atan2(rho*this.cosX1,this.akm1);cosphi=Math.cos(tp);sinphi=Math.sin(tp);if(rho==0.0){phi_l=Math.asin(cosphi*this.sinX1);}else{phi_l=Math.asin(cosphi*this.sinX1+(y*sinphi*this.cosX1/rho));}
-tp=Math.tan(.5*(Proj4js.common.HALF_PI+phi_l));x*=sinphi;y=rho*this.cosX1*cosphi-y*this.sinX1*sinphi;pi2=Proj4js.common.HALF_PI;halfe=.5*this.e;break;case this.N_POLE:y=-y;case this.S_POLE:tp=-rho/this.akm1;phi_l=Proj4js.common.HALF_PI-2.*Math.atan(tp);pi2=-Proj4js.common.HALF_PI;halfe=-.5*this.e;break;}
-for(i=this.NITER;i--;phi_l=lat){sinphi=this.e*Math.sin(phi_l);lat=2.*Math.atan(tp*Math.pow((1.+sinphi)/(1.-sinphi),halfe))-pi2;if(Math.abs(phi_l-lat)<this.CONV){if(this.mode==this.S_POLE)lat=-lat;lon=(x==0.&&y==0.)?0.:Math.atan2(x,y);p.x=Proj4js.common.adjust_lon(lon+this.long0);p.y=lat;return p;}}}}};Proj4js.Proj.nzmg={iterations:1,init:function(){this.A=new Array();this.A[1]=+0.6399175073;this.A[2]=-0.1358797613;this.A[3]=+0.063294409;this.A[4]=-0.02526853;this.A[5]=+0.0117879;this.A[6]=-0.0055161;this.A[7]=+0.0026906;this.A[8]=-0.001333;this.A[9]=+0.00067;this.A[10]=-0.00034;this.B_re=new Array();this.B_im=new Array();this.B_re[1]=+0.7557853228;this.B_im[1]=0.0;this.B_re[2]=+0.249204646;this.B_im[2]=+0.003371507;this.B_re[3]=-0.001541739;this.B_im[3]=+0.041058560;this.B_re[4]=-0.10162907;this.B_im[4]=+0.01727609;this.B_re[5]=-0.26623489;this.B_im[5]=-0.36249218;this.B_re[6]=-0.6870983;this.B_im[6]=-1.1651967;this.C_re=new Array();this.C_im=new Array();this.C_re[1]=+1.3231
 270439;this.C_im[1]=0.0;this.C_re[2]=-0.577245789;this.C_im[2]=-0.007809598;this.C_re[3]=+0.508307513;this.C_im[3]=-0.112208952;this.C_re[4]=-0.15094762;this.C_im[4]=+0.18200602;this.C_re[5]=+1.01418179;this.C_im[5]=+1.64497696;this.C_re[6]=+1.9660549;this.C_im[6]=+2.5127645;this.D=new Array();this.D[1]=+1.5627014243;this.D[2]=+0.5185406398;this.D[3]=-0.03333098;this.D[4]=-0.1052906;this.D[5]=-0.0368594;this.D[6]=+0.007317;this.D[7]=+0.01220;this.D[8]=+0.00394;this.D[9]=-0.0013;},forward:function(p){var lon=p.x;var lat=p.y;var delta_lat=lat-this.lat0;var delta_lon=lon-this.long0;var d_phi=delta_lat/Proj4js.common.SEC_TO_RAD*1E-5;var d_lambda=delta_lon;var d_phi_n=1;var d_psi=0;for(n=1;n<=10;n++){d_phi_n=d_phi_n*d_phi;d_psi=d_psi+this.A[n]*d_phi_n;}
-var th_re=d_psi;var th_im=d_lambda;var th_n_re=1;var th_n_im=0;var th_n_re1;var th_n_im1;var z_re=0;var z_im=0;for(n=1;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;z_re=z_re+this.B_re[n]*th_n_re-this.B_im[n]*th_n_im;z_im=z_im+this.B_im[n]*th_n_re+this.B_re[n]*th_n_im;}
-x=(z_im*this.a)+this.x0;y=(z_re*this.a)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var x=p.x;var y=p.y;var delta_x=x-this.x0;var delta_y=y-this.y0;var z_re=delta_y/this.a;var z_im=delta_x/this.a;var z_n_re=1;var z_n_im=0;var z_n_re1;var z_n_im1;var th_re=0;var th_im=0;for(n=1;n<=6;n++){z_n_re1=z_n_re*z_re-z_n_im*z_im;z_n_im1=z_n_im*z_re+z_n_re*z_im;z_n_re=z_n_re1;z_n_im=z_n_im1;th_re=th_re+this.C_re[n]*z_n_re-this.C_im[n]*z_n_im;th_im=th_im+this.C_im[n]*z_n_re+this.C_re[n]*z_n_im;}
-for(i=0;i<this.iterations;i++){var th_n_re=th_re;var th_n_im=th_im;var th_n_re1;var th_n_im1;var num_re=z_re;var num_im=z_im;for(n=2;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;num_re=num_re+(n-1)*(this.B_re[n]*th_n_re-this.B_im[n]*th_n_im);num_im=num_im+(n-1)*(this.B_im[n]*th_n_re+this.B_re[n]*th_n_im);}
-th_n_re=1;th_n_im=0;var den_re=this.B_re[1];var den_im=this.B_im[1];for(n=2;n<=6;n++){th_n_re1=th_n_re*th_re-th_n_im*th_im;th_n_im1=th_n_im*th_re+th_n_re*th_im;th_n_re=th_n_re1;th_n_im=th_n_im1;den_re=den_re+n*(this.B_re[n]*th_n_re-this.B_im[n]*th_n_im);den_im=den_im+n*(this.B_im[n]*th_n_re+this.B_re[n]*th_n_im);}
-var den2=den_re*den_re+den_im*den_im;th_re=(num_re*den_re+num_im*den_im)/den2;th_im=(num_im*den_re-num_re*den_im)/den2;}
-var d_psi=th_re;var d_lambda=th_im;var d_psi_n=1;var d_phi=0;for(n=1;n<=9;n++){d_psi_n=d_psi_n*d_psi;d_phi=d_phi+this.D[n]*d_psi_n;}
-var lat=this.lat0+(d_phi*Proj4js.common.SEC_TO_RAD*1E5);var lon=this.long0+d_lambda;p.x=lon;p.y=lat;return p;}};Proj4js.Proj.mill={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon;var y=this.y0+this.a*Math.log(Math.tan((Proj4js.common.PI/4.0)+(lat/2.5)))*1.25;p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lon=Proj4js.common.adjust_lon(this.long0+p.x/this.a);var lat=2.5*(Math.atan(Math.exp(0.8*p.y/this.a))-Proj4js.common.PI/4.0);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.gnom={init:function(def){this.sin_p14=Math.sin(this.lat0);this.cos_p14=Math.cos(this.lat0);this.infinity_dist=1000*this.a;this.rc=1;},forward:function(p){var sinphi,cosphi;var dlon;var coslon;var ksp;var g;var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);sinphi=Math.sin(lat);cosphi=Math.cos(lat);coslon=Math.cos(dlon);g=this.sin_p14*sinphi+this.cos_p14*cosphi*coslon;ksp=1.0;if
 ((g>0)||(Math.abs(g)<=Proj4js.common.EPSLN)){x=this.x0+this.a*ksp*cosphi*Math.sin(dlon)/g;y=this.y0+this.a*ksp*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon)/g;}else{Proj4js.reportError("orthoFwdPointError");x=this.x0+this.infinity_dist*cosphi*Math.sin(dlon);y=this.y0+this.infinity_dist*(this.cos_p14*sinphi-this.sin_p14*cosphi*coslon);}
-p.x=x;p.y=y;return p;},inverse:function(p){var rh;var z;var sinc,cosc;var c;var lon,lat;p.x=(p.x-this.x0)/this.a;p.y=(p.y-this.y0)/this.a;p.x/=this.k0;p.y/=this.k0;if((rh=Math.sqrt(p.x*p.x+p.y*p.y))){c=Math.atan2(rh,this.rc);sinc=Math.sin(c);cosc=Math.cos(c);lat=Proj4js.common.asinz(cosc*this.sin_p14+(p.y*sinc*this.cos_p14)/rh);lon=Math.atan2(p.x*sinc,rh*this.cos_p14*cosc-p.y*this.sin_p14*sinc);lon=Proj4js.common.adjust_lon(this.long0+lon);}else{lat=this.phic0;lon=0.0;}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.sinu={init:function(){this.R=6370997.0;},forward:function(p){var x,y,delta_lon;var lon=p.x;var lat=p.y;delta_lon=Proj4js.common.adjust_lon(lon-this.long0);x=this.R*delta_lon*Math.cos(lat)+this.x0;y=this.R*lat+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var lat,temp,lon;p.x-=this.x0;p.y-=this.y0;lat=p.y/this.R;if(Math.abs(lat)>Proj4js.common.HALF_PI){Proj4js.reportError("sinu:Inv:DataError");}
-temp=Math.abs(lat)-Proj4js.common.HALF_PI;if(Math.abs(temp)>Proj4js.common.EPSLN){temp=this.long0+p.x/(this.R*Math.cos(lat));lon=Proj4js.common.adjust_lon(temp);}else{lon=this.long0;}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.vandg={init:function(){this.R=6370997.0;},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var x,y;if(Math.abs(lat)<=Proj4js.common.EPSLN){x=this.x0+this.R*dlon;y=this.y0;}
-var theta=Proj4js.common.asinz(2.0*Math.abs(lat/Proj4js.common.PI));if((Math.abs(dlon)<=Proj4js.common.EPSLN)||(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN)){x=this.x0;if(lat>=0){y=this.y0+Proj4js.common.PI*this.R*Math.tan(.5*theta);}else{y=this.y0+Proj4js.common.PI*this.R*-Math.tan(.5*theta);}}
-var al=.5*Math.abs((Proj4js.common.PI/dlon)-(dlon/Proj4js.common.PI));var asq=al*al;var sinth=Math.sin(theta);var costh=Math.cos(theta);var g=costh/(sinth+costh-1.0);var gsq=g*g;var m=g*(2.0/sinth-1.0);var msq=m*m;var con=Proj4js.common.PI*this.R*(al*(g-msq)+Math.sqrt(asq*(g-msq)*(g-msq)-(msq+asq)*(gsq-msq)))/(msq+asq);if(dlon<0){con=-con;}
-x=this.x0+con;con=Math.abs(con/(Proj4js.common.PI*this.R));if(lat>=0){y=this.y0+Proj4js.common.PI*this.R*Math.sqrt(1.0-con*con-2.0*al*con);}else{y=this.y0-Proj4js.common.PI*this.R*Math.sqrt(1.0-con*con-2.0*al*con);}
-p.x=x;p.y=y;return p;},inverse:function(p){var dlon;var xx,yy,xys,c1,c2,c3;var al,asq;var a1;var m1;var con;var th1;var d;p.x-=this.x0;p.y-=this.y0;con=Proj4js.common.PI*this.R;xx=p.x/con;yy=p.y/con;xys=xx*xx+yy*yy;c1=-Math.abs(yy)*(1.0+xys);c2=c1-2.0*yy*yy+xx*xx;c3=-2.0*c1+1.0+2.0*yy*yy+xys*xys;d=yy*yy/c3+(2.0*c2*c2*c2/c3/c3/c3-9.0*c1*c2/c3/c3)/27.0;a1=(c1-c2*c2/3.0/c3)/c3;m1=2.0*Math.sqrt(-a1/3.0);con=((3.0*d)/a1)/m1;if(Math.abs(con)>1.0){if(con>=0.0){con=1.0;}else{con=-1.0;}}
-th1=Math.acos(con)/3.0;if(p.y>=0){lat=(-m1*Math.cos(th1+Proj4js.common.PI/3.0)-c2/3.0/c3)*Proj4js.common.PI;}else{lat=-(-m1*Math.cos(th1+Proj4js.common.PI/3.0)-c2/3.0/c3)*Proj4js.common.PI;}
-if(Math.abs(xx)<Proj4js.common.EPSLN){lon=this.long0;}
-lon=Proj4js.common.adjust_lon(this.long0+Proj4js.common.PI*(xys-1.0+Math.sqrt(1.0+2.0*(xx*xx-yy*yy)+xys*xys))/2.0/xx);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.cea={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;dlon=Proj4js.common.adjust_lon(lon-this.long0);var x=this.x0+this.a*dlon*Math.cos(this.lat_ts);var y=this.y0+this.a*Math.sin(lat)/Math.cos(this.lat_ts);p.x=x;p.y=y;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var lon=Proj4js.common.adjust_lon(this.long0+(p.x/this.a)/Math.cos(this.lat_ts));var lat=Math.asin((p.y/this.a)*Math.cos(this.lat_ts));p.x=lon;p.y=lat;return p;}};Proj4js.Proj.eqc={init:function(){if(!this.x0)this.x0=0;if(!this.y0)this.y0=0;if(!this.lat0)this.lat0=0;if(!this.long0)this.long0=0;if(!this.lat_ts)this.lat_ts=0;if(!this.title)this.title="Equidistant Cylindrical (Plate Carre)";this.rc=Math.cos(this.lat_ts);},forward:function(p){var lon=p.x;var lat=p.y;var dlon=Proj4js.common.adjust_lon(lon-this.long0);var dlat=Proj4js.common.
 adjust_lat(lat-this.lat0);p.x=this.x0+(this.a*dlon*this.rc);p.y=this.y0+(this.a*dlat);return p;},inverse:function(p){var x=p.x;var y=p.y;p.x=Proj4js.common.adjust_lon(this.long0+((x-this.x0)/(this.a*this.rc)));p.y=Proj4js.common.adjust_lat(this.lat0+((y-this.y0)/(this.a)));return p;}};Proj4js.Proj.cass={init:function(){if(!this.sphere){this.en=this.pj_enfn(this.es)
-this.m0=this.pj_mlfn(this.lat0,Math.sin(this.lat0),Math.cos(this.lat0),this.en);}},C1:.16666666666666666666,C2:.00833333333333333333,C3:.04166666666666666666,C4:.33333333333333333333,C5:.06666666666666666666,forward:function(p){var x,y;var lam=p.x;var phi=p.y;lam=Proj4js.common.adjust_lon(lam-this.long0);if(this.sphere){x=Math.asin(Math.cos(phi)*Math.sin(lam));y=Math.atan2(Math.tan(phi),Math.cos(lam))-this.phi0;}else{this.n=Math.sin(phi);this.c=Math.cos(phi);y=this.pj_mlfn(phi,this.n,this.c,this.en);this.n=1./Math.sqrt(1.-this.es*this.n*this.n);this.tn=Math.tan(phi);this.t=this.tn*this.tn;this.a1=lam*this.c;this.c*=this.es*this.c/(1-this.es);this.a2=this.a1*this.a1;x=this.n*this.a1*(1.-this.a2*this.t*(this.C1-(8.-this.t+8.*this.c)*this.a2*this.C2));y-=this.m0-this.n*this.tn*this.a2*(.5+(5.-this.t+6.*this.c)*this.a2*this.C3);}
-p.x=this.a*x+this.x0;p.y=this.a*y+this.y0;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var x=p.x/this.a;var y=p.y/this.a;if(this.sphere){this.dd=y+this.lat0;phi=Math.asin(Math.sin(this.dd)*Math.cos(x));lam=Math.atan2(Math.tan(x),Math.cos(this.dd));}else{ph1=this.pj_inv_mlfn(this.m0+y,this.es,this.en);this.tn=Math.tan(ph1);this.t=this.tn*this.tn;this.n=Math.sin(ph1);this.r=1./(1.-this.es*this.n*this.n);this.n=Math.sqrt(this.r);this.r*=(1.-this.es)*this.n;this.dd=x/this.n;this.d2=this.dd*this.dd;phi=ph1-(this.n*this.tn/this.r)*this.d2*(.5-(1.+3.*this.t)*this.d2*this.C3);lam=this.dd*(1.+this.t*this.d2*(-this.C4+(1.+3.*this.t)*this.d2*this.C5))/Math.cos(ph1);}
-p.x=Proj4js.common.adjust_lon(this.long0+lam);p.y=phi;return p;},pj_enfn:function(es){en=new Array();en[0]=this.C00-es*(this.C02+es*(this.C04+es*(this.C06+es*this.C08)));en[1]=es*(this.C22-es*(this.C04+es*(this.C06+es*this.C08)));var t=es*es;en[2]=t*(this.C44-es*(this.C46+es*this.C48));t*=es;en[3]=t*(this.C66-es*this.C68);en[4]=t*es*this.C88;return en;},pj_mlfn:function(phi,sphi,cphi,en){cphi*=sphi;sphi*=sphi;return(en[0]*phi-cphi*(en[1]+sphi*(en[2]+sphi*(en[3]+sphi*en[4]))));},pj_inv_mlfn:function(arg,es,en){k=1./(1.-es);phi=arg;for(i=Proj4js.common.MAX_ITER;i;--i){s=Math.sin(phi);t=1.-es*s*s;t=(this.pj_mlfn(phi,s,Math.cos(phi),en)-arg)*(t*Math.sqrt(t))*k;phi-=t;if(Math.abs(t)<Proj4js.common.EPSLN)
-return phi;}
-Proj4js.reportError("cass:pj_inv_mlfn: Convergence error");return phi;},C00:1.0,C02:.25,C04:.046875,C06:.01953125,C08:.01068115234375,C22:.75,C44:.46875,C46:.01302083333333333333,C48:.00712076822916666666,C66:.36458333333333333333,C68:.00569661458333333333,C88:.3076171875}
-Proj4js.Proj.gauss={init:function(){sphi=Math.sin(this.lat0);cphi=Math.cos(this.lat0);cphi*=cphi;this.rc=Math.sqrt(1.0-this.es)/(1.0-this.es*sphi*sphi);this.C=Math.sqrt(1.0+this.es*cphi*cphi/(1.0-this.es));this.phic0=Math.asin(sphi/this.C);this.ratexp=0.5*this.C*this.e;this.K=Math.tan(0.5*this.phic0+Proj4js.common.FORTPI)/(Math.pow(Math.tan(0.5*this.lat0+Proj4js.common.FORTPI),this.C)*Proj4js.common.srat(this.e*sphi,this.ratexp));},forward:function(p){var lon=p.x;var lat=p.y;p.y=2.0*Math.atan(this.K*Math.pow(Math.tan(0.5*lat+Proj4js.common.FORTPI),this.C)*Proj4js.common.srat(this.e*Math.sin(lat),this.ratexp))-Proj4js.common.HALF_PI;p.x=this.C*lon;return p;},inverse:function(p){var DEL_TOL=1e-14;var lon=p.x/this.C;var lat=p.y;num=Math.pow(Math.tan(0.5*lat+Proj4js.common.FORTPI)/this.K,1./this.C);for(var i=Proj4js.common.MAX_ITER;i>0;--i){lat=2.0*Math.atan(num*Proj4js.common.srat(this.e*Math.sin(p.y),-0.5*this.e))-Proj4js.common.HALF_PI;if(Math.abs(lat-p.y)<DEL_TOL)break;p.y=l
 at;}
-if(!i){Proj4js.reportError("gauss:inverse:convergence failed");return null;}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.omerc={init:function(){if(!this.mode)this.mode=0;if(!this.lon1){this.lon1=0;this.mode=1;}
-if(!this.lon2)this.lon2=0;if(!this.lat2)this.lat2=0;var temp=this.b/this.a;var es=1.0-Math.pow(temp,2);var e=Math.sqrt(es);this.sin_p20=Math.sin(this.lat0);this.cos_p20=Math.cos(this.lat0);this.con=1.0-this.es*this.sin_p20*this.sin_p20;this.com=Math.sqrt(1.0-es);this.bl=Math.sqrt(1.0+this.es*Math.pow(this.cos_p20,4.0)/(1.0-es));this.al=this.a*this.bl*this.k0*this.com/this.con;if(Math.abs(this.lat0)<Proj4js.common.EPSLN){this.ts=1.0;this.d=1.0;this.el=1.0;}else{this.ts=Proj4js.common.tsfnz(this.e,this.lat0,this.sin_p20);this.con=Math.sqrt(this.con);this.d=this.bl*this.com/(this.cos_p20*this.con);if((this.d*this.d-1.0)>0.0){if(this.lat0>=0.0){this.f=this.d+Math.sqrt(this.d*this.d-1.0);}else{this.f=this.d-Math.sqrt(this.d*this.d-1.0);}}else{this.f=this.d;}
-this.el=this.f*Math.pow(this.ts,this.bl);}
-if(this.mode!=0){this.g=.5*(this.f-1.0/this.f);this.gama=Proj4js.common.asinz(Math.sin(this.alpha)/this.d);this.longc=this.longc-Proj4js.common.asinz(this.g*Math.tan(this.gama))/this.bl;this.con=Math.abs(this.lat0);if((this.con>Proj4js.common.EPSLN)&&(Math.abs(this.con-Proj4js.common.HALF_PI)>Proj4js.common.EPSLN)){this.singam=Math.sin(this.gama);this.cosgam=Math.cos(this.gama);this.sinaz=Math.sin(this.alpha);this.cosaz=Math.cos(this.alpha);if(this.lat0>=0){this.u=(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}else{this.u=-(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}}else{Proj4js.reportError("omerc:Init:DataError");}}else{this.sinphi=Math.sin(this.at1);this.ts1=Proj4js.common.tsfnz(this.e,this.lat1,this.sinphi);this.sinphi=Math.sin(this.lat2);this.ts2=Proj4js.common.tsfnz(this.e,this.lat2,this.sinphi);this.h=Math.pow(this.ts1,this.bl);this.l=Math.pow(this.ts2,this.bl);this.f=this.el/this.h;this.g=.5*(this.f-1.0/this.f);this.j=
 (this.el*this.el-this.l*this.h)/(this.el*this.el+this.l*this.h);this.p=(this.l-this.h)/(this.l+this.h);this.dlon=this.lon1-this.lon2;if(this.dlon<-Proj4js.common.PI)this.lon2=this.lon2-2.0*Proj4js.common.PI;if(this.dlon>Proj4js.common.PI)this.lon2=this.lon2+2.0*Proj4js.common.PI;this.dlon=this.lon1-this.lon2;this.longc=.5*(this.lon1+this.lon2)-Math.atan(this.j*Math.tan(.5*this.bl*this.dlon)/this.p)/this.bl;this.dlon=Proj4js.common.adjust_lon(this.lon1-this.longc);this.gama=Math.atan(Math.sin(this.bl*this.dlon)/this.g);this.alpha=Proj4js.common.asinz(this.d*Math.sin(this.gama));if(Math.abs(this.lat1-this.lat2)<=Proj4js.common.EPSLN){Proj4js.reportError("omercInitDataError");}else{this.con=Math.abs(this.lat1);}
-if((this.con<=Proj4js.common.EPSLN)||(Math.abs(this.con-HALF_PI)<=Proj4js.common.EPSLN)){Proj4js.reportError("omercInitDataError");}else{if(Math.abs(Math.abs(this.lat0)-Proj4js.common.HALF_PI)<=Proj4js.common.EPSLN){Proj4js.reportError("omercInitDataError");}}
-this.singam=Math.sin(this.gam);this.cosgam=Math.cos(this.gam);this.sinaz=Math.sin(this.alpha);this.cosaz=Math.cos(this.alpha);if(this.lat0>=0){this.u=(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}else{this.u=-(this.al/this.bl)*Math.atan(Math.sqrt(this.d*this.d-1.0)/this.cosaz);}}},forward:function(p){var theta;var sin_phi,cos_phi;var b;var c,t,tq;var con,n,ml;var q,us,vl;var ul,vs;var s;var dlon;var ts1;var lon=p.x;var lat=p.y;sin_phi=Math.sin(lat);dlon=Proj4js.common.adjust_lon(lon-this.longc);vl=Math.sin(this.bl*dlon);if(Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI)>Proj4js.common.EPSLN){ts1=Proj4js.common.tsfnz(this.e,lat,sin_phi);q=this.el/(Math.pow(ts1,this.bl));s=.5*(q-1.0/q);t=.5*(q+1.0/q);ul=(s*this.singam-vl*this.cosgam)/t;con=Math.cos(this.bl*dlon);if(Math.abs(con)<.0000001){us=this.al*this.bl*dlon;}else{us=this.al*Math.atan((s*this.cosgam+vl*this.singam)/con)/this.bl;if(con<0)us=us+Proj4js.common.PI*this.al/this.bl;}}else{if(lat>=0){ul=thi
 s.singam;}else{ul=-this.singam;}
-us=this.al*lat/this.bl;}
-if(Math.abs(Math.abs(ul)-1.0)<=Proj4js.common.EPSLN){Proj4js.reportError("omercFwdInfinity");}
-vs=.5*this.al*Math.log((1.0-ul)/(1.0+ul))/this.bl;us=us-this.u;var x=this.x0+vs*this.cosaz+us*this.sinaz;var y=this.y0+us*this.cosaz-vs*this.sinaz;p.x=x;p.y=y;return p;},inverse:function(p){var delta_lon;var theta;var delta_theta;var sin_phi,cos_phi;var b;var c,t,tq;var con,n,ml;var vs,us,q,s,ts1;var vl,ul,bs;var dlon;var flag;p.x-=this.x0;p.y-=this.y0;flag=0;vs=p.x*this.cosaz-p.y*this.sinaz;us=p.y*this.cosaz+p.x*this.sinaz;us=us+this.u;q=Math.exp(-this.bl*vs/this.al);s=.5*(q-1.0/q);t=.5*(q+1.0/q);vl=Math.sin(this.bl*us/this.al);ul=(vl*this.cosgam+s*this.singam)/t;if(Math.abs(Math.abs(ul)-1.0)<=Proj4js.common.EPSLN)
-{lon=this.longc;if(ul>=0.0){lat=Proj4js.common.HALF_PI;}else{lat=-Proj4js.common.HALF_PI;}}else{con=1.0/this.bl;ts1=Math.pow((this.el/Math.sqrt((1.0+ul)/(1.0-ul))),con);lat=Proj4js.common.phi2z(this.e,ts1);theta=this.longc-Math.atan2((s*this.cosgam-vl*this.singam),con)/this.bl;lon=Proj4js.common.adjust_lon(theta);}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.lcc={init:function(){if(!this.lat2){this.lat2=this.lat0;}
-if(!this.k0)this.k0=1.0;if(Math.abs(this.lat1+this.lat2)<Proj4js.common.EPSLN){Proj4js.reportError("lcc:init: Equal Latitudes");return;}
-var temp=this.b/this.a;this.e=Math.sqrt(1.0-temp*temp);var sin1=Math.sin(this.lat1);var cos1=Math.cos(this.lat1);var ms1=Proj4js.common.msfnz(this.e,sin1,cos1);var ts1=Proj4js.common.tsfnz(this.e,this.lat1,sin1);var sin2=Math.sin(this.lat2);var cos2=Math.cos(this.lat2);var ms2=Proj4js.common.msfnz(this.e,sin2,cos2);var ts2=Proj4js.common.tsfnz(this.e,this.lat2,sin2);var ts0=Proj4js.common.tsfnz(this.e,this.lat0,Math.sin(this.lat0));if(Math.abs(this.lat1-this.lat2)>Proj4js.common.EPSLN){this.ns=Math.log(ms1/ms2)/Math.log(ts1/ts2);}else{this.ns=sin1;}
-this.f0=ms1/(this.ns*Math.pow(ts1,this.ns));this.rh=this.a*this.f0*Math.pow(ts0,this.ns);if(!this.title)this.title="Lambert Conformal Conic";},forward:function(p){var lon=p.x;var lat=p.y;if(lat<=90.0&&lat>=-90.0&&lon<=180.0&&lon>=-180.0){}else{Proj4js.reportError("lcc:forward: llInputOutOfRange: "+lon+" : "+lat);return null;}
-var con=Math.abs(Math.abs(lat)-Proj4js.common.HALF_PI);var ts,rh1;if(con>Proj4js.common.EPSLN){ts=Proj4js.common.tsfnz(this.e,lat,Math.sin(lat));rh1=this.a*this.f0*Math.pow(ts,this.ns);}else{con=lat*this.ns;if(con<=0){Proj4js.reportError("lcc:forward: No Projection");return null;}
-rh1=0;}
-var theta=this.ns*Proj4js.common.adjust_lon(lon-this.long0);p.x=this.k0*(rh1*Math.sin(theta))+this.x0;p.y=this.k0*(this.rh-rh1*Math.cos(theta))+this.y0;return p;},inverse:function(p){var rh1,con,ts;var lat,lon;var x=(p.x-this.x0)/this.k0;var y=(this.rh-(p.y-this.y0)/this.k0);if(this.ns>0){rh1=Math.sqrt(x*x+y*y);con=1.0;}else{rh1=-Math.sqrt(x*x+y*y);con=-1.0;}
-var theta=0.0;if(rh1!=0){theta=Math.atan2((con*x),(con*y));}
-if((rh1!=0)||(this.ns>0.0)){con=1.0/this.ns;ts=Math.pow((rh1/(this.a*this.f0)),con);lat=Proj4js.common.phi2z(this.e,ts);if(lat==-9999)return null;}else{lat=-Proj4js.common.HALF_PI;}
-lon=Proj4js.common.adjust_lon(theta/this.ns+this.long0);p.x=lon;p.y=lat;return p;}};Proj4js.Proj.laea={S_POLE:1,N_POLE:2,EQUIT:3,OBLIQ:4,init:function(){var t=Math.abs(this.lat0);if(Math.abs(t-Proj4js.common.HALF_PI)<Proj4js.common.EPSLN){this.mode=this.lat0<0.?this.S_POLE:this.N_POLE;}else if(Math.abs(t)<Proj4js.common.EPSLN){this.mode=this.EQUIT;}else{this.mode=this.OBLIQ;}
-if(this.es>0){var sinphi;this.qp=Proj4js.common.qsfnz(this.e,1.0);this.mmf=.5/(1.-this.es);this.apa=this.authset(this.es);switch(this.mode){case this.N_POLE:case this.S_POLE:this.dd=1.;break;case this.EQUIT:this.rq=Math.sqrt(.5*this.qp);this.dd=1./this.rq;this.xmf=1.;this.ymf=.5*this.qp;break;case this.OBLIQ:this.rq=Math.sqrt(.5*this.qp);sinphi=Math.sin(this.lat0);this.sinb1=Proj4js.common.qsfnz(this.e,sinphi)/this.qp;this.cosb1=Math.sqrt(1.-this.sinb1*this.sinb1);this.dd=Math.cos(this.lat0)/(Math.sqrt(1.-this.es*sinphi*sinphi)*this.rq*this.cosb1);this.ymf=(this.xmf=this.rq)/this.dd;this.xmf*=this.dd;break;}}else{if(this.mode==this.OBLIQ){this.sinph0=Math.sin(this.lat0);this.cosph0=Math.cos(this.lat0);}}},forward:function(p){var x,y;var lam=p.x;var phi=p.y;lam=Proj4js.common.adjust_lon(lam-this.long0);if(this.sphere){var coslam,cosphi,sinphi;sinphi=Math.sin(phi);cosphi=Math.cos(phi);coslam=Math.cos(lam);switch(this.mode){case this.EQUIT:y=(this.mode==this.EQUIT)?1.+cosphi*co
 slam:1.+this.sinph0*sinphi+this.cosph0*cosphi*coslam;if(y<=Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:y less than eps");return null;}
-y=Math.sqrt(2./y);x=y*cosphi*Math.sin(lam);y*=(this.mode==this.EQUIT)?sinphi:this.cosph0*sinphi-this.sinph0*cosphi*coslam;break;case this.N_POLE:coslam=-coslam;case this.S_POLE:if(Math.abs(phi+this.phi0)<Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:phi < eps");return null;}
-y=Proj4js.common.FORTPI-phi*.5;y=2.*((this.mode==this.S_POLE)?Math.cos(y):Math.sin(y));x=y*Math.sin(lam);y*=coslam;break;}}else{var coslam,sinlam,sinphi,q,sinb=0.0,cosb=0.0,b=0.0;coslam=Math.cos(lam);sinlam=Math.sin(lam);sinphi=Math.sin(phi);q=Proj4js.common.qsfnz(this.e,sinphi);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){sinb=q/this.qp;cosb=Math.sqrt(1.-sinb*sinb);}
-switch(this.mode){case this.OBLIQ:b=1.+this.sinb1*sinb+this.cosb1*cosb*coslam;break;case this.EQUIT:b=1.+cosb*coslam;break;case this.N_POLE:b=Proj4js.common.HALF_PI+phi;q=this.qp-q;break;case this.S_POLE:b=phi-Proj4js.common.HALF_PI;q=this.qp+q;break;}
-if(Math.abs(b)<Proj4js.common.EPSLN){Proj4js.reportError("laea:fwd:b < eps");return null;}
-switch(this.mode){case this.OBLIQ:case this.EQUIT:b=Math.sqrt(2./b);if(this.mode==this.OBLIQ){y=this.ymf*b*(this.cosb1*sinb-this.sinb1*cosb*coslam);}else{y=(b=Math.sqrt(2./(1.+cosb*coslam)))*sinb*this.ymf;}
-x=this.xmf*b*cosb*sinlam;break;case this.N_POLE:case this.S_POLE:if(q>=0.){x=(b=Math.sqrt(q))*sinlam;y=coslam*((this.mode==this.S_POLE)?b:-b);}else{x=y=0.;}
-break;}}
-p.x=this.a*x+this.x0;p.y=this.a*y+this.y0;return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var x=p.x/this.a;var y=p.y/this.a;if(this.sphere){var cosz=0.0,rh,sinz=0.0;rh=Math.sqrt(x*x+y*y);var phi=rh*.5;if(phi>1.){Proj4js.reportError("laea:Inv:DataError");return null;}
-phi=2.*Math.asin(phi);if(this.mode==this.OBLIQ||this.mode==this.EQUIT){sinz=Math.sin(phi);cosz=Math.cos(phi);}
-switch(this.mode){case this.EQUIT:phi=(Math.abs(rh)<=Proj4js.common.EPSLN)?0.:Math.asin(y*sinz/rh);x*=sinz;y=cosz*rh;break;case this.OBLIQ:phi=(Math.abs(rh)<=Proj4js.common.EPSLN)?this.phi0:Math.asin(cosz*sinph0+y*sinz*cosph0/rh);x*=sinz*cosph0;y=(cosz-Math.sin(phi)*sinph0)*rh;break;case this.N_POLE:y=-y;phi=Proj4js.common.HALF_PI-phi;break;case this.S_POLE:phi-=Proj4js.common.HALF_PI;break;}
-lam=(y==0.&&(this.mode==this.EQUIT||this.mode==this.OBLIQ))?0.:Math.atan2(x,y);}else{var cCe,sCe,q,rho,ab=0.0;switch(this.mode){case this.EQUIT:case this.OBLIQ:x/=this.dd;y*=this.dd;rho=Math.sqrt(x*x+y*y);if(rho<Proj4js.common.EPSLN){p.x=0.;p.y=this.phi0;return p;}
-sCe=2.*Math.asin(.5*rho/this.rq);cCe=Math.cos(sCe);x*=(sCe=Math.sin(sCe));if(this.mode==this.OBLIQ){ab=cCe*this.sinb1+y*sCe*this.cosb1/rho
-q=this.qp*ab;y=rho*this.cosb1*cCe-y*this.sinb1*sCe;}else{ab=y*sCe/rho;q=this.qp*ab;y=rho*cCe;}
-break;case this.N_POLE:y=-y;case this.S_POLE:q=(x*x+y*y);if(!q){p.x=0.;p.y=this.phi0;return p;}
-ab=1.-q/this.qp;if(this.mode==this.S_POLE){ab=-ab;}
-break;}
-lam=Math.atan2(x,y);phi=this.authlat(Math.asin(ab),this.apa);}
-p.x=Proj4js.common.adjust_lon(this.long0+lam);p.y=phi;return p;},P00:.33333333333333333333,P01:.17222222222222222222,P02:.10257936507936507936,P10:.06388888888888888888,P11:.06640211640211640211,P20:.01641501294219154443,authset:function(es){var t;var APA=new Array();APA[0]=es*this.P00;t=es*es;APA[0]+=t*this.P01;APA[1]=t*this.P10;t*=es;APA[0]+=t*this.P02;APA[1]+=t*this.P11;APA[2]=t*this.P20;return APA;},authlat:function(beta,APA){var t=beta+beta;return(beta+APA[0]*Math.sin(t)+APA[1]*Math.sin(t+t)+APA[2]*Math.sin(t+t+t));}};Proj4js.Proj.aeqd={init:function(){this.sin_p12=Math.sin(this.lat0);this.cos_p12=Math.cos(this.lat0);},forward:function(p){var lon=p.x;var lat=p.y;var ksp;var sinphi=Math.sin(p.y);var cosphi=Math.cos(p.y);var dlon=Proj4js.common.adjust_lon(lon-this.long0);var coslon=Math.cos(dlon);var g=this.sin_p12*sinphi+this.cos_p12*cosphi*coslon;if(Math.abs(Math.abs(g)-1.0)<Proj4js.common.EPSLN){ksp=1.0;if(g<0.0){Proj4js.reportError("aeqd:Fwd:PointError");return;}}else
 {var z=Math.acos(g);ksp=z/Math.sin(z);}
-p.x=this.x0+this.a*ksp*cosphi*Math.sin(dlon);p.y=this.y0+this.a*ksp*(this.cos_p12*sinphi-this.sin_p12*cosphi*coslon);return p;},inverse:function(p){p.x-=this.x0;p.y-=this.y0;var rh=Math.sqrt(p.x*p.x+p.y*p.y);if(rh>(2.0*Proj4js.common.HALF_PI*this.a)){Proj4js.reportError("aeqdInvDataError");return;}
-var z=rh/this.a;var sinz=Math.sin(z);var cosz=Math.cos(z);var lon=this.long0;var lat;if(Math.abs(rh)<=Proj4js.common.EPSLN){lat=this.lat0;}else{lat=Proj4js.common.asinz(cosz*this.sin_p12+(p.y*sinz*this.cos_p12)/rh);var con=Math.abs(this.lat0)-Proj4js.common.HALF_PI;if(Math.abs(con)<=Proj4js.common.EPSLN){if(lat0>=0.0){lon=Proj4js.common.adjust_lon(this.long0+Math.atan2(p.x,-p.y));}else{lon=Proj4js.common.adjust_lon(this.long0-Math.atan2(-p.x,p.y));}}else{con=cosz-this.sin_p12*Math.sin(lat);if((Math.abs(con)<Proj4js.common.EPSLN)&&(Math.abs(p.x)<Proj4js.common.EPSLN)){}else{var temp=Math.atan2((p.x*sinz*this.cos_p12),(con*rh));lon=Proj4js.common.adjust_lon(this.long0+Math.atan2((p.x*sinz*this.cos_p12),(con*rh)));}}}
-p.x=lon;p.y=lat;return p;}};Proj4js.Proj.moll={init:function(){},forward:function(p){var lon=p.x;var lat=p.y;var delta_lon=Proj4js.common.adjust_lon(lon-this.long0);var theta=lat;var con=Proj4js.common.PI*Math.sin(lat);for(var i=0;true;i++){var delta_theta=-(theta+Math.sin(theta)-con)/(1.0+Math.cos(theta));theta+=delta_theta;if(Math.abs(delta_theta)<Proj4js.common.EPSLN)break;if(i>=50){Proj4js.reportError("moll:Fwd:IterationError");}}
-theta/=2.0;if(Proj4js.common.PI/2-Math.abs(lat)<Proj4js.common.EPSLN)delta_lon=0;var x=0.900316316158*this.a*delta_lon*Math.cos(theta)+this.x0;var y=1.4142135623731*this.a*Math.sin(theta)+this.y0;p.x=x;p.y=y;return p;},inverse:function(p){var theta;var arg;p.x-=this.x0;var arg=p.y/(1.4142135623731*this.a);if(Math.abs(arg)>0.999999999999)arg=0.999999999999;var theta=Math.asin(arg);var lon=Proj4js.common.adjust_lon(this.long0+(p.x/(0.900316316158*this.a*Math.cos(theta))));if(lon<(-Proj4js.common.PI))lon=-Proj4js.common.PI;if(lon>Proj4js.common.PI)lon=Proj4js.common.PI;arg=(2.0*theta+Math.sin(2.0*theta))/Proj4js.common.PI;if(Math.abs(arg)>1.0)arg=1.0;var lat=Math.asin(arg);p.x=lon;p.y=lat;return p;}};
\ No newline at end of file

Modified: trunk/templates/mapguide/standard/index.html
===================================================================
--- trunk/templates/mapguide/standard/index.html	2011-04-14 19:43:29 UTC (rev 2370)
+++ trunk/templates/mapguide/standard/index.html	2011-04-15 18:46:31 UTC (rev 2371)
@@ -9,7 +9,7 @@
 <!-- script src='http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.1'></script-->
 <!-- change the source of the following tag to point to your fusion installation -->
 <script type="text/javascript" src="../../../lib/fusion.js"></script>
-<link rel="stylesheet" href="themes/delicious/jxtheme.css" type="text/css" media="screen" charset="utf-8">
+<link rel="stylesheet" href="../../../lib/jxLib/themes/delicious/jxtheme.css" type="text/css" media="screen" charset="utf-8">
 <link rel="stylesheet" href="icons.css" type="text/css" media="screen" charset="utf-8">
 
 <style type="text/css">

Modified: trunk/templates/mapserver/standard/index.html
===================================================================
--- trunk/templates/mapserver/standard/index.html	2011-04-14 19:43:29 UTC (rev 2370)
+++ trunk/templates/mapserver/standard/index.html	2011-04-15 18:46:31 UTC (rev 2371)
@@ -8,7 +8,7 @@
  -->    
 <script type="text/javascript" src="../../../lib/fusion.js" charset="utf-8"></script>
 <script type="text/javascript" src="EPSG42304.js"></script>
-<link rel="stylesheet" href="themes/delicious/jxtheme.uncompressed.css" type="text/css" media="screen" charset="utf-8">
+<link rel="stylesheet" href="../../../lib/jxLib/themes/delicious/jxtheme.uncompressed.css" type="text/css" media="screen" charset="utf-8">
 <link rel="stylesheet" href="icons.css" type="text/css" media="screen" charset="utf-8">
 <style type="text/css">
     #Statusbar .spanCursorPosition,



More information about the fusion-commits mailing list