(function()
{
	Date.patterns.jControlsDate				= "d.m.Y";
	Date.patterns.jControlsTime				= "H:i:s";
	Date.patterns.jControlsTimeShort		= "H:i";
	Date.patterns.jControlsDateTime			= "d.m.Y H:i:s";
	Date.patterns.jControlsDateTimeShort	= "d.m.Y H:i";

	var REQUIRED_PROTOTYPE			= '1.6.0';	/* erwartete Prototype Version */
	var REQUIRED_PROTOTYPE_EXTENDED	= '1.0.1';	/* erwartete Prototype Ext Version */
	var REQUIRED_SCRIPTACULOUS		= '1.8.2';	/* erwartete Scriptaculous Version */

	var idCounter					= 0;		/* ID Counter */
	var containers					= $A([]);	/* Liste aller jControls */
	var loaded						= false;	/* gibt an ob das Fenster geladen ist oder nicht */

	var DATATYPES					= ["number", "time", "date", "datetime", "string", "ascii"];	/* alle datentype die unterstützt werden */
	var ONCLICK_IGNORE_ELEMENTTAG	= ["OPTION", "INPUT", "TEXTAREA", "SELECT", "BUTTON", "IMG"];	/* diese element Tags ignorieren und nicht auf onClick reagieren */

	var xTypes						= {			/* die verschiedenen xTypes */
		Button:				"button",
		Container:			"container",
		Control:			"control",
		Grid:				"grid",
		Layer:				"layer",
		Loading:			"loading",
		PageList:			"pagelist",
		Select:				"select",
		Tree:				"tree",
		NestedSetContainer:	"nestedSet",
		Calendar:			"calendar",
		unkown:				undefined
	};

	/**
	 * konvertiert versions-nummern-string
	 *
	 * @param string versionString
	 * @access private
	 * @retrun array
	 */
	var convertVersionString = function(versionString)
	{
		var r = versionString.split('.');
		return parseInt(r[0]) * 100000 + parseInt(r[1]) * 1000 + parseInt(r[2]);
	};

	/* Prüfen nach Prototype und korrekter Version */
	if ( typeof Prototype		== "undefined" ||
		 typeof Element			== "undefined" ||
	   	 typeof Element.Methods	== "undefined" ||
		 convertVersionString(Prototype.Version) < convertVersionString(REQUIRED_PROTOTYPE)
		)
	{
		throw("jControls requires the Prototype JavaScript framework >= " + REQUIRED_PROTOTYPE);
	}
	/* Prüfen nach Scriptaculous und korreter Version */
	if ( typeof Scriptaculous	== "undefined" ||
		 convertVersionString(Scriptaculous.Version) < convertVersionString(REQUIRED_SCRIPTACULOUS)
		)
	{
		throw("jControls requires the Prototype JavaScript framework >= " + REQUIRED_PROTOTYPE);
	}
	/* Prüfen nach Prototype Ext und korrekter Version */
	if ( typeof Prototype.VersionExtended == "undefined"	||
		 convertVersionString(Prototype.VersionExtended) < convertVersionString(REQUIRED_PROTOTYPE_EXTENDED)
		)
	{
		throw("jControls requires the Prototype Extended JavaScript framework >= " + REQUIRED_PROTOTYPE_EXTENDED);
	}

	/**
	 * etwas nach dem Dom load laden bzw ausführen
	 *
	 * @param function fn
	 * @access private
	 */
	var autoload = function(fn)
	{
		if (!loaded)
		{
			document.observe("dom:loaded", function()
			{
				loaded = true;
				fn();
			});
		}
		else
		{
			fn.bind(window)();
		}
	};
	/* info durchschleifen, wenn DOM geladen ist */
	autoload(Prototype.emptyFunction);

	/**
	 * konvertiert einen Typ in den entsprechenden Datentyp
	 *
	 * @param string value
	 * @param string dataType (DATATYPES)
	 * @access private
	 * @return mixed
	 */
	var convertToDataType = function(value, dataType)
	{
		if (!Object.isString(value))
		{
			value = String(value);
		}

		switch ((dataType || "string").toLowerCase())
		{
			case "number":
				return value.strip().toNumber(jControls.FORMAT.NUMBER_DEC_POINT, jControls.FORMAT.NUMBER_THOUSANDS_SEP);
				break;

			case "date":
				return Date.parseDate(value).getTime();
				break;

			case "time":
				return Date.parseDate(value).getTime();
				break;

			case "datetime":
				return Date.parseDate(value).getTime();
				break;

			case "ascii":
				return value;
				break;

			case "string":
			default:
				return value.toLowerCase();
				break;
		}
	};

	/**
	 * liefert eine neue standardisierte ID egal für was
	 *
	 * @access public
	 * @return string
	 */
	var getId = function()
	{
		idCounter++;
		return "jControl" + idCounter;
	};

	/**
	 * registriert ein Container
	 *
	 * @param jControls.* container
	 * @access protected
	 * @return jControls.*
	 */
	var register = function(container)
	{
		containers.push(container);

		return containers;
	};

	/**
	 * registriert ein Container
	 *
	 * @param jControls.* container
	 * @access protected
	 * @return jControls.*
	 */
	var unregister = function(container)
	{
		containers = containers.findAll(function(c)
		{
			return c != container;
		});
	};

	/****************************************************************************************************
	 *																									*
	 *											JCONTROLS												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * @author Marco Starker <marco.starker@sapodo.com>
	 *
	 * Sammlung diverse Steuerelement für javascript
	 */
	window.jControls = {
		/**
		 * jControls Version
		 */
		Version: '1.1.0',

		/**
		 * formatierung für die Zahighlighten
		 */
		FORMAT:		{
			NUMBER_DEC_POINT		: ".",
			NUMBER_THOUSANDS_SEP	: ","
		},

		/**
		 * enthält diverse URLs
		 */
		url:	{
			/**
			 * CSS URL
			 *
			 * @var string
			 * @access protected
			 */
			css:	"",

			/**
			 * Bild URL
			 *
			 * @var string
			 * @access protected
			 */
			img:	"",
			/**
			 * Bild welches 1x1 ist
			 *
			 * @var string
			 * @access protected
			 */
			imgS:	""
		},

		/**
		 * globales xType zum prüfen ob das obj ein jControl.* ist
		 */
		xType: "jcontrol",

		/**
		 * liefert das jControls zu einem DOM
		 *
		 * @param string|node element
		 * @access public
		 * @return jControls.*
		 */
		$: function(element)
		{
			var container = $(element);

			if (!container && Object.isString(element))
			{
				container = containers.find(function(c)
				{
					return c.id == element;
				});
			}
			else if (container && !Object.isUndefined(container._jControl))
			{
				container = container._jControl;
			}
			else if (container && Object.isUndefined(container._jControl))
			{
				container = null;
			}

			return (container ? container : null);
		},

		/**
		 * gibt ggf debugmeldungen aus funktioniert nur mit FF und FireBug
		 *
		 * @param mixed notice
		 * @access public
		 */
		$d: function(notice)
		{
			if (Prototype.Browser.isFF)
			{
				console.info(notice);
			}
			else if (Prototype.Browser.isSafari)
			{
				console.log(notice);
			}
			else if (Prototype.Browser.isOpera)
			{
				opera.postError(notice);
			}
			else if (Prototype.Browser.isIE8 && window.console)
			{
				if (typeof notice == "object")
				{
					console.log(Object.toJSON(notice));
				}
				else
				{
					console.log(notice);
				}
			}
			else
			{
				if (!loaded)
				{
					autoload(function()
					{
						jControls.$d(notice);
					});

					return;
				}

				if (!$("jControls_console"))
				{
					Element.insert(document.body,
					{
						top:
							"<div id='jControls_console' style='position:absolute;left:0px;height:150px;width:100%;bottom:0px;z-index:100000;overflow:auto;overflow-x:hidden;overflow-y:auto;border-top:1px solid #A06060;background-color:#FFD0D0;font-size:10px;font-family:tahoma,arial,helvetica,sans-serif;'>" +
								"<div style='font-weight:bold;padding:1px;margin-bottom: 2px;border-bottom:1px solid #858484;'>Console</div>" +
							"</div>"
					});
				}
				$("jControls_console").insertBottom(Object.toHTML(notice) + "<br />");
			}
		}
	};

	/* jControls css suchen */
	jControls.url.css = $A(document.getElementsByTagName("link")).find(function(s)
	{
		return (s.rel && s.rel == "stylesheet" && s.type && s.type == "text/css" && s.href && s.href.match( /jControls\.css(\?.*)?$/ ));
	}).href.replace( /jControls\.css(\?.*)?$/ , '');

	/* auf basis vom CSS den Image Pfad */
	jControls.url.img = jControls.url.css + "images/";
	/* auf basis vom Image Pfade das 1x1 Pic */
	jControls.url.imgS = jControls.url.img + "s.gif";

	/****************************************************************************************************
	 *																									*
	 *												COOKIES												*
	 *																									*
	 ****************************************************************************************************/

	var cookies = null;

	/**
	 * lädt alle vorhandenen Cookies
	 */
	var loadCookies = function()
	{
		cookies = new Array();
		var c = document.cookie + ";";
		var re = /\s?(.*?)=(.*?);/g;
		var matches;
		while ((matches = re.exec(c)) != null)
		{
			var name	= matches[1];
			var value	= matches[2];
			if (name && name.startsWith("jcontrols-"))
			{
				cookies.push(new jControls.Cookies.Cookie(
				{
					name:	name.substr("jcontrols-".length),
					value:	value
				}));
			}
		}
		return cookies;
	};

	/**
	 * Wrapper funktion für Cookie zugriff
	 *
	 * @param string name Name des Cookie
	 * @return mixed Value
	 * @example alert(jControls.Cookies('meinCookie').get());
	 */
	jControls.Cookies = function(name)
	{
		if (cookies == null) loadCookies();

		var r = cookies.find(function(cookie)
		{
			return cookie.name == name;
		});

		if (!r) {
			r = new jControls.Cookies.Cookie(
			{
				name:	name,
				value:	Object.toJSON(null)
			});

			cookies.push(r);
		}

		return r;
	};

	/**
	 * ein cookie
	 */
	jControls.Cookies.Cookie = Class.create(
	{
		domain:		null,
		expires:	new Date((new Date()).getTime() + 7 * 24 * 60  * 60 * 1000),
		name:		null,
		path:		"/",
		secure:		false,
		value:		null,

		/**
		 * konstruktor
		 */
		initialize: function(options)
		{
			this.name	= options.name;
			this.value	= this.decodeValue(options.value);
		},

		/**
		 * wert dekodieren
		 */
		decodeValue: function(value)
		{
			try
			{
				return value.evalJSON(true);
			}
			catch(e)
			{
				return null;
			}
		},

		/**
		 * wert kodieren
		 */
		encodeValue: function()
		{
			return Object.toJSON(this.value);
		},

		/**
		 * liefert den Cookie Wert
		 */
		get: function()
		{
			return this.value;
		},

		/**
		 * setzt den Cookie wert
		 */
		set: function(value)
		{
			if (typeof value == "undefined" || value === null)
			{
				this.value = null;
				this.clear(this.name);
				return;
			}

			this.value = value;
			this.save();
			return this;
		},

		/**
		 * speichert das cookie
		 */
		save: function()
		{
			document.cookie = "jcontrols-" + this.name + "=" + this.encodeValue(this.value) +
								(this.expires == null	? "" : "; expires=" + this.expires.toGMTString()) +
								(this.path == null		? "" : "; path=" + this.path) +
								(this.domain == null	? "" : "; domain=" + this.domain) +
								(this.secure == false	? "" : "; secure");
		},

		/**
		 * löscht das cookie
		 */
		clear: function()
		{
			document.cookie = "jcontrols-" + this.name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
								(this.path == null		? "" : "; path=" + this.path) +
								(this.domain == null	? "" : "; domain=" + this.domain) +
								(this.secure == false	? "" : "; secure");
		}
	});

	/****************************************************************************************************
	 *																									*
	 *												AJAX												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * jControls Ajax Schnittstelle damit alle funktione über eine stelle laufen
	 */
	jControls.Ajax =
	{
		FUNCTION:
		{
			REQUEST:	function(url, options) { return new Ajax.Request(url, options); }
		},

		/**
		 * standard einstellungen für jede Ajax Kommunikation
		 *
		 * @var object
		 * @access public
		 */
		DEFAULT:
		{
			OPTIONS:
			{
				url:			"/",
				method:			"post",
				loader:			true
			},

			PARAMETERS:
			{
				ajax:		1,
				jControls:	1
			}
		},

		/**
		 * Requesterfunktion see Prototype / Ajax.Request
		 *
		 * @param jControls.* container
		 * @param object options (die options ist identisch mit Prototype.Ajax.Request und zusätzlich optional noch den key "url")
		 * @access public
		 * @return Ajax.Request
		 */
		Request: function(jControl, options)
		{
			options = Object.extend(options || {}, jControls.Ajax.DEFAULT.OPTIONS);
			if (Object.isString(options.parameters))
			{
				options.parameters = options.parameters.toQueryParams();
			}
			options.parameters = Object.extend(options.parameters || {}, jControls.Ajax.DEFAULT.PARAMETERS);

			var on =
			{
				create:		options.onCreate	|| Prototype.emptyFunction,
				complete:	options.onComplete	|| Prototype.emptyFunction,
				success:	options.onSuccess	|| Prototype.emptyFunction
			};

			Object.extend(options || {},
			{
				onCreate: function (transport)
				{
					if (options.loader)
					{
						if (!jControl._jcLoading)
						{
							jControl._jcLoading = new jControls.Loading({renderTo: jControl.dom});
						}
						jControl._jcLoading.show();
					}
					on.create(transport);
				},

				onComplete: function (transport)
				{
					on.complete(transport);
					if (jControl._jcLoading)
					{
						jControl._jcLoading.hide();
					}
				},

				onSuccess: function(transport)
				{
					/* noch kein JSON */
					if (typeof transport.responseJSON != "undefined")
					{
						on.success(transport.responseJSON);
					}
					/* JSON dann weiterleiten */
					else
					{
						on.success(transport);
					}
				}

			});

			return jControls.Ajax.FUNCTION.REQUEST(options.url, options);
		}
	};

	/****************************************************************************************************
	 *																									*
	 *										CONTAINER BASIS												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * Container Klasse
	 *
	 * Container Class options
	 *		name						type				default		description
	 *		=====================================================================================================================
	 *		applyTo			optional	string|element					in dieses Element einfügen und den inhalt überschreiben
	 *		attributes		optional	object							zusätzliche DOM Node Attributes
	 *		cls				optional	string				""			zusätzliche Classes für den Container
	 *		html			optional	string				""			HTML Content der eingefügt werden soll in das Element
	 *		id				optional	string							eine ID für dieses element
	 *		listeners		optional	object							Object von verschiedenen DOM Event Listeners
	 *		renderAfter		optional	string|element					zu diesem Element ans Ende anfügen
	 *		renderTo		optional	string|element					zu diesem Element ans Ende anfügen
	 *		renderTop		optional	string|element					zu diesem Element an Anfang anfügen
	 *		renderReplace	optional	string|element					das angegebene Element mit diesem ersetzen
	 *		qtip			optional	jControls.QuickTip.options		einen entsprechende Quicktip für den SPaltenkopf anzeigen
	 *		style			optional	string|object		""			zusätzliche styles
	 *		type			optional	string				div			element-Typ des DOM Node
	 *
	 * Rangordnung der render options
	 *		1.	applyTo
	 *		2.	renderAfter
	 *		3.	renderTop
	 *		4.	renderReplace
	 *		5.	renderTo
	 *
	 * options.listeners (die Keys des Objectes sind die Event Names)
	 *		name					type				default		description
	 *		=====================================================================================================================
	 *		scope		optional	object							der Scope der Funktion
	 *		fn						function						Callbackfunction
	 */
	jControls.Container = Class.create(
	{
		/**
		 * standard insert funktion
		 *
		 * @var function
		 * @access private
		 */
		_insertFn: function(renderTo, element)
		{
			Element.insert(renderTo,
			{
				bottom: element
			});
		},

		/**
		 * der zugehörige DOM Node
		 *
		 * @var node
		 * @access protected
		 */
		dom:	null,

		/**
		 * die Id des Containers
		 *
		 * @var string
		 * @access protected
		 */
		id:		null,

		/**
		 * die zugehörigen initialoptionen
		 *
		 * @var object
		 * @access private
		 */
		options:	null,

		/**
		 * element wurde gezeichnet
		 *
		 * @var bool
		 * @access protected
		 */
		rendered:	false,

		/**
		 * xTyp des Objektes
		 *
		 * @access proctected
		 */
		xType:		xTypes.unkown,

		/**
		 * Org Style Height des Containers, wenn noch nichts reingerendert wurde
		 *
		 * @var string
		 * @access private
		 */
		_domHeightOrg: "",

		/**
		 * in das element rendern und inhalt ersetzen
		 *
		 * @param string|node|jControl applyTo
		 * @access public
		 * @return this
		 */
		applyTo: function(applyTo)
		{
			this._insertFn = function(renderTo, element)
			{
				renderTo.removeChildrens();
				renderTo.innerHTML = "";
				Element.insert(renderTo,
				{
					bottom:element
				});
			};
			return this.render(applyTo);
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function(options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Container.xType;
			}

			this.options = options;
			this.id = (typeof this.options.id != "undefined" ? this.options.id : getId());

			/* einfügen durch ersetzen */
			if (this.options.applyTo)
			{
				this.applyTo(this.options.applyTo);
			}
			/* einfügen nach diesem element */
			else if (this.options.renderAfter)
			{
				this.renderAfter(this.options.renderAfter);
			}
			/* einfügen am anfang des elementes */
			else if (this.options.renderTop)
			{
				this.renderTop(this.options.renderTop);
			}
			/* element ersetzen */
			else if (this.options.renderReplace)
			{
				this.renderReplace(this.options.renderReplace);
			}
			/* einfügen ans ende des elementes */
			else if (this.options.renderTo)
			{
				this.render(this.options.renderTo);
			}
		},

		/**
		 * zerstört das kontroll
		 */
		remove: function()
		{
			this.dom.remove();
			this.dom._jControl = undefined;
		},

		/**
		 * rendert den container
		 *
		 * @params node|string|jControls.* renderTo
		 * @access public
		 * @return this
		 */
		render: function(renderTo)
		{
			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			this.dom = $(document.createElement(this.options.type ? this.options.type.toUpperCase() : "DIV"));
			this.dom._jControl	= this;
			this.dom.id			= this.id;
			this.dom.addClass("jcContainer jcxType-" + this.xType + " " + (this.options.cls || ""));

			if (this.options.qtip)
			{
				this.dom.title = this.options.qtip;
			}
			if (this.options.html)
			{
				this.dom.innerHTML = this.options.html;
			}
			if (this.options.attributes)
			{
				this.dom.writeAttribute(this.options.attributes);
			}
			if (this.options.style)
			{
				this.dom.setStyle(this.options.style);
			}
			if (this.options.listeners)
			{
				this.dom.on(this.options.listeners);
			}

			/* einfügen */
			this._insertFn(renderTo, this.dom);

			/* mit IE müssen wir direkt draufgehen, da prototyp offsetHeight dann liefert und das ist mit line-heiht immer != 0 */
			if (Prototype.Browser.isIE)
			{
				this._domHeightOrg = this.dom.currentStyle["height"];
				if (this._domHeightOrg == "auto")
				{
					this._domHeightOrg = "0px";
				}

			}
			/* alle anderen gehn so */
			else
			{
				this._domHeightOrg = this.dom.getStyle("height");
			}

			this.rendered = true;

			return this;
		},

		/**
		 * rendert das element nach einem bestimmten element
		 *
		 * @param string|node|jControl renderAfter
		 * @access public
		 * @return this
		 */
		renderAfter: function(renderAfter)
		{
			this._insertFn = function(renderTo, element)
			{
				Element.insert(renderTo,
				{
					after:element
				});
			};
			return this.render(renderAfter);
		},

		/**
		 * ersetzt das angegebene element durch dieses control
		 *
		 * @param string|node|jControl renderReplace
		 * @access public
		 * return this
		 */
		renderReplace: function(renderReplace)
		{
			this._insertFn = function(renderTo, element)
			{
				Element.insert(renderTo,
				{
					after:element
				});
				Element.remove(renderTo);
			};
			return this.render(renderReplace);
		},

		/**
		 * anfügen das angegebene element durch dieses control
		 *
		 * @param string|node|jControl renderReplace
		 * @access public
		 * return this
		 */
		renderTo: function(renderTo)
		{
			this._insertFn = function(renderTo, element)
			{
				Element.insert(renderTo,
				{
					bottom: element
				});
			};
			return this.render(renderTo);
		},

		/**
		 * rendert das control in das element am anfang
		 *
		 * @param string|node|jControl renderTop
		 * @access public
		 * @return this
		 */
		renderTop: function(renderTop)
		{
			this._insertFn = function(renderTo, element)
			{
				Element.insert(renderTo,
				{
					top:element
				});
			};
			return this.render(renderTop);
		}
	});
	/**
	 * xTyp
	 */
	jControls.Container.xType = xTypes.Container;

	/****************************************************************************************************
	 *																									*
	 *												LAYER												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * Layer Displayer
	 *
	 * options
	 *		name						type			default							description
	 *		=====================================================================================================================
	 *		content			optional	string|element									Content Element was immer mit angepasst wird
	 *		contentPosition	optional	string			centerUp
	 *		opacity			optional	float			0.8
	 *
	 * Default Event Names
	 *		deklaration											result type		description
	 *		=====================================================================================================================
	 *		onAfterShow(this)
	 *		onBeforeShow(this)									bool			bei return === false wird abgebrochen
	 */
	jControls.Layer = Class.create(jControls.Container,
	{
		/**
		 * verstecken
		 *
		 * @access public
		 * @return this;
		 */
		hide: function()
		{
			var finishFn = (function()
			{
				this.dom.hide();
				this.dom.setOpacity(0);
				if (Prototype.Browser.isIE6)
				{
					$$("select.layer-hide-ie6").invoke("setVisible", true);
				}
			}).bind(this);

			if (this.effect && this.effect.cancel)
			{
				this.effect.cancel();
			}

			if (this.content)
			{
				this.effect = new Effect.Parallel([
					new Effect.Fade(this.dom,
					{
						to:				0,
						sync:			true,
						afterFinish:	finishFn
					}),
					new Effect.Fade(this.content,
					{
						to:				0,
						sync:			true,
						afterFinish:	(function()
						{
							this.content.setOpacity(0);
						}).bind(this)
					})
				],
				{
					duration: 0.5
				});
			}
			else
			{
				this.effect = new Effect.Fade(this.dom,
				{
					duration:		0.5,
					to:				0,
					afterFinish:	finishFn
				});
			}

			return this;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Layer.xType;
			}

			options.opacity = !Object.isUndefined(options.opacity) ? options.opacity : 0.8;

			if (this.listeners == null)
			{
				this.listeners = options.listeners || {};
			}

			this.content = $(options.content);
			options.contentPosition = options.contentPosition || "centerUp";

			$super(options);
		},

		/**
		 * rendert
		 *
		 * @param node|string|jControls.* renderTo zu diesem Element ans Ende rendern
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			this.options.style = this.options.style || {};
			this.options.style.display = "none";
			$super(document.body);

			this.dom.addClass("jcLayer" + (Prototype.Browser.isIE6 ? " jIE6" : ""));
			this.dom.setOpacity(0);

			if (this.content)
			{
				this.content.hide().setOpacity(0);
			}

			if (renderTo == document.body)
			{
				this.sizes = this.sizes.bind(this, document);
				if (!Prototype.Browser.isIE6)
				{
					this.dom.setStyle(
					{
						position:	"fixed"
					});

					if (this.content)
					{
						this.content.setStyle(
						{
							position:	"fixed"
						});
					}
				}
				else
				{
					Event.observe(document, "scroll", this.sizes);
				}
				Event.observe(document, "resize", this.sizes);
			}
			else
			{
				this.sizes = this.sizes.bind(this, renderTo);
				Event.observe(renderTo, "resize", this.sizes);
				Event.observe(document, "scroll", this.sizes);
			}

			return this;
		},

		/**
		 * anzeigen
		 *
		 * @access public
		 * @return this
		 */
		show: function()
		{
			if (this.listeners.onBeforeShow && this.listeners.onBeforeShow(this) === false)
			{
				return this;
			}

			if (Prototype.Browser.isIE6)
			{
				$$("select").each(function(element)
				{
					if (element.isDisplayed())
					{
						element.addClass("layer-hide-ie6").setVisible(false);
					}
				});
			}

			if (this.effect && this.effect.cancel)
			{
				this.effect.cancel();
			}

			this.dom.show();
			if (this.content)
			{
				this.content.show();
			}

			this.sizes();

			if (this.content)
			{

				this.effect = new Effect.Parallel([
					new Effect.Appear(this.dom,
					{
						to:				this.options.opacity,
						sync:			true
					}),
					new Effect.Appear(this.content,
					{
						to:		1,
						sync:	true
					})
				],
				{
					duration:		0.5,
					afterFinish:	(function()
					{
						if (this.listeners.onAfterShow)
						{
							this.listeners.onAfterShow(this);
						}
					}).bind(this)
				});
			}
			else
			{
				this.effect = new Effect.Appear(this.dom,
				{
					duration:		0.5,
					to:				this.options.opacity,
					afterFinish:	(function()
					{
						if (this.listeners.onAfterShow)
						{
							this.listeners.onAfterShow(this);
						}
					}).bind(this)
				});
			}

			return this;
		},

		/**
		 * größe und so
		 *
		 * @param element element
		 * @access private
		 * @return this
		 */
		sizes: function(element)
		{
			if (!this.rendered)
			{
				return this;
			}

			var scrollOffset	= document.viewport.getScrollOffsets();
			var viewport		= document.viewport.getDimensions();
			var contentBox		= null;

			if (this.content)
			{
				contentBox	= this.content.getSize();
			}

			viewport.x			= scrollOffset.left;
			viewport.y			= scrollOffset.top;
			viewport.right		= viewport.x + viewport.width;
			viewport.bottom		= viewport.y + viewport.height;

			/* über alles */
			if (element == document)
			{
				/* is fixed */
				this.dom.setStyle(
				{
					width:	viewport.width + "px",
					height:	viewport.height + "px"
				});
				if (this.content && this.options.contentPosition != "none")
				{
					this.content.setStyle(
					{
						left:	(viewport.width / 2 - contentBox.width / 2) + "px",
						top:	(viewport.height / 2 - contentBox.height / (this.options.contentPosition == "center" ? 2 : 1)) + "px"
					});
				}
			}
			/* in einem element */
			else
			{
				var elementBox = element.getBox();

				this.dom.setBox(elementBox);

				if (this.content && this.options.contentPosition != "none")
				{
					var left = (elementBox.width / 2 - contentBox.width / 2);
					var top = (elementBox.height / 2 - contentBox.height / 2);

					/* element zum teil ausserhalb des viewport */
					if (!(
						(viewport.x <= elementBox.x && viewport.y <= elementBox.y && elementBox.right <= viewport.right && elementBox.bottom <= viewport.bottom)	|| /* 100 % drin in viewport */
						(viewport.right < elementBox.x && viewport.bottom < elementBox.y) || /* rechts und unten außerhalb des viewport */
						(elementBox.right < viewport.x && elementBox.bottom < viewport.y) /* links und oben außerhalb des viewports */
					))
					{
						elementBox.left		= viewport.x < elementBox.x ? elementBox.x : viewport.x;
						elementBox.top		= viewport.y < elementBox.y ? elementBox.y : viewport.y;
						elementBox.right	= elementBox.right < viewport.right ? elementBox.right : viewport.right;
						elementBox.bottom	= elementBox.bottom < viewport.bottom ? elementBox.bottom : viewport.bottom;

						left = (elementBox.left - elementBox.x + (elementBox.right - elementBox.left) / 2 - contentBox.width / 2);
						top = (elementBox.top - elementBox.y + (elementBox.bottom - elementBox.top) / 2 - contentBox.height / 2);

						if (left < 0)
						{
							left = 0;
						}
						else if (left + contentBox.width > elementBox.width)
						{
							left = elementBox.width - contentBox.width;
						}
						if (top < 0)
						{
							top = 0;
						}
						else if (top + contentBox.height > elementBox.height)
						{
							top = elementBox.height - contentBox.height;
						}
					}

					this.content.setStyle(
					{
						left:	(elementBox.x + left) + "px",
						top:	(elementBox.y + top) + "px"
					});
				}
			}

			return this;
		}
	});
	/**
	 * xTyp
	 */
	jControls.Layer.xType = xTypes.Layer;

	/****************************************************************************************************
	 *																									*
	 *												LOADING												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * Loading Displayer
	 *
	 *		name					type		default							description
	 *		=====================================================================================================================
	 *		opacityMax	optional	float		0.8
	 *		text		optional	string		jControls.Loading.DEFAULTTEXT	lade text
	 *
	 * Default Event Names
	 *		deklaration											result type		description
	 *		=====================================================================================================================
	 *		onAfterShow(this)
	 *		onBeforeShow(this)									bool			bei return === false wird abgebrochen
	 */
	jControls.Loading = Class.create(jControls.Container,
	{
		/**
		 * dom node overlay
		 *
		 * @var node
		 * @access private
		 */
		layer: null,

		/**
		 * maximaler Opacity Wert
		 *
		 * @var float
		 * @access private
		 */
		opacityMax:	0.8,

		/**
		 * overlay text
		 *
		 * @var string
		 * @access private
		 */
		text: null,

		/**
		 * verstecken
		 *
		 * @access public
		 * @return this;
		 */
		hide: function()
		{
			this.layer.hide();

			return this;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Loading.xType;
			}

			options.text = options.text || jControls.Loading.DEFAULTTEXT;

			this.opacityMax = options.opacityMax || this.opacityMax;

			$super(options);
		},

		/**
		 * rendert
		 *
		 * @param node|string|jControls.* renderTo zu diesem Element ans Ende rendern
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			var effect = null;

			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			this.options.style			= this.options.style		|| {};
			this.options.listeners		= this.options.listeners	|| {};
			this.options.style.display	= "none";
			$super(document.body);

			this.dom.addClass("jcLoading jcLoading-info" + (Prototype.Browser.isIE6 ? " IE6" : ""));

			Element.insert(this.dom,
			{
				bottom:
					"<div class='jcLoading-background-ml'>" +
						"<div class='jcLoading-background-mr'>" +
							"<div class='jcLoading-background-mc'>" +
								"<div class='jcLoading-animation'></div>" +
								"<div class='jcLoading-text'>" + this.options.text + "</div>" +
							"</div>" +
						"</div>" +
					"</div>"
			});

			this.text	= this.dom.down(".jcLoading-text");
			this.layer	= new jControls.Layer(
			{
				renderTo:	renderTo,
				opacity:	this.opacityMax,
				content:	this.dom,
				listeners:	this.options.listeners
			});

			return this;
		},

		/**
		 * text setzen
		 *
		 * @param string text
		 * @access public
		 * @return this
		 */
		setText: function(text)
		{
			this.dom.clearStyle("width");
			this.text.innerHTML = text;
			this.layer.sizes();

			return this;
		},

		/**
		 * anzeigen
		 *
		 * @param string text
		 * @access public
		 * @return this
		 */
		show: function(text)
		{
			if (this.text)
			{
				this.setText(text || this.options.text || jControls.Loading.DEFAULTTEXT);
			}

			this.layer.show();

			return this;
		}
	});
	/**
	 * xTyp
	 */
	jControls.Loading.xType = xTypes.Loading;
	/**
	 * Standard lade text
	 */
	jControls.Loading.DEFAULTTEXT = "Please wait...";

	/****************************************************************************************************
	 *																									*
	 *											CONTROL BASIS											*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * Basisclass für ein Steuerelement
	 *
	 * Basisclass options (geerbte options von jControls.Container siehe oben)
	 *		name							type	default						description
	 *		====================================================================================================================
	 *		buttons				optional	array								array of jControls.Button options (welche in eigene Toolbar am Bottom gerendert werden)
	 *		closeable			optional	bool	false						element kann via button im title geschlossen werden (erzwingt top panel)
	 *		collapsable			optional	bool	false						wurde collapsed oder expanded angegeben dann true ansonsten immer ausgeklappt (erzwingt top panel)
	 *		collapsed			optional	bool	false						eingeklappt
	 *		expanded			optional	bool	false						ausgeklappt
	 *		enabled				optional	bool	true						aktiviert
	 *		enterable			optional	bool	true						eingebbar
	 *		forceCollapsedState	optional	bool	false						Ein/Ausgeklappt Status erzwingen
	 *		id					optional	string								Elementid
	 *		listeners			optional	object								Events für die Controls
	 *		moveable			optional	bool	false						element kann via titel bewegt werden (erzwingt top panel)
	 *		name				optional	string								element name
	 *		pinable				optional	bool	false						kann angepinnt und nur mit moveable mgl
	 *		pinned				optional	bool	true						ist angepinnt aber nur mit pinable
	 *		title				optional	string								es wird ein allgemeiner titel über das element gezeichnet (erzwingt top panel)
	 *		value				optional	mixed								diesen wert setzen
	 *
	 * Default Event Names
	 *	deklaration											result type		description
	 *	=====================================================================================================================
	 *	onAfterClick(this)
	 *	onAfterCollapse(this)
	 *	onAfterExpand(this)
	 *	onAfterMove(this, boxNew, boxOld)
	 *	onAfterRender(this)
	 *	onBeforeClick(this)									bool			bei return === false wird abgebrochen
	 *	onBeforeChange(this, oldValue, newValue)			bool			bei return === false wird abgebrochen
	 *	onBeforeCollapse(this)								bool			bei return === false wird abgebrochen
	 *	onBeforeExpand(this)								bool			bei return === false wird abgebrochen
	 *	onBeforeMove(this)									bool			bei return === false wird abgebrochen
	 *	onBeforeRender(this)								bool			bei return === false wird abgebrochen
	 *	onChange(this, value, initialized)
	 *	onClose(this)
	 *	onEnableStateChange(this, state)
	 *	onMouseMove(event, this)
	 *	onMouseOver(this)
	 *	onMouseOut(this)
	 *	onMove(this, boxNew, boxOld)						bool			bei return === false wird abgebrochen
	 */
	jControls.Control = Class.create(jControls.Container,
	{
		/**
		 * Control ist schliessbar
		 */
		closeable: false,

		/**
		 * aus und einklappbar
		 *
		 * @var bool
		 * @access protected
		 */
		collapsable: false,

		/**
		 * zusammengeklappt
		 *
		 * @var bool
		 * @access protected
		 */
		collapsed: false,

		/**
		 * aktiviert ?
		 *
		 * @var bool
		 * @access protected
		 */
		enabled: true,

		/**
		 * eingebbar
		 *
		 * @var bool
		 * @access protected
		 */
		enterable: true,

		/**
		 * ausgeklappt
		 *
		 * @var bool
		 * @access protected
		 */
		expanded: true,

		/**
		 * einzufügendes HTML
		 *
		 * @var string
		 * @access private
		 */
		html: "",

		/**
		 * control ist initialisiert
		 *
		 * @var bool
		 * @access public
		 */
		initialized: false,

		/**
		 * das input element welches die werte aufnimmt
		 *
		 * @var object
		 * @access public
		 */
		inputContainer: null,

		/**
		 * event listeners für die controls
		 *
		 * @var object
		 * @access private
		 */
		listeners:	null,

		/**
		 * control ist moveable
		 *
		 * @var bool
		 * @access protected
		 */
		moveable:	false,

		/**
		 * der Form Name des Elementes
		 *
		 * @var string
		 * @access protected
		 */
		name: null,

		/**
		 * kann angepinnt werden
		 *
		 * @var bool
		 * @access protected
		 */
		pinable: false,

		/**
		 * ist angepinnt
		 *
		 * @var bool
		 * @access protected
		 */
		pinned: true,

		/**
		 * titel
		 *
		 * @var string
		 * @access protected
		 */
		title: null,

		/**
		 * der wert des Controls
		 *
		 * @var mixed
		 * @access protected
		 */
		value: null,

		/**
		 * Control einklappen
		 *
		 * @access public
		 * @return this;
		 */
		collapse: function()
		{
			if (!this.rendered || this.collapsed || !this.collapsable || (this.listeners.onBeforeCollapse && this.listeners.onBeforeCollapse(this) === false))
			{
				return this;
			}

			var style = this.dom.readAttribute("style");
			this.dom._expandHeightClear		= (style != null && style.indexOf("height") != -1 ? false : true);
			this.dom._expandHeight			= this.dom.getStyle("height");

			new Effect.Morph(this.dom,
			{
				duration:	0.4,
				style:
				{
					height:	this.dom.down(".jcControl-panel").getHeight() + "px"
				},
				afterFinish: (function()
				{
					if (this.listeners.onAfterCollapse)
					{
						this.listeners.onAfterCollapse.defer(this);
					}
				}).bind(this)
			});

			this.collapsed	= true;
			this.expanded	= false;
			this.setCookie();

			var btnNode = this.dom.down(".jcTool-button-collapsable");
			if (btnNode.hasClassName("jcTool-collapse-north"))
			{
				btnNode.removeClass("jcTool-collapse-north").addClass("jcTool-expand-north");
			}

			return this;
		},

		/**
		 * Control ausklappen
		 *
		 * @access public
		 * @return this;
		 */
		expand: function()
		{
			if (!this.rendered || this.expanded || !this.collapsable || !this.dom._expandHeight || (this.listeners.onBeforeExpand && this.listeners.onBeforeExpand(this) === false))
			{
				return this;
			}

			new Effect.Morph(this.dom,
			{
				duration:	0.4,
				style:
				{
					height:	this.dom._expandHeight
				},
				afterFinish:	(function()
				{
					if (this.dom._expandHeightClear)
					{
						this.dom.clearStyle("height");
					}
					if (this.listeners.onAfterExpand)
					{
						this.listeners.onAfterExpand.defer(this);
					}
				}).bind(this)
			});

			this.collapsed	= false;
			this.expanded	= true;
			this.setCookie();

			var btnNode = this.dom.down(".jcTool-button-collapsable");
			if (btnNode.hasClassName("jcTool-expand-north"))
			{
				btnNode.removeClass("jcTool-expand-north").addClass("jcTool-collapse-north");
			}

			return this;
		},

		/**
		 * liefert die Cookie daten
		 *
		 * @access public
		 * @return object
		 */
		getCookie: function(options)
		{
			var cookie = jControls.Cookies(this.id).get() || {};

			if (typeof cookie.expanded != "undefined")
			{
				cookie.expanded = (this.collapsable ? cookie.expanded  : undefined);
			}
			if (typeof cookie.pinned != "undefined")
			{
				cookie.pinned = this.pinned;
			}

			return cookie;
		},

		/**
		 * liefert den aktuellen Wert
		 *
		 * @access public
		 * @return mixed
		 */
		getValue: function()
		{
			return this.value;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Control.xType;
			}

			/* listeners für controls hier abfangen, die gehen nicht direkt auf dom */
			if (this.listeners == null)
			{
				this.listeners = options.listeners || {};
			}
			register(this);

			/* Listeners fürs DomNode */
			if (this.listeners.onMouseOver)
			{
				options.listeners.mouseover =
				{
					scope:	this,
					fn:		function(e)
					{
						if (this.dom._pe)
						{
							this.dom._pe.stop();
						}
						this.dom._pe = new PeriodicalExecuter((function()
						{
							var node = this.dom;
							node._pe.stop();
							this.listeners.onMouseOver(this);
						}).bind(this), 0.1);
					}
				};
			}

			if (this.listeners.onMouseOut)
			{
				options.listeners.mouseout =
				{
					scope:	this,
					fn:		function(e)
					{
						if (this.dom._pe)
						{
							this.dom._pe.stop();
						}
						this.dom._pe = new PeriodicalExecuter((function()
						{
							var node = this.dom;
							node._pe.stop();
							this.listeners.onMouseOut(this);
						}).bind(this), 0.1);
					}
				};
			}

			if (this.listeners.onMouseMove)
			{
				options.listeners.mousemove =
				{
					scope:	this,
					fn:		function(e)
					{
						this.listeners.onMouseMove(e, this);
					}
				};
			}

			if (this.listeners.onBeforeClick || this.listeners.onAfterClick)
			{
				options.listeners.click =
				{
					scope:	this,
					fn:		function(e)
					{
						if (this.listeners.onBeforeClick && this.listeners.onBeforeClick(this) === false)
						{
							return;
						}
						if (this.listeners.onAfterClick)
						{
							this.listeners.onAfterClick.defer(this);
						}
					}
				};
			}

			/* name sichern */
			this.name = options.name || this.id;
			options.name = undefined;

			/* html sichern */
			if (this.html == "")
			{
				this.html = options.html || "";
				options.html = undefined;
			}

			/* options */
			if (typeof options.enabled != "undefined")
			{
				this.enabled		= options.enabled;
			}
			if (typeof options.enterable != "undefined")
			{
				this.enterable		= options.enterable;
			}
			if (typeof options.closeable != "undefined")
			{
				this.closeable		= options.closeable;
			}
			if (typeof options.collapsable != "undefined")
			{
				this.collapsable	= options.collapsable;
			}
			else if (typeof options.expanded != "undefined" || typeof options.collapsed != "undefined")
			{
				this.collapsable = true;
			}
			if (typeof options.moveable != "undefined")
			{
				this.moveable		= options.moveable;
			}
			if (typeof options.pinable != "undefined")
			{
				this.pinable 		= options.pinable;
			}
			if (typeof options.pinned != "undefined")
			{
				this.pinned 		= options.pinned;
			}

			if (typeof options.expanded != "undefined" && typeof options.collapsed == "undefined")
			{
				options.collapsed	= !options.expanded;
			}
			else if (typeof options.expanded == "undefined" && typeof options.collapsed != "undefined")
			{
				options.expanded	= !options.collapsed;
			}
			if (!Object.isUndefined(options.expanded))
			{
				this.expanded		= options.expanded;
			}
			if (!Object.isUndefined(options.collapsed))
			{
				this.collapsed		= options.collapsed;
			}
			if (this.collapsable && this.expanded == this.collapsed )
			{
				this.expanded = true;
				this.collapsed = false;
			}
			if (Object.isUndefined(options.forceCollapsedState))
			{
				options.forceCollapsedState	= false;
			}

			this.title = options.title || this.title;

			/* pinable nur mit moveable */
			if (!this.moveable && this.pinnable)
			{
				this.pinnable = false;
			}

			$super(options);

			/* set value */
			if (options.value)
			{
				this.setValue.bind(this).defer(options.value, false);
			}

			this.initialized = true;
		},

		/**
		 * pins or unpins das control
		 *
		 * @param bool state
		 * @access public
		 * @return this
		 */
		pin: function(state)
		{
			this.pinned = state;

			var btnNode = this.dom.down(".jcTool-button-pin");

			/* einschalten */
			if (this.pinned && btnNode.hasClassName("jcTool-pin-off"))
			{
				btnNode.removeClass("jcTool-pin-off").addClass("jcTool-pin-on");
			}

			/* ausschalten */
			else if (!this.pinned && btnNode.hasClassName("jcTool-pin-on"))
			{
				btnNode.removeClass("jcTool-pin-on").addClass("jcTool-pin-off");
			}

			/* scroll observer installieren? */
			if (!this._scrollObserved)
			{
				this._scrollObserved = true;
				this._scrollSO = this.getCookie().scrollSO || null;
				Event.observe(document, "scroll", (function(element, so)
				{
					if (!this.pinned)
					{
						return;
					}

					var xy = this.dom.getXY();
					if (this._scrollSO)
					{
						xy[0] -= this._scrollSO.left;
						xy[1] -= this._scrollSO.top;
					}
					xy[0] += so.left;
					xy[1] += so.top;
					this._scrollSO = Object.clone(so);
					this.dom.setXY(xy);
					this.setCookie();
				}).bind(this));
				$(document).fire("scroll");
			}

			this.setCookie();
			return this;
		},

		/**
		 * zerstört das kontroll
		 */
		remove: function($super)
		{
			unregister(this);
			$super();
		},

		/**
		 * rendert das ganze
		 *
		 * @param node|string|jControls.* renderTo zu diesem Element ans Ende rendern
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			var cookie;
			var html = "";

			if (this.rendered)
			{
				return this;
			}

			cookie = this.getCookie();
			if (typeof cookie.expanded != "undefined" && this.options.forceCollapsedState == false)
			{
				this.expanded	= cookie.expanded;
				this.collapsed	= !cookie.expanded;
			}

			if (this.xType == jControls.Control.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			$super(renderTo);

			this.dom.addClass("jcControl");
			if (!this.enabled)
			{
				this.dom.addClass("jcControl-disabled");
			}

			/* titel rendern */
			if (this.title || this.collapsable || this.moveable || this.pinnable)
			{
				var hasBtn = this.collapsable || this.closeable || this.pinnable;
				html =
					"<div class='jcControl-panel jcControl-panel-top" + (this.moveable ? " jcCursorMove" : "") + "'>" +
						"<span class='jcControl-title' style='width:" + (hasBtn ? "auto" : "100%" ) + ";'>" +
							(this.title || "") +
						"</span>" +
						(this.closeable ? "<div class='jcTool jcTool-button-close jcTool-close' title='" + jControls.Button.TITLE.CLOSE + "'></div>" : "") +
						(this.collapsable ? "<div class='jcTool jcTool-button-collapsable jcTool-" + (!this.collapsed ? "collapse" : "expand") + "-north' title='" + jControls.Button.TITLE.EXPANDCOLLAPSE + "'></div>" : "") +
						(this.pinable ? "<div class='jcTool jcTool-button-pin jcTool-pin-" + (this.pinned ? "on" : "off") + "' title='" + jControls.Button.TITLE.PINNED + "'></div>" : "") +
						/* kleiner helper damit voll aufgezogen wird aber nur im IE und nur mit keinen einklapteil */
						(!hasBtn && Prototype.Browser.isIE ? "<div class='jcWidthFull'>&#160;</div>" : "") +
						"<div class='jcClear'></div>" +
					"</div>";
			}

			/* html einfügen */
			if (html + this.html != "")
			{
				Element.insert(this.dom,
				{
					bottom: html + this.html
				});
			}

			/* wenn collapsed dann events und einklappen? */
			if (this.collapsable)
			{
				(function()
				{
					Element.observe(this.dom.down(".jcTool-button-collapsable"), "click", (function(e)
					{
						var btnNode = this.dom.down(".jcTool-button-collapsable");
						if (!this.collapsed && btnNode.hasClassName("jcTool-collapse-north"))
						{
							this.collapse();
						}
						else if (!this.expanded && btnNode.hasClassName("jcTool-expand-north"))
						{
							this.expand();
						}
					}).bindAsEventListener(this));

					/* einklappen aber bitte nur verzögert */
					if (this.collapsed)
					{
						var style = this.dom.readAttribute("style");
						this.dom._expandHeightClear		= (style != null && style.indexOf("height") != -1 ? false : true);
						this.dom._expandHeight			= this.dom.getStyle("height");
						this.dom.setHeight(this.dom.down(".jcControl-panel").getHeight());
					}
				}).bind(this).defer();
			}

			/* wenn closeable dann */
			if (this.closeable && this.listeners.onClose)
			{
				(function()
				{
					Element.observe(this.dom.down(".jcTool-close"), "click", this.listeners.onClose.curry(this));
				}).bind(this).defer();
			}

			/* wenn moveable dann */
			if (this.moveable)
			{
				(function()
				{
					var posMouseOld = null;
					var posDomBefore = null;
					var moveXY = this.getCookie().moveXY;
					if (moveXY)
					{
						this.dom.setXY(moveXY);
					}

					/* fn für mouse move */
					var fn = (function(e)
					{
						if (posMouseOld == null || !e.isLeftClick())
						{
							return;
						}

						var posDom = this.dom.getBox();
						var posDomOld = Object.clone(posDom);
						var posMouse = e.pointer();
						var viewport = document.viewport.getDimensions();
						var scrollOffsets = document.viewport.getScrollOffsets();

						posDom.x = posDom.x + posMouse.x - posMouseOld.x;
						posDom.y = posDom.y + posMouse.y - posMouseOld.y;

						/* nicht rausfliegen lassen */
						if (posDom.x < 0)
						{
							posDom.x = 0;
						}
						if (posDom.y < 0)
						{
							posDom.y = 0;
						}
						if (posDom.x > viewport.width + scrollOffsets.left - posDom.width)
						{
							posDom.x = viewport.width + scrollOffsets.left - posDom.width;
						}
						if (posDom.y > viewport.height + scrollOffsets.top - posDom.height)
						{
							posDom.y = viewport.height + scrollOffsets.top - posDom.height;
						}

						if (this.listeners.onMove && this.listeners.onMove(this, posDom, posDomOld) === false)
						{
							return;
						}
						this.dom.setXY(posDom);

						posMouseOld = e.pointer();
					}).bindAsEventListener(this);

					/* mouse geht runter */
					Element.observe(this.dom.down(".jcControl-panel-top"), "mousedown", (function(e)
					{
						if (!e.isLeftClick())
						{
							return;
						}
						if (this.listeners.onBeforeMove && this.listeners.onBeforeMove(this) === false)
						{
							return;
						}
						posMouseOld = e.pointer();
						posDomBefore = this.dom.getBox();

						/* event installieen global für mouse wird bewegt */
						Element.observe(document, "mousemove", fn);
					}).bindAsEventListener(this));

					/* mouse release */
					Element.observe(this.dom.down(".jcControl-panel-top"), "mouseup", (function(e)
					{
						posMouseOld = null;
						/* event deinstallieren */
						Element.stopObserving(document, "mousemove", fn);

						if (this.listeners.onAfterMove)
						{
							this.listeners.onAfterMove(this, this.dom.getBox(), posDomBefore);
						}

						this._moveXY = this.dom.getXY();
						this.setCookie();
					}).bindAsEventListener(this));
				}).bind(this).defer();
			}


			/* wenn pinable dann */
			if (this.pinable)
			{
				(function()
				{
					Element.observe(this.dom.down(".jcTool-button-pin"), "click", (function(e)
					{
						this.pin(!this.pinned);
					}).bindAsEventListener(this));
					if (this.pinned)
					{
						this.pin.bind(this).defer(this.pinned);
					}
				}).bind(this).defer();
			}

			/* buttons? */
			if (this.options.buttons)
			{
				var buttonFn = function(ctl, buttons)
				{
					buttons.each(function(button)
					{
						new jControls.Button(Object.extend(button || {},
						{
							renderTo: ctl
						}));
					});
				};

				/* aktuelles control */
				if (this.xType == jControls.PageList.xType)
				{
					buttonFn(this, this.options.buttons);
				}
				/* buttons ohne page list */
				else if (!this.pagelist)
				{
					this.toolbarBottom = new jControls.Control(
					{
						cls:		"jcControl-panel jcControl-panel-bottom jcToolbar",
						listeners:
						{
							onAfterRender: (function(ctl)
							{
								buttonFn(ctl, this.options.buttons);
							}).bind(this)
						}
					});
					this.toolbarBottom.render.bind(this.toolbarBottom).defer(this);
				}
				/* buttons mit page list */
				else if(this.pagelist)
				{
					this.pagelist.listeners.onAfterRender = (this.pagelist.listeners.onAfterRender || Prototype.emptyFunction).wrap(function(proceed, ctl)
					{
						buttonFn(ctl, this.options.buttons);
						return proceed(ctl);
					}).bind(this);
				}
			}

			if (this.xType == jControls.Control.xType && this.listeners.onAfterRender)
			{
				this.listeners.onAfterRender.defer(this);
			}

			return this;
		},

		/**
		 * Zeichnet den wert
		 *
		 * @param mixed oldValue
		 * @param mixed newValue
		 * @access public
		 */
		renderValue: function(oldValue, newValue)
		{
		},

		/**
		 * speichert neue cookie data ab
		 *
		 * @param object options
		 * @access public
		 * @return this
		 */
		setCookie: function(options)
		{
			jControls.Cookies(this.id).set(Object.extend(options || {},
			{
				expanded:	(this.collapsable ? this.expanded  : undefined),
				pinned:		this.pinned,
				moveXY:		this._moveXY,
				scrollSO:	this._scrollSO
			}));

			return this;
		},

		/**
		 * aktivieren | deaktivieren
		 *
		 * @param bool value
		 * @access public
		 * @return this
		 */
		setEnabled: function(state)
		{
			if (this.enabled != state)
			{
				this.enabled = state;

				this.dom.removeClass("jcControl-disabled");
				if (!this.enabled)
				{
					this.dom.addClass("jcControl-disabled");
				}

				if (this.listeners.onEnableStateChange)
				{
					this.listeners.onEnableStateChange.defer(this, state);
				}
			}

			return this;
		},

		/**
		 * eingebbar oder nicht
		 *
		 * @param bool state
		 * @access public
		 * @return this
		 */
		setEnterable: function(state)
		{
			if (this.enterable != state)
			{
				this.enterable = state;
			}

			return this;
		},

		/**
		 * setzt den titel. das setzen funktioniert nur, wenn in den option schon ein titel gesetzt wurde
		 * oder das control auf collapsable gesetzt ist
		 *
		 * @param string newTitle
		 * @access public
		 * @return this
		 */
		setTitle: function(newTitle)
		{
			this.dom.down(".jcControl-title").innerHTML = newTitle;
			this.title = newTitle;
		},

		/**
		 * setzt den Wert
		 *
		 * @param mixed value
		 * @param bool initialized
		 * @access public
		 * @return this
		 */
		setValue: function(value, initialized)
		{
			if (this.value == value)
			{
				return this;
			}

			if (typeof initialized == "undefined")
			{
				initialized = this.initialized;
			}

			/* onBeforeChange */
			if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, value, initialized) === false)
			{
				return this;
			}

			/* wert setzen */
			var oldValue = this.value;
			this.value = value;
			if (this.inputContainer)
			{
				if (value != null && !Object.isUndefined(value))
				{
					this.inputContainer.writeAttribute("value", value);
					this.inputContainer.value = value;
				}
				else
				{
					this.inputContainer.value = "";
				}
			}

			this.renderValue(oldValue, this.value);

			/* onChange */
			if (this.listeners.onChange)
			{
				this.listeners.onChange.defer(this, value, initialized);
			}

			return this;
		}
	});
	/**
	 * xTyp
	 */
	jControls.Control.xType = xTypes.Control;

	/****************************************************************************************************
	 *																									*
	 *												SELECT												*
	 *																									*
	 ****************************************************************************************************/
	/**
	 * ein Select Control
	 *
	 * Select Control Class options (geerbte options von jControls.Control siehe oben)
	 *
	 *	Hinweis: editable kann nur aktiviert werden, wenn nur eine Spalte
	 *		name					type												default			description
	 *		=====================================================================================================================
	 *		data					object																die zu verwendenden Daten
	 *		emptyText				string																diesen text anzeigen wenn nix ausgewählt
	 *		handler					jControls.Calendar|jControls.Grid|jControls.Tree	jControls.Grid	datenhändler
	 */
	jControls.Select = Class.create(jControls.Control,
	{
		/**
		 * der Node mit dem Text
		 *
		 * @var jControls.Container
		 * @access private
		 */
		containerText:	null,

		/**
		 * datenhandler verwies
		 *
		 * @var jControls.Grid|jControls.Tree
		 * @access private
		 */
		dataHandler: null,

		/**
		 * der Datastor mit den Werten
		 *
		 * @var jControls.Grid
		 * @access protected
		 */
		grid: null,

		/**
		 * der Datastore mit den Werten
		 *
		 * @var jControls.Grid|jControls.Tree
		 * @access protected
		 */
		handler: null,

		/**
		 * erzeugt den Handler für die Drop down
		 *
		 * @access private
		 * @return jControls.Grid|jControls.Tree
		 */
		getHandler: function()
		{
			if (!this.handler)
			{
				/* das handler erzeugen */
				if (this.dataHandler == jControls.Calendar && typeof this.options.data.value == "undefined")
				{
					this.options.data.value = this.options.value;
				}
				this.handler = this.grid = new this.dataHandler(Object.extend(this.options.data,
				{
					cls:		"jcSelect-list " + (this.options.data.cls || ""),
					id:			this.id + "_handler",
					enterable:	true,
					style:		Object.extend(this.options.data.style || {},
					{
						display:	"none"
					}),
					listeners:	Object.extend(this.options.data.listeners || {}, (function()
					{
						var on = {};

						if (this.options.listeners && this.options.listeners.onBeforeChange)
						{
							on.onBeforeChange = (function(handler, oldValue, newValue)
							{
								return this.listeners.onBeforeChange(this, oldValue, newValue);
							}).bind(this);
						}

						on.onChange = (function(handler, value, initialized)
						{
							if (handler.xType == jControls.Calendar.xType && (value == null || (value != null && this.value == null) || (this.value != null && !value.clearTime(true).isEqual(this.value.clearTime(true)))))
							{
								this.hideList();
							}

							var oldValue = this.value;
							this.value = value;

							if (this.inputContainer)
							{
								if (handler.xType == jControls.Calendar.xType)
								{
									if (value != null)
									{
										this.inputContainer.value = handler.options.time.show ? value.format("Y-m-d H:i:s") : value.format("Y-m-d");
									}
									else
									{
										this.inputContainer.value = "";
									}
								}
								else
								{
									this.inputContainer.value = value;
								}
							}

							this.renderValue(oldValue, this.value);

							if (this.listeners.onChange)
							{
								this.listeners.onChange.defer(this, value, initialized);
							}
						}).bind(this);

						return on;
					}).bind(this)()),
					renderTo:	document.body
				}));
			}

			return this.handler;
		},

		/**
		 * versteckt die Drop Down liste
		 *
		 * @access public
		 * @return this
		 */
		hideList: function(event)
		{
			/* dient dazu nach dem click für liste anzeigen */
			/* das die liste gleich versteckt wird wieder */
			/* da das event trotzdem hierher noch von showlist durchgereicht wird. */
			if (this._preventHideList)
			{
				this._preventHideList = undefined;
				return this;
			}

			if (!this.handler || !this.handler.dom)
			{
				return this;
			}

			var box = this.handler.dom.getBox();
			/* scroll offset muss beachtet werden */
			var scrollOffset = document.viewport.getScrollOffsets();
			box.x -= scrollOffset.left;
			box.y -= scrollOffset.top;
			box.right -= scrollOffset.left;
			box.bottom -= scrollOffset.top;
			/* wenn innerhalb eines Calendars geklickt wurde dann nicht */
			if (event && this.handler.xType == jControls.Calendar.xType && box.x <= event.clientX && box.y <= event.clientY && event.clientX <= box.right && event.clientY <= box.bottom)
			{
				return;
			}

			if (this.rendered && this.handler && this.handler.rendered)
			{
				this.handler.dom.hide();
			}
			return this;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options, autoloadIE)
		{
			if (!this.xType)
			{
				this.xType = jControls.Select.xType;
			}

			if (!autoloadIE && Prototype.Browser.isIE)
			{
				autoload(this.initialize.bind(this, options, true));
				return;
			}
			this.dataHandler = (!Object.isUndefined(options.handler) ? options.handler : jControls.Grid);

			this.id = options.id || getId();

			$super(options);
		},

		/**
		 * remove
		 *
		 * @access public
		 * @return this
		 */
		remove: function($super)
		{
			if (this.handler)
			{
				this.handler.remove();
			}

			return $super();
		},

		/**
		 * rendert das control
		 *
		 * @param node|string|jControls.* renderTo zu diesem Element ans Ende rendern
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			if (this.rendered)
			{
				return this;
			}

			var html = "";

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			if (this.xType == jControls.Select.xType)
			{
				if (this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
				{
					return this;
				}
				var parents = renderTo.toggleDisplayed();
			}

			$super(renderTo);

			this.dom.addClass("jcSelect");
			Event.observe(this.dom, "mousedown", (function()
			{
				this.dom.down(".jcSelect-button").addClass("jcSelect-button-click");
			}).bind(this));
			Event.observe(this.dom, "mouseup", (function()
			{
				this.dom.down(".jcSelect-button").removeClass("jcSelect-button-click");
			}).bind(this));
			Event.observe(this.dom, "click", this.showList.bindAsEventListener(this));
			Event.observe(document, "click", this.hideList.bind(this)); /* globales click überwachen zum verstecken */

			Element.insert(this.dom,
			{
				bottom:
					"<input type='hidden' name='" + this.name + "' value='" + (this.value || "") + "' />" +
					"<div class='jcSelect-value'></div>" +
					"<div class='jcSelect-button" + (!this.enabled ? " jcSelect-button-disabled" : "") + "'></div>"
			});

			/* text Node */
			this.containerText		= this.dom.down(".jcSelect-value");
			this.inputContainer		= this.dom.down("input", 0);

			if (this.options.emptyText)
			{
				this.renderValue(null, null);
			}

			if (this.xType == jControls.Select.xType)
			{
				renderTo.toggleDisplayed(parents);
				if (this.listeners.onAfterRender)
				{
					this.listeners.onAfterRender.defer(this);
				}
			}

			return this;
		},

		/**
		 * Zeichnet den wert
		 *
		 * @param mixed oldValue
		 * @param mixed oldValue
		 * @access public
		 */
		renderValue: function($super, oldValue, newValue)
		{
			$super(oldValue, newValue);
			(function()
			{
				var row = null;
				if (!this.rendered)
				{
					return;
				}
				this.containerText.childElements().invoke("remove");

				row = this.getHandler().getRow(this.value);
				if (this.value || row)
				{
					this.getHandler().renderRow(row, this.containerText, true);
				}
				else if (this.options.emptyText)
				{
					this.containerText.applyTo("<div class='jcSelect-select-value-empty'>" + this.options.emptyText + "</div>");
				}
			}).bind(this).defer();
		},

		/**
		 * aktiviert oder dekativiert das controll
		 *
		 * @param bool state
		 * @access public
		 * @return this
		 */
		setEnabled: function($super, state)
		{
			$super(state);

			this.dom.down(".jcSelect-button").removeClass("jcSelect-button-disabled");

			if (!this.enabled)
			{
				this.dom.down(".jcSelect-button").addClass("jcSelect-button-disabled");
			}

			return this;
		},

		/**
		 * setzt die position der liste an die richtige stelle
		 *
		 * @access public
		 * @return this
		 */
		setListPosition: function()
		{
			var parents = this.dom.toggleDisplayed();
			var box = this.dom.getBox();
			this.getHandler().dom.setStyle(
			{
				left:	(box.x - (Prototype.Browser.isIE ? 2 : 0)) + "px",
				top:	(box.y + box.height - (Prototype.Browser.isIE ? 2 : 0)) + "px",
				width:	(box.width - this.dom.getBorderWidth("lr")) + "px"
			});
			this.dom.toggleDisplayed(parents);
		},

		/**
		 * setzt den Wert neu
		 *
		 * @param mixed value
		 * @param bool initialized
		 * @access public
		 * @return this
		 */
		setValue: function(value, initialized)
		{
			if (!this.handler)
			{
				this.getHandler();
				(function()
				{
					this.getHandler().setValue(value, initialized);
				}).bind(this).defer();
			}
			else
			{
				this.getHandler().setValue(value, initialized);
			}
			return this;
		},

		/**
		 * zeigt die Drop Down an
		 *
		 * @access public
		 * @return this
		 */
		showList: function()
		{
			if (!this.rendered || !this.enabled)
			{
				return this;
			}

			this.getHandler();
			if (this.handler.dom.isDisplayed())
			{
				return this.hideList();
			}

			this.setListPosition();
			if (this.handler.reset)
			{
				this.handler.reset();
			}

			this.handler.dom.show();
			if (this.value)
			{
				this.handler.scrollTo(this.handler.getRow(this.value));
			}

			/* dient dazu nach dem click für liste anzeigen */
			/* das die liste gleich versteckt wird wieder */
			/* da das event trotzdem hierher noch von showlist durchgereicht wird. */
			this._preventHideList = true;

			return this;
		}
	});
	/**
	 * xTyp
	 */
	jControls.Select.xType = xTypes.Select;

	/**
	 * autoloadfunktion für Select
	 *
	 * @param string selector Selektor (see Prototype/$$) (standard "select:not([class~=noselect])" alle select ausser die mit der class "noselect")
	 * @access public
	 */
	jControls.Select.autoload = function(selector)
	{
		if (jControls.Select._autoloaded)
		{
			return;
		}
		jControls.Select._autoloaded = true;

		autoload(function()
		{
			$$(selector || "select:not([class~=noselect])").each(function(element)
			{
				if (element.hasClassName("jcContainer"))
				{
					return;
				}
				jControls.Select.initBy(element);
			});
		});
	};

	/**
	 * initialisiert ein Select anhand eines/mehrerer SELECTs
	 * mit den classNames an der SELECT (DATATYPES) kann der datentyp der spalte definiert werden
	 * mit den classNames an der SELECT (nosort) kann festgelegt werden, das die Select Values nicht sortierbar ist
	 *
	 * @param node element
	 * @param node element
	 * @param node ...
	 * @param object options initialoptions
	 * @access public
	 * @return new jControls.Select
	 */
	jControls.Select.initBy = function()
	{
		arguments = $A(arguments).flatten();
		if (arguments.length < 1 && arguments[0] === undefined)
		{
			return;
		}

		if ((arguments.length == 1 && (Object.isElement(arguments[0]) || Object.isString(arguments[0])))	||	/* nur ein element */
			(arguments.length == 2 && (Object.isElement(arguments[0]) || Object.isString(arguments[0])) && !Object.isElement(arguments[1]) && !Object.isString(arguments[1]))) /* ein element und eine option */
		{
			var element = $(arguments[0]);
			var options = arguments[1] || {};

			if (!element)
			{
				return;
			}
			if (element.tagName.toUpperCase() != "SELECT")
			{
				return;
			}

			/* allgemeine options */
			options = Object.extend(
			{
				cls:			element.className,
				enterable:		false,
				enabled:		!element.disabled,
				id:				element.id || undefined,
				name:			element.name || undefined,
				renderReplace:	element,
				qtip:			element.getQtipText() || undefined,
				value:			element.value || undefined
			}, options || {});

			/* Styles */
			options.style = Object.extend(element.collectStyles(), options.style || {});

			/* listeners */
			options.listeners = options.listeners || {};
			if (element.readAttribute("onchange"))
			{
				var onChange = element.readAttribute("onchange");
				options.listeners.onChange = (options.listeners.onChange || Prototype.emptyFunction).wrap(function(proceed, select, value, initialized)
				{
					if (initialized)
					{
						(function()
						{
							eval(onChange);
						}).bind(select.inputContainer)();
					}
					return proceed(select, value, initialized);
				});
			}

			/* data grid */
			options.data = Object.extend(
			{
				columns:	[
				{
					id:			"column0",
					type:		jControls.Grid.getElementFormat(element),
					visible:	true
				}],
				header:		true,
				sortable:	!element.hasClassName("nosort"),
				data:		$A(element.options).collect(function(option)
				{
					option = $(option);
					var value = option.text.strip();

					var attrib = [];
					if (option.id)
					{
						attrib.push("id='" + option.id + "'");
					}
					if (option.className)
					{
						attrib.push("class='" + option.className + "'");
					}
					if (option.style.cssText)
					{
						attrib.push("style='" + option.style.cssText + "'");
					}
					if (attrib.length != 0)
					{
						value = "<div " + attrib.join(" ") + ">" + value + "</div>";
					}

					return {
						id:			option.id || option.value,
						"column0":	value,
						qtip:		option.getQtipText() || undefined,
						style:		option.collectStyles()
					};
				})
			}, options.data || {});

			return new jControls.Select(options);

		}
		/* mehrere element */
		else
		{
			var options = arguments.last();
			options = (options.nodeType != 1 && typeof options != "string" ? arguments.pop() : {});
			return arguments.collect(function(element)
			{
				return jControls.Select.initBy(element, options);
			}, this);
		}
	};

	/****************************************************************************************************
	 *																									*
	 *										GRID UTILITY FUNKTIONS										*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * komplexen Spaltenkopf rendern anhand der style informationen
	 *
	 * @param jControls.Grid|jControls.Tree control
	 * @access private
	 */
	var renderHeaderComplex = function(control)
	{
		var htmlColumn = "";
		var cssPrefix = control.xType == jControls.Grid.xType ? "jcGrid" : "jcTree";

		/* jede TR Zeile durchlaufen */
		control.options.columnStyles.each(function(columnRow, columnRowIndex)
		{
			htmlColumn = htmlColumn + "<tr class='" + cssPrefix + "-header-hd-row'>";

			/* jede Zelle der Row durchlaufen */
			columnRow.each(function(columnCell, columnCellIndex)
			{
				if (columnCell.render)
				{
					var column = columnCell.dataCell ? control.columns[columnCellIndex] : null;
					if (column)
					{
						var columnCls			= column.cls;
						var columnId			= column.id;
						var columnQTip			= column.qtip;
						var columnSortable		= column.sortable && (control.options.sortable || Object.isFunction(control.listeners.onBeforeClickColumn) || Object.isFunction(control.listeners.onAfterClickColumn));
						var columnSortBy		= control.sortBy == column.id;
						var columnStyle			= column.styleText;
						var columnStyleWidth	= column.visible && !column.style.width ? "width:" + control._variableColumnWidth + "%;" : "";
						var	columnHighlight		= column.columnHighlight || control.options.columnHighlight || false;
					}
					else
					{
						var columnCls			= columnCell.element.className;
						var columnId			= columnCell.element.id ? columnCell.element.id : "column-" + columnRowIndex + "-" + columnCellIndex;
						var columnQTip			= columnCell.element.readAttribute("qtip") || columnCell.element.readAttribute("title") || "";
						var columnSortable		= control.options.sortable || Object.isFunction(control.listeners.onBeforeClickColumn) || Object.isFunction(control.listeners.onAfterClickColumn);
						var columnSortBy		= false;
						var columnStyle			= Object.toStyleString(columnCell.element.collectStyles());
						var columnStyleWidth	= ""; /*column ? (column.visible && !column.style.width ? "width:" + control._variableColumnWidth + "%;" : "") : */
						var	columnHighlight		= columnCell.columnHighlight || control.options.columnHighlight || false;
					}

					if (columnHighlight)
					{
						var columnHighlightClasses = "";
						for (var i = 0; i < columnCell.colspan; i++)
						{

							columnHighlightClasses = columnHighlightClasses + (i != 0 ? "," : "") + '".' + cssPrefix + '-cell-td-' + (columnCellIndex + i) + '"';
						}
					}
					htmlColumn = htmlColumn +
								"<td" +
									(columnCell.rowspan > 1 ? " rowspan='" + columnCell.rowspan + "'" : "") +
									(columnCell.colspan > 1 ? " colspan='" + columnCell.colspan + "'" : "") +
									" class='" +
										cssPrefix + "-header-hd" +
										(columnSortBy ? " jcSort-" + control.sortOrder : "") +
										(columnSortable ? "" : " " + cssPrefix + "-header-hd-nosort") +
										" " + cssPrefix + "-header-hd-cell " + cssPrefix + "-header-hd-" + columnId +
										(!columnCell.dataCell ? " " + cssPrefix + "-header-hd-nodata" : "") +
										(columnCls ? " " + columnCls : "") +
									"'" +
									" style='" + columnStyle + columnStyleWidth + "'" +
									(columnQTip ? "title='" + columnQTip + "'" : "" ) +
									" onclick='jControls.onHeaderColumnClick(event, jControls.$(\"" + control.id + "\"), \"" + columnId + "\", " + (column ? columnCellIndex : 'null') + ");'" +
									(columnHighlight
										?
											" onmouseover='jControls.$(\"" + control.id + "\").dom.select(" + columnHighlightClasses + ").each(function(element){element.addClass(\"" + cssPrefix + "-cell-column-hover\")});' " +
											" onmouseout='jControls.$(\"" + control.id + "\").dom.select(" + columnHighlightClasses + ").each(function(element){element.removeClass(\"" + cssPrefix + "-cell-column-hover\")});' "
										: ""
									) +
									">" +
										"<div class='" + cssPrefix + "-header-hd-inner'>" +
											columnCell.element.innerHTML.stripTags() +
											"<img src='" + jControls.url.imgS + "' class='jcSort-icon' />" +
										"</div>" +
								"</td>";
				}
			});

			htmlColumn = htmlColumn + "</tr>";
		});

		return	"<div class='" + cssPrefix + "-header'>" +
					"<div class='" + cssPrefix + "-header-inner'>" +
						"<div class='" + cssPrefix + "-header-offset'>" +
							"<table cellspacing='0' cellpadding='0' border='0' class='" + cssPrefix + "-header-table' style='width:100%'>" +
								"<thead>" +
									htmlColumn +
								"</thead>" +
							"</table>" +
						"</div>" +
					"</div>" +
				"</div>";
	};

	/**
	 * einfachen spaltenkopf zeichen ohne irgend welche schönheiten
	 *
	 * @param jControls.Grid|jControls.Tree control
	 * @access private
	 */
	var renderHeaderSimple = function(control)
	{
		var htmlColumn = "";
		var cssPrefix = control.xType == jControls.Grid.xType ? "jcGrid" : "jcTree";

		control.columns.each(function(column, columnIndex)
		{
			var columnSortable = (control.options.sortable || Object.isFunction(control.listeners.onBeforeClickColumn) || Object.isFunction(control.listeners.onAfterClickColumn)) && column.sortable;
			column.columnHighlight = column.columnHighlight || control.options.columnHighlight || false;

			htmlColumn = htmlColumn +
						"<td" +
							" class='" +
								cssPrefix + "-header-hd" +
								(control.sortBy == column.id ? " jcSort-" + control.sortOrder : "") +
								(columnSortable ? "" : " " + cssPrefix + "-header-hd-nosort") +
								" " + cssPrefix + "-header-hd-cell " + cssPrefix + "-header-hd-" + column.id +
								(column.cls ? " " + column.cls : "") +
							"'" +
							" style='" + column.styleText + (column.visible && !column.style.width ? "width:" + control._variableColumnWidth + "%;" : "") + "'" +
							(column.qtip ? "title='" + column.qtip + "'" : "" ) +
							" onclick='jControls.onHeaderColumnClick(event, jControls.$(\"" + control.id + "\"), \"" + column.id + "\", " + columnIndex + ");'" +
							(column.columnHighlight
								?
									" onmouseover='jControls.$(\"" + control.id + "\").dom.select(\"." + cssPrefix + "-cell-td-" + columnIndex + "\").each(function(element){element.addClass(\"" + cssPrefix + "-cell-column-hover\")});' " +
									" onmouseout='jControls.$(\"" + control.id + "\").dom.select(\"." + cssPrefix + "-cell-td-" + columnIndex + "\").each(function(element){element.removeClass(\"" + cssPrefix + "-cell-column-hover\")});' "
								: ""
							) +
							">" +
								"<div class='" + cssPrefix + "-header-hd-inner'>" +
									(column.title ? column.title : "") +
									"<img src='" + jControls.url.imgS + "' class='jcSort-icon' />" +
								"</div>" +
						"</td>";
		});

		return	"<div class='" + cssPrefix + "-header'>" +
					"<div class='" + cssPrefix + "-header-inner'>" +
						"<div class='" + cssPrefix + "-header-offset'>" +
							"<table cellspacing='0' cellpadding='0' border='0' class='" + cssPrefix + "-header-table' style='width:100%'>" +
								"<thead>" +
									"<tr class='" + cssPrefix + "-header-hd-row'>" +
										htmlColumn +
									"</tr>" +
								"</thead>" +
							"</table>" +
						"</div>" +
					"</div>" +
				"</div>";
	};

	/**
	 * entsprechend die Body größe eines Controls setzen
	 *
	 * @param jControls.Grid|jControls.Tree control
	 * @access private
	 */
	var correctBodyHeight = function(control)
	{
		var cssPrefix = control.xType == jControls.Grid.xType ? "jcGrid" : "jcTree";
		var fnHeight = Prototype.emptyFunction;

		/* scroller anpassen wenn nötig */
		if ((control.options.header || control.title || control.pagelist || control.toolbarBottom || control.options.footer) && control._domHeightOrg != null && parseInt(control._domHeightOrg) != 0)
		{
			/* in % berechnen */
			if (control._domHeightOrg.indexOf("%") != -1)
			{
				fnHeight = function(height, header, panelTop, panelBottom, footer)
				{
					Event.observe(document, "resize", function()
					{
						control.dom.down("." + cssPrefix + "-scroller").setStyle(
						{
							"height": (
								(100
									- (header ? header.getHeight() : 0)
									- (panelTop ? panelTop.getHeight() : 0)
									- (panelBottom ? panelBottom.getHeight() : 0)
									- (footer ? footer.getHeight() : 0)
								) * 100 / height
							) + "%"
						});
					});
					$(document).fire("resize");
				};
			}
			/* fixe höhe */
			else
			{
				fnHeight = function(height, header, panelTop, panelBottom, footer)
				{
					control.dom.down("." + cssPrefix + "-scroller").setStyle(
					{
						"height": (
							height
							- (header ? header.getHeight() + header.getBorderWidth("tb") : 0)
/*								- (header ? this.dom.getBorderWidth("tb") : 0) */
							- (panelTop ? panelTop.getHeight() + panelTop.getBorderWidth("tb") : 0)
							- (panelBottom ? panelBottom.getHeight() + panelBottom.getBorderWidth("tb") : 0)
							- (footer ? footer.getHeight() + footer.getBorderWidth("tb") : 0)
						) + "px"
					});

				};
			}

			/* controlls fürs berechnen und anzeigen der eltern nodes */
			fnHeight = fnHeight.wrap(function(proceed)
			{
				var parents = control.dom.toggleDisplayed();
				var panelBottom = control.pagelist || control.toolbarBottom;

				proceed(
					control.dom._expandHeight						/* richtige höhe nehmen */
						? parseInt(control.dom._expandHeight) + control.dom.getBorderWidth("tb")
						: control.dom.getHeight(),
					control.dom.down("." + cssPrefix + "-header"),	/* header */
					control.dom.down(".jcControl-panel-top"),		/* top panel */
					(panelBottom ? panelBottom.dom : undefined),	/* bottom panel */
					control.dom.down("." + cssPrefix + "-footer")	/* footer */
				);
				control.dom.toggleDisplayed(parents);
			});

			/* können wir gleich loslegen oder erst später */
			if (!control.pagelist && !control.toolbarBottom)
			{
				fnHeight();
			}
			/* warten bis diese fertig sind */
			else if (control.pagelist)
			{
				control.pagelist.listeners.onAfterRender = (control.pagelist.listeners.onAfterRender || Prototype.emptyFunction).wrap(function(proceed, ctl)
				{
					var result = proceed(ctl);
					fnHeight();
					return result;
				});
			}
			/* warten bis diese fertig sind */
			else if (control.toolbarBottom)
			{
				control.toolbarBottom.listeners.onAfterRender = (control.toolbarBottom.listeners.onAfterRender || Prototype.emptyFunction).wrap(function(proceed, ctl)
				{
					var result = proceed(ctl);
					fnHeight();
					return result;
				});
			}
		}
	};

	/**
	 * Footer zeichnen
	 *
	 * @param jControls.Grid|jControls.Tree control
	 * @access private
	 */
	var renderFooter = function(control)
	{
		var htmlColumn = "";
		var cssPrefix = control.xType == jControls.Grid.xType ? "jcGrid" : "jcTree";

		control.columns.each(function(column, columnIndex)
		{
			var cell = {
				style:		{},
				visible:	true
			};
			if (control.options.footer && control.options.footer.cell && control.options.footer.cell[column.id])
			{
				cell = control.options.footer.cell[column.id] || {};
			}

			htmlColumn = htmlColumn +
				"<td" +
					(cell.id ? " id='" + cell.id + "'" : "") +
					" class='" +
						cssPrefix + "-footer-hd " +
						cssPrefix + "-footer-hd-cell " +
						cssPrefix + "-footer-hd-" + column.id +
						(cell.cls ? " " + cell.cls : "") +
					"'" +
					" style='" +
						column.styleText +
						" " + Object.toStyleString(cell.style) +
						" " + (!cell.visible ? "display:none;" : "") +
						" " + (cell.visible + column.visible && !column.style.width ? "width:" + control._variableColumnWidth + "%;" : "")
					+ "'" +
					(cell.qtip ? "title='" + cell.qtip + "'" : "" ) +
					">" +
						"<div class='" + cssPrefix + "-footer-hd-inner'>" +
							(column.format ? column.format(control.options.footer[column.id]) : control.options.footer[column.id]) +
						"</div>" +
				"</td>";
		});

		return	"<div class='" + cssPrefix + "-footer'>" +
					"<div class='" + cssPrefix + "-footer-inner'>" +
						"<div class='" + cssPrefix + "-footer-offset'>" +
							"<table cellspacing='0' cellpadding='0' border='0' class='" + cssPrefix + "-footer-table' style='width:100%'>" +
								"<thead>" +
									"<tr class='" + cssPrefix + "-footer-hd-row'>" +
										htmlColumn +
									"</tr>" +
								"</thead>" +
							"</table>" +
						"</div>" +
					"</div>" +
				"</div>";
	};

	/**
	 * helper function für column click
	 *
	 * @param Event e
	 * @param jControls.Grid|jControls.Tree control
	 * @param string column
	 * @param int columIndex
	 * @access private
	 */
	jControls.onHeaderColumnClick = function(e, control, column, columnIndex)
	{
		Event.stop(e);

		if (columnIndex == null || !control.enabled)
		{
			return;
		}

		if (control.listeners.onBeforeClickColumn && control.listeners.onBeforeClickColumn(control, column) === false)
		{
			return;
		}

		if (control.options.sortable === true && control.columns[column].sortable === true && Object.isFunction(control.sort))
		{
			var order = control.sortOrder;

			if (control.sortBy == column)
			{
				order = (order == 'asc' ? 'desc' : 'asc');
			}
			control.sort(column, order);
		}

		if (control.listeners.onAfterClickColumn)
		{
			control.listeners.onAfterClickColumn.defer(control, column);
		}
	};

	/****************************************************************************************************
	 *																									*
	 *												GRID												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * neue daten anketten an ein Grid
	 *
	 * @param jControls.Grid grid
	 * @param object rowData
	 * @param int newPos
	 * @access private
	 */
	var gridAppendRowData = function(grid, rowData, newPos)
	{
		rowData.id		= rowData.id || ("row" + Math.floor(Math.random() * 100000000));
		rowData.visible	= typeof rowData.visible != "undefined" ? rowData.visible : true;
		rowData._index	= newPos;
		grid.options.data.splice(newPos, 0, rowData);
		grid.data.splice(newPos, 0, rowData);
		grid.rowDom.splice(newPos, 0, {dom: null, rendered: false});
	};

	/**
	 * löscht die Row Daten im Grid
	 *
	 * @param jControls.Grid
	 * @param int delPos
	 * @access private
	 */
	var gridDeleteRowData = function(grid, delPos)
	{
		grid.options.data.splice(delPos, 1);
		grid.data.splice(delPos, 1);
		grid.rowDom.splice(delPos, 1);
	};

	/**
	 * setzt die zeilenalternierung wieder korrekt im Grid
	 *
	 * @param node gridDom
	 * @access private
	 */
	var gridSetRowClassAlternate = function(gridDom)
	{
		gridDom.down(".jcGrid-body").select(".jcGrid-row").each(function(divRow, rowIndex)
		{
			divRow.removeClass("jcGrid-row-alt");
			if (rowIndex % 2 == 1)
			{
				divRow.addClass("jcGrid-row-alt");
			}
		});
	};

	/**
	 * Grid
	 *
	 *	Grid Class options (geerbte options von jControls.Control siehe oben)
	 *
	 *	Grid options
	 *		name						type				default		description
	 *		=====================================================================================================================
	 *		ajax			optional	object							jControls.Ajax.Request Options für das nachladen von Daten (müssen gültig zu options.data sein)
	 *		columns						array							die Spalten als Object
	 *		columnHighlight	optional	bool				false		Spalten highlighten wenn maus über spaltenkopf
	 *		columnStyles	optional	array							Informationsarray mit den Definition, wie die Spaltenköpfe gezeichnet werden sollen
	 *		data						array							die Daten als object Array in Bezug auf columns.id
	 *		footer			optional	object							Daten für eine Footer Zeile, welche immer sichtbar ist und aufgebaut ist wie data[] Entry
	 *		forcePages		optional	bool				true		erzwingt das Anzeigen des PageList Control, selbst wenn es nur eine seite gibt
	 *		header			optional	bool				true		Spaltentitel anzeigen
	 *		multiselect		optional	bool				false		mehrfachauswahl
	 *		pages			optional	bool|object			false		seitenweise anzeigen (bool für jControls.PageList Options)
	 *		sort			optional	string				asc | desc	sortieren wenn angegeben
	 *		sortable		optional	bool				true		anklicken der Spalten sortiert
	 *		sortAjaxUrl		optional	string							wenn ajax und dieser wert gesetzt dann wird via URL sortiert und dabei diese URL genommen
	 *		sortBy			optional	string				[0]			nach diesem Data Key sortieren
	 *		style			optional	string|object					zusätzliche styles für den zugehörigen anzeigecontainer
	 *
	 *		columns Entry
	 *		name						type				default		description
	 *		=====================================================================================================================
	 *		cls				optional	string							zusätzliche css class für die spalte
	 *		columnHighlight	optional	bool				true		Spalten highlighten wenn maus über spaltenkopf
	 *		format			optional	function						Formatierungsfunktion
	 *		id							string							id bzw key der spalte über welche nach data referenziert wird
	 *		qtip			optional	jControls.QuickTip.options		einen entsprechende Quicktip für den SPaltenkopf anzeigen
	 *		sortable		optional	bool				true		spalte sortierbar?
	 *		style			optional	string|object					zusätzliche styles
	 *		title			optional	string							Titel der spalte
	 *		type			optional	string				string		Datentyp der Spalte (DATATYPES)
	 *		visible			optional	bool				true		Sichtbar
	 *
	 *		columnStyles Entry (columnStyles[Head Row][Head Column])
	 *			pro Head Zeile ein Index in columnStyles
	 *			in jeder Index is ein Array mit allen verfügbaren spalten
	 *		name					type				default		description
	 *		=====================================================================================================================
	 *		colspan					int								anzahl der Col Spans dieser Spalte (die nachfolgenden zugehörigen Spalten sollten dann render = false sein)
	 *		dataCell				bool							definiert diese Zelle als Datenzelle womit diese ggf sortierbar ist
	 *		element					node							das original element welches die Spaltendefinitionen definierte
	 *		render					bool							wird diese Spalte gezeichnet (sind idR meinst von colspan und rowspan abghängig)
	 *		rowspan					int								anzahl der Row Spans dieser Spalte (die nachfolgenden zugehörigen Zeilen-Spalten sollten dann render = false sein)
	 *
	 *		data Entry
	 *		name						type				default		description
	 *		=====================================================================================================================
	 *		cell			optional	Object							zusätzliche Optionen für die Zelle wobei die columnIds die Keys sind
	 *		cls				optional	string							zusätzliche css class für die row
	 *		id							mixed							die ID für diese Zeile und wird als value verwendet (muss uniq sein)
	 *		qtip			optional	jControls.QuickTip.options		einen entsprechende Quicktip für den Zeile anzeigen
	 *		style			optional	string|object		""			zusätzliche styles
	 *		visible			optional	bool				true		Sichtbar
	 *		"columnId"					mixed							der Wert für die Spalte columnId
	 *
	 *		data Entry cell."columnId"
	 *		name						type				default		description
	 *		=====================================================================================================================
	 *		cls				optional	string							zusätzliche css class für die Zelle XYZ
	 *		columnHighlight	optional	bool				true		Spalten highlighten wenn maus über spaltenkopf
	 *		id				optional	string							id der Zelle
	 *		qtip			optional	jControls.QuickTip.options		einen entsprechende Quicktip für den Zeile anzeigen
	 *		style			optional	string|object		""			zusätzliche styles
	 *		visible			optional	bool				true		Sichtbar
	 *
	 *		sortAjaxUrl Template Variables
	 *		name					type				default		description
	 *		=====================================================================================================================
	 *		#{columnId}				string							spalte nach welcher sortiert werden soll
	 *		#{direction}			string							sortierrichtung der Spalte
	 *		#{pageId}				int								aktuelle seitennummer wenn paging an
	 *
	 * Default Event Names
	 *	deklaration											result type		description
	 * =====================================================================================================================
	 *	onAfterAjaxRequest(grid, ajaxOptions)								nach dem Request nach neuen Daten
	 *	onAfterAjaxRequestSuccess(grid, ajaxOptions, data)					nach dem auswerten der Daten
	 *	onAfterClickColumn(grid, columnid)									nach dem anklicken eines spaltentitels
	 *	onAfterClickRow(grid, row)											nach dem anklicken einer zeile
	 *	onAfterRenderRow(grid, row)											nach dem rendern einer zeile
	 *	onAfterSort(grid, sortBy, sortOrder)								nach dem sort
	 *	onAfterUpdate(grid)													nach dem aktualisieren
	 *	onAfterUpdateRow(grid, row, node)									nach dem aktualisieren einer Zeile
	 *	onBeforeAjaxRequest(grid, ajaxOptions)				bool			vor dem Request nach neuen Daten (ajaxOptions sind änderbar)
	 *	onBeforeAjaxRequestSuccess(grid, ajaxOptions, data)	bool			vor dem auswerten der Daten
	 *	onBeforeClickColumn(grid, columnid)					bool			bei return === false wird abgebrochen
	 *	onBeforeClickRow(grid, row)							bool			bei return === false wird abgebrochen
	 *	onBeforeRenderRow(grid, row)						bool			vor dem rendern einer zeile
	 *	onBeforeSort(grid, sortBy, sortOrder)				bool			vor dem sort
	 *	onBeforeUpdate(grid)								bool			vor dem aktualisieren des Grids
	 *	onBeforeUpdateRow(grid, row, node)					bool			vor dem aktualisieren einer Zeile
	 *	onMouseMoveRow(event, grid, row)									mouse bewegt sich über die zeile
	 *	onMouseOutRow(grid, row)											mouse ist aus der zeile raus
	 *	onMouseOverRow(grid, row)											mouse is in die zeile rein
	 *
	 * <code>
	 *	new jControls.Grid(
	 *	{
	 *		renderTo:	"blub",
	 * 		sort:		"asc",
	 * 		sortBy:		"column1",
	 *		style:
	 *		{
	 *			width:	"300px"
	 *		},
	 * 		data:
	 * 		[
	 * 			{
	 *				id:			"s4",
	 * 				column1:	"Spalte 1 Zeile 4",
	 * 				column2:	2.4,
	 * 				column3:	"Spalte 3 Zeile 4"
	 *
	 * 			},
	 * 			{
	 *				id:			"s2",
	 * 				column1:	"Spalte 1 Zeile 2",
	 * 				column2:	2.2,
	 * 				column3:	"Spalte 3 Zeile 2"
	 * 			},
	 * 			{
	 *				id:			"s3",
	 * 				column1:	"Spalte 1 Zeile 3",
	 * 				column2:	2.3,
	 * 				column3:	"Spalte 3 Zeile 3"
	 * 			},
	 * 			{
	 *				id:			"s1",
	 * 				column1:	"Spalte 1 Zeile 1",
	 * 				column2:	2.1,
	 * 				column3:	"Spalte 3 Zeile 1"
	 * 			}
	 * 		],
	 * 		columns:
	 * 		[
	 * 			{
	 * 				id:			"column1",
	 * 				title:		"Spalte 1",
	 * 				type:		"string",
	 * 				format:		function(value) { return value.substring(10); }
	 * 			},
	 * 			{
	 * 				id:			"column2",
	 * 				title:		"Spalte 2",
	 * 				type:		"number",
	 * 				format:		function(number) { return number.numberFormat(2, ",", "." ); }
	 * 			},
	 * 			{
	 * 				id:			"column3",
	 * 				title:		"Spalte 3"
	 * 			}
	 * 		]
	 *	});
	 * </code>
	 */
	jControls.Grid = Class.create(jControls.Control,
	{
		/**
		 * die spalten
		 *
		 * @var array
		 * @access protected
		 */
		columns:	null,

		/**
		 * die einzelnen Spalten
		 *
		 * @var array
		 * @access protected
		 */
		columnsId:	null,

		/**
		 * die Daten
		 *
		 * @var array
		 * @access protected
		 */
		data:		null,

		/**
		 * pagelisting Controls
		 *
		 * @access public
		 * @var jControls.PageList
		 */
		pagelist: null,

		/**
		 * alle Doms aller rows
		 *
		 * @var array
		 * @access private
		 */
		rowDom:		null,

		/**
		 * nach dieser Spalte ist aktuell sortiert
		 *
		 * @var string
		 * @access protected
		 */
		sortBy:		null,

		/**
		 * die aktuelle spalte ist in diese richtung sortiert
		 *
		 * @var string
		 * @access protected
		 */
		sortOrder:	null,

		/**
		 * fügt die angegebenen classes der row hinzu
		 *
		 * @param object row
		 * @param string className
		 * @access public
		 * @return this
		 */
		addRowClass: function(row, className)
		{
			this.rowDom[row._index].dom.addClass(className);
			row.cls = (row.cls + " " + className).strip();

			return this;
		},

		/**
		 * fügt eine TR an die Row TR an
		 *
		 *	options
		 *		name					type				default		description
		 *		=====================================================================================================================
		 *		cls			optional	string
		 *		html		optional	string
		 *		style		optional	object				display:none;padding:3px 3px 3px 5px;white-space:normal;
		 *
		 * @param object|node row
		 * @param array|object options
		 * @access public
		 * @return this
		 */
		appendTRToRow: function(row, options)
		{
			if (options.constructor !== Array)
			{
				options = [options];
			}
			var element = row && row.nodeType == 1 ? row : this.rowDom[row._index].dom;
			options.each(function(option)
			{
				var lastTr = element.down("tr");
				if (lastTr.next())
				{
					lastTr = lastTr.nextSiblings().last();
				}

				Element.insert(lastTr,
				{
					after:
						"<tr>" +
							"<td colspan='" + this.columns.length + "' class='jcGrid-col jcGrid-cell'>" +
								"<div" +
									" style='" + (option.style ? Object.toStyleString(option.style) : "display:none;padding:3px 3px 3px 5px;white-space:normal;") + "'" +
									(option.cls ? " class='" + option.cls + "'" : "") +
								">" +
									(option.html || "") +
								"</div>" +
							"</td>" +
						"</tr>"
				})
			}, this);

			return this;
		},

		/**
		 * clonet die daten von den options in die class
		 *
		 * @access private
		 */
		cloneData: function()
		{
			this.data = this.options.data.collect(function(row, rowIndex)
			{
				var clRow = Object.clone(row);
				clRow._index = rowIndex;
				return clRow;
			}, this);
		},

		/**
		 * löscht eine zeile aus der liste
		 *
		 * @param object delRow
		 * @access public
		 * @return this
		 */
		deleteRow: function(delRow)
		{
			var delPos = delRow._index;
			if (this.rendered)
			{
				this.rowDom[delPos].dom.remove();
				gridSetRowClassAlternate(this.dom);
			}

			gridDeleteRowData(this, delPos);

			this.data.each(function(row, rowIndex)
			{
				row._index = rowIndex;
			});

			return this;
		},

		/**
		 * liefert den angegebene Index
		 *
		 * @param long|string index der index oder id (id-match bevorzugt bie gleichem datentyp)
		 * @access public
		 * @return object
		 */
		getRow: function(index)
		{
			if (typeof index != "string" && typeof index != "number")
			{
				return index;
			}

			/* allgemeine such über row.id erstmal */
			this.data.find(function(row, rowIndex)
			{
				if (row.id == index && typeof row.id == typeof index)
				{
					index = rowIndex;
				}
				return row.id == index && typeof row.id == typeof index;
			});

			/* gefunden? */
			return this.getRowByIndex(index);
		},

		/**
		 * liefert die Row, welche zu einer Spalte mit dem angegebenen wert passt
		 *
		 * @param string column
		 * @param mixed value
		 * @access public
		 * @return object
		 */
		getRowByColumnValue: function(column, value)
		{
			return this.data.find(function(row)
			{
				if (!row[column])
				{
					return;
				}
				return row[column] == value;
			});
		},

		/**
		 * liefert die Row anhand des Indexes
		 *
		 * @param int index
		 * @access public
		 * @return object
		 */
		getRowByIndex: function(index)
		{
			if (Object.isUndefined(this.data[index]))
			{
				return;
			}

			return this.data[index];
		},

		/**
		 * liefert den DOM Node für die Row
		 *
		 * @param object row
		 * @access public
		 * @return node
		 */
		getRowDom: function(row)
		{
			if (typeof row != "object")
			{
				row = this.getRow(row);
			}
			return (this.rowDom[row._index] ? this.rowDom[row._index].dom : null);
		},

		/**
		 * Konstruktor
		 *
		 * @param object options object mit den einstellungen und den entsprechenden Daten
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Grid.xType;
			}
			this.rowDom = [];

			/* merken */
			this.options = options;

			/* fehighlightendes setzen */
			this.options.header			= (typeof this.options.header		!= "undefined" ? this.options.header		: true);
			this.options.sortable		= (typeof this.options.sortable		!= "undefined" ? this.options.sortable		: true);
			this.options.multiselect	= (typeof this.options.multiselect	!= "undefined" ? this.options.multiselect	: false);
			if (Object.isUndefined(this.options.columnHighlight))
			{
				this.options.columnHighlight = false;
			}

			/* ids der Spalten ermitteln/prüfen */
			this.columns	= $A(options.columns);
			this._variableColumnWidth = 0;
			this.columnsId	= this.columns.collect(function(column, index)
			{
				column.type		= column.type || "string";
				column.visible	= (typeof column.visible != "undefined" ? column.visible : true);
				column.sortable	= (typeof column.sortable != "undefined" ? column.sortable : true);
				this.columns[column.id] = column;

				/* formatfunktionen machen und columns mit id setzen */
				if (column.format)
				{
					column.format = column.format.bind(this.options);
				}

				/* Column style */
				column.style = column.style || {};
				if (!column.visible)
				{
					column.style.display = "none";
				}
				if (column.visible && !column.style.width)
				{
					this._variableColumnWidth++;
				}
				column.styleText = Object.toStyleString(column.style);

				return column.id;
			}, this);
			if (this._variableColumnWidth > 0)
			{
				this._variableColumnWidth = Math.floor(100 / this._variableColumnWidth);
			}

			if (typeof this.options.pages == "boolean" && this.options.pages)
			{
				this.options.pages = {};
			}
			if (typeof this.options.forcePages == "undefined")
			{
				this.options.forcePages = true;
			}
			if (this.options.multiselect)
			{
				this.value = $A([]);
			}

			/****************************************************************
			 *																*
			 *	   bei Ajax Request wird Sort über URLs gehandelt			*
			 *																*
			 ***************************************************************/
			if (this.options.pages && this.options.ajax && Object.isUndefined(this.options.sortAjaxUrl))
			{
				this.options.sortable	= false;
				this.options.sortBy		= null;
				this.options.sort		= null;
			}

			$super(options);
		},

		/**
		 * fügt einen oder mehrere neue zeilen ein
		 *
		 * @param array|object newRow
		 * @param int|string|object beforeRow	vor dieser Row einfügen oder wenn nicht
		 *										angegeben ans enden anketten, nur sinnvoll wenn
		 *										aktuell keine sortierung
		 * @access public
		 * @return this
		 */
		insertRow: function(newRow, beforeRow)
		{
			if (!Object.isArray(newRow))
			{
				newRow = [newRow];
			}
			if (!Object.isUndefined(beforeRow))
			{
				beforeRow = this.getRow(beforeRow);
			}
			var newPos = (!beforeRow || this.sortBy != null ? this.data.length : beforeRow._index);
			if (this.rendered && beforeRow)
			{
				beforeRow = this.rowDom[beforeRow._index].dom;
			}

			newRow.each(function(newRow, rowIndex)
			{
				gridAppendRowData(this, newRow, newPos++);
				if (this.rendered)
				{
					var newRowDom = this.renderRow(newRow, this._gBody);
					if (beforeRow && this.sortBy == null)
					{
						Element.insert(beforeRow, {before: newRowDom.remove()});
					}
				}

			}, this);

			/* sortiert */
			if (this.sortBy != null)
			{
				this.sort(this.sortBy, this.sortOrder);
			}
			else
			{
				this.data.each(function(row, rowIndex) { row._index = rowIndex; });
			}

			if (this.rendered)
			{
				gridSetRowClassAlternate(this.dom);
			}

			return this;
		},

		/**
		 * auswahl umkehren
		 *
		 * @access public
		 * @return this
		 */
		invertSelection: function()
		{
			/* nur bei mulitselect */
			if (!this.options.multiselect)
			{
				return this;
			}
			if (this.value == null)
			{
				this.value = $A([]);
			}
			/* nix markiert dann alle */
			if (this.value.length == 0)
			{
				return this.selectAll();
			}
			/* alles markiert dann nix */
			if (this.value.length == this.data.length)
			{
				return this.selectNone();
			}

			var unsel = this.value.clone();
			/* markiertes markieren und umgekehrt */
			this.selectValue(this.data.findAll(function(row)
			{
				return this.value.indexOf(row.id) == -1;
			}, this));

			/* Markierung aufheben */
			this.unselectValue(unsel);

			return this;
		},

		/**
		 * prüft ob die Row gerendert ist
		 *
		 * @param object row
		 * @access public
		 * @return bool
		 */
		isRowRendered: function(row)
		{
			return (this.rowDom[row._index] ? this.rowDom[row._index].rendered : false);
		},

		/**
		 * row markiert?
		 *
		 * @param object row
		 * @access public
		 * @return bool
		 */
		isRowSelected: function(row)
		{
			if (!this.options.multiselect)
			{
				return this.value == row.id;
			}
			if (!this.value || this.value.constructor !== Array)
			{
				return false;
			}
			if (!row)
			{
				return false;
			}

			return (this.value.indexOf(row.id) != -1 ? true : false);
		},

		/**
		 * entfernt die angegebenen classes aus der row
		 *
		 * @param object row
		 * @param string className
		 * @access public
		 * @return this
		 */
		removeRowClass: function(row, className)
		{
			this.rowDom[row._index].dom.removeClass(className);
			row.cls = $w(row.cls).spliceArray($w(className)).join(" ");

			return this;
		},

		/**
		 * die struktur rendern
		 *
		 * @param string|node renderTo
		 * @access public
		 */
		render: function($super, renderTo)
		{
			var parents		= null;
			var html		= "";
			var cookie		= null;

			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			if (this.xType == jControls.Grid.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			/* Cookies holen */
			cookie = this.getCookie();
			if (this.options.sortable && cookie.sorted && this.columns[cookie.sortBy] && !this.options.ajax)
			{
				this.options.sortBy		= cookie.sortBy;
				this.options.sort		= cookie.sortOrder;
			}

			/* sortieren */
			if (this.options.sortable && this.options.sort && !this.options.ajax)
			{
				this.sort(this.options.sortBy || this.columnsId[0], this.options.sort);
			}
			else
			{
				this.cloneData();
			}

			if (this.xType == jControls.Grid.xType)
			{
				parents = renderTo.toggleDisplayed();
			}

			/**
			 * folgende strutkur wird erzeugt
			 *
			 *	<div CONTAINER>
			 *		<input type="hidden" ...>
			 *		HEAD
			 *		BODY
			 *	</div>
			 */

			/* page list anlegen */
			if (this.options.pages)
			{
				this.options.pages.current		= 1; /*this.options.pages.current || 1; */
				this.options.pages.entrycount	= this.options.pages.entrycount || this.options.data.length;
				this.options.pages.maxperpage	= this.options.pages.maxperpage || jControls.PageList.MAXPERPAGE;
				if (this.options.forcePages || this.options.pages.maxperpage < this.options.pages.entrycount)
				{
					if (!this.options.pages.listeners)
					{
						this.options.pages.listeners = {};
					}
					this.options.pages.listeners.onBeforePage = (function()
					{
/*						var pageLoaded = (this.options.ajax ? [this.options.pages.current] : true); */

						/* wenn ajax dann daten, option, rowDom mit dummy daten auffüllen */
						if (this.options.ajax)
						{
							for (var i = this.options.data.length; i < this.options.pages.entrycount; i++)
							{
								gridAppendRowData(this, {_template: true}, i);
							}
						}

						function showPage(grid, pagelist, oldPage, newPage)
						{
							/* noch nicht vorhandene vorrendern */
							for (var rowIndex = pagelist.getMinDisplay(newPage), max = pagelist.getMaxDisplay(newPage); rowIndex <= max; rowIndex++)
							{
								if (grid.data[rowIndex] && (!grid.rowDom[rowIndex] || !grid.rowDom[rowIndex].rendered))
								{
									grid.renderRow(grid.data[rowIndex], grid._gBody, false, true);
								}
							}

							/* anzeigen und verstecken */
							for (var i = 0, minPageOld = pagelist.getMinDisplay(oldPage), minPageNew = pagelist.getMinDisplay(newPage); i < pagelist.maxperpage; i++, minPageOld++, minPageNew++)
							{
								if (grid.rowDom[minPageNew] && grid.rowDom[minPageNew].rendered)
								{
									grid.rowDom[minPageNew].dom.show();
									grid.updateRow(grid.rowDom[minPageNew].dom, minPageNew, grid.data[minPageNew]);
								}
								if (grid.rowDom[minPageOld] && grid.rowDom[minPageOld].rendered)
								{
									grid.rowDom[minPageOld].dom.hide();
								}
							}
						}

						/* die alten Rows verstecken */
						function hideRows(grid, pagelist, page)
						{
							for (var rowIndex = pagelist.getMinDisplay(page), max = pagelist.getMaxDisplay(page); rowIndex <= max; rowIndex++)
							{
								if (grid.rowDom[rowIndex] && grid.rowDom[rowIndex].rendered)
								{
									grid.rowDom[rowIndex].dom.hide();
								}
							}
						}

						return (function(pagelist, oldPage, newPage)
						{
							if (typeof this.pagelist.pageLoaded == "boolean" || this.pagelist.pageLoaded.indexOf(newPage) != -1)
							{
								showPage(this, pagelist, oldPage, newPage);
								return;
							}

							/* ajax options aufbereiten */
							var ajaxOptions = Object.clone(this.options.ajax);
							var on =
							{
								success:	ajaxOptions.onSuccess || Prototype.emptyFunction
							};
							if (!ajaxOptions.parameters)
							{
								ajaxOptions.parameters = {};
							}

							/* parameter anpassen */
							ajaxOptions.parameters.pagenr			= newPage;
							ajaxOptions.parameters.oldPage			= oldPage;
							ajaxOptions.parameters.newPage			= newPage;
							ajaxOptions.parameters.pageEntryMin		= pagelist.getMinDisplay(newPage);
							ajaxOptions.parameters.pageEntryMax		= pagelist.getMaxDisplay(newPage);
							ajaxOptions.parameters.pageMaxPerPage	= pagelist.maxperpage;

							/* Callback */
							if (this.listeners.onBeforeAjaxRequest && this.listeners.onBeforeAjaxRequest(this, ajaxOptions) === false)
							{
								return;
							}

							/* Callback im Ajax Request */
							ajaxOptions.onSuccess = (function(data)
							{
								/* diese seite merken das sie drin ist */
								this.pagelist.pageLoaded.push(newPage);
								if (this.listeners.onBeforeAjaxRequestSuccess && this.listeners.onBeforeAjaxRequestSuccess(this, ajaxOptions, data) === false)
								{
									return;
								}

								on.success(data);

								/* übernehmen und rendern */
								var rowIndex = pagelist.getMinDisplay(newPage);
								data.each(function(newRow, newRowIndex)
								{
									gridDeleteRowData(this, rowIndex); /*platzhalter löschen */
									gridAppendRowData(this, newRow, rowIndex); /* neues einfügen */
									this.renderRow(this.data[rowIndex], this._gBody, false, true); /* rendern */
									rowIndex++;
								}, this);

								showPage(this, pagelist, oldPage, newPage);

								if (this.listeners.onAfterAjaxRequestSuccess)
								{
									this.listeners.onAfterAjaxRequestSuccess(this, ajaxOptions, data);
								}
							}).bind(this);

							/* Request auslösen */
							jControls.Ajax.Request(this, ajaxOptions);

							/* Callback */
							if (this.listeners.onAfterAjaxRequest)
							{
								this.listeners.onAfterAjaxRequest(this, ajaxOptions);
							}
						}).bind(this);
					}).bind(this)();

					/* pagelist erzeugen und rendern aber zeitversetzt */
					this.pagelist = new jControls.PageList(this.options.pages);
					this.pagelist.pageLoaded = (this.options.ajax ? [this.options.pages.current] : true);
					this.pagelist.render.bind(this.pagelist).defer(this);
				}
			}

			/* Container */
			$super(renderTo);

			/* input */
			if (this.name && this.name != "" && !this.options.multiselect)
			{
				html = html + "<input type='hidden' name='" + this.name + "' value='" + (this.value || "") + "' />";
			}

			/* Spaltenkopf */
			if (this.options.header)
			{
				if (Object.isArray(this.options.columnStyles))
				{
					html = html + renderHeaderComplex(this);
				}
				else
				{
					html = html + renderHeaderSimple(this);
				}
			}

			/* body */
			html = html + "<div class='jcGrid-scroller'><div class='jcGrid-body'></div></div>";

			/* Footer */
			if (this.options.footer)
			{
				html = html + renderFooter(this);
			}

			/* einfügen */
			this._gBody = Element.insert(this.dom,
			{
				bottom: html
			}).down(".jcGrid-body");

			/* Body correct setzen */
			correctBodyHeight(this);

			/* row zeichnen */
			var rowIndexMin = (this.pagelist ? this.pagelist.getMinDisplay() : 0);
			var rowIndexMax = (this.pagelist ? this.pagelist.getMaxDisplay() : this.data.length - 1);
			this.data.each(function(row, rowIndex)
			{
				if (rowIndexMin <= rowIndex && rowIndex <= rowIndexMax)
				{
					this.renderRow(row, this._gBody);
				}
			}, this);

			/* input Container setzen */
			if (this.name && this.name != "" && !this.options.multiselect)
			{
				this.inputContainer = this.dom.down("input", 0);
			}

			/* sort column setzen */
			this.setSortColumn.bind(this).defer(this.options.sortBy, this.options.sort);

			if (this.xType == jControls.Grid.xType)
			{
				renderTo.toggleDisplayed(parents);
				if (this.listeners.onAfterRender)
				{
					this.listeners.onAfterRender.defer(this);
				}
			}
		},

		/**
		 * rendert die Row an die entsprechende stelle
		 *
		 * @param object row
		 * @param node renderTo
		 * @param bool valueShow
		 * @param bool renderHidden
		 * @access public
		 * @return node
		 */
		renderRow: function(row, renderTo, valueShow, renderHidden)
		{
			/**
			 * folgende Struktur wird für eine zeile erzeugt
			 *
			 *	<div ROW>
			 *		<table>
			 *			<tbody>
			 *				<tr>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					...
			 *				<tr>
			 *			</tbody>
			 *		</table>
			 *	</div>
			 */

			row._index = row._index || 0;
			if (typeof this.rowDom[row._index] == "undefined")
			{
				this.rowDom[row._index] = {
					rendered:	false,
					dom:		null
				};
			}
			if (!valueShow && this.rowDom[row._index].rendered)
			{
				return;
			}
			row.visible	= (typeof row.visible != "undefined" ? row.visible : true);

			if (!valueShow && this.listeners.onBeforeRenderRow && this.listeners.onBeforeRenderRow(this, row) === false)
			{
				return;
			}

			var tdhtml = "";
			this.columns.each(function(column, columnIndex)
			{
				var cell = {
					style:		{},
					visible:	true
				};
				if (row.cell && row.cell[column.id])
				{
					cell = row.cell[column.id] || {};
				}
				if (Object.isUndefined(cell.visible))
				{
					cell.visible = true;
				}

				tdhtml = tdhtml +
							"<td" +
								(cell.id ? " id='" + cell.id + "'" : "") +
								" class='" +
									"jcGrid-col " +
									"jcGrid-cell " +
									"jcGrid-cell-td-" + column.id + " " +
									"jcGrid-cell-td-" + columnIndex +
									(columnIndex == 0 ? " jcGrid-cell-td-first" : "") +
									(columnIndex == this.columns.length - 1 ? " jcGrid-cell-td-last" : "") +
									(cell.cls ? " " + cell.cls : "") +
								"'" +
								" style='" +
									column.styleText +
									" " + Object.toStyleString(cell.style) +
									" " + (!cell.visible ? "display:none;" : "") +
									(cell.visible && column.visible && !column.style.width ? "width:" + this._variableColumnWidth + "%;" : "") +
								"'" +
								(cell.qtip ? "title='" + cell.qtip + "'" : "" ) +
								">" +
									"<div" +
										" class='jcGrid-cell-inner jcGrid-cell-inner-" + column.id + " jcGrid-cell-inner-" + columnIndex + (columnIndex == 0 ? " jcGrid-cell-inner-first" : "") + (columnIndex == this.columns.length - 1 ? " jcGrid-cell-inner-last" : "") + "'" +
										">" +
											(column.format ? column.format(row[column.id]) : row[column.id]) +
									"</div>" +
							"</td>";
			}, this);

			var rowStyle = Object.clone(row.style || {});
			rowStyle.width = "100%";
			if (renderHidden || (!valueShow && (!row.visible || (this.pagelist && !this.pagelist.isBetween(row._index)))))
			{
				rowStyle.display = "none";
			}

			var rowDom = Element.insert(renderTo,
			{
				bottom:
					"<div" +
						(!valueShow && row.domId ? " id='" + row.domId + "'" : "") +
						" class='" +
								"jcGrid-row" +
								(row._index == 0 ? " jcGrid-row-first" : "") +
								(row._index == this.data.length - 1 ? " jcGrid-row-last" : "") +
								(this.pagelist && (row._index + 1) % this.pagelist.maxperpage == 0 ? " jcGrid-row-last-on-page" : "") +
								(!valueShow && row._index % 2 ? " jcGrid-row-alt" : "") +
								(!valueShow && this.isRowSelected(row) ? " jcGrid-row-selected" : "") +
								(row.cls ? " " + row.cls : "") +
							"'" +
						(!Object.isUndefined(row.qtip) && !valueShow ? " title='" + row.qtip + "'" : "") +
						" style='" + Object.toStyleString(rowStyle) + "'" +
						">" +
							"<table cellspacing='0' cellpadding='0' border='0' class='jcGrid-row-table' style='width:100%;'>" +
								"<tbody>" +
									"<tr>" +
										tdhtml +
									"</tr>" +
								"</tbody>" +
							"</table>" +
					"</div>"
			}).childElements().last();

			if (!valueShow)
			{
				(function()
				{
					if (this.listeners.onMouseOverRow)
					{
						Event.observe(rowDom, "mouseover", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseOverRow(this, this.getRowByIndex(row._index));
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onMouseOutRow)
					{
						Event.observe(rowDom, "mouseout", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseOutRow(this, this.getRowByIndex(row._index));
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onMouseMoveRow)
					{
						Event.observe(rowDom, "mousemove", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseMoveRow(e, this, this.getRowByIndex(row._index));
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onBeforeClickRow	|| this.listeners.onAfterClickRow || this.enterable)
					{
						Event.observe(rowDom, "click", (function(e)
						{
							if (ONCLICK_IGNORE_ELEMENTTAG.indexOf(e.element().tagName.toUpperCase()) != -1)
							{
								return;
							}
							if (this.listeners.onBeforeClickRow && this.listeners.onBeforeClickRow(this, this.getRowByIndex(row._index)) === false)
							{
								return;
							}
							if (this.enterable && this.enabled)
							{
								this.setValue(this.getRowByIndex(row._index));
							}
							if (this.listeners.onAfterClickRow)
							{
								this.listeners.onAfterClickRow.defer(this, this.getRowByIndex(row._index));
							}
						}).bindAsEventListener(this));
					}
				}).bind(this).defer();
			}

			if (!valueShow)
			{
				this.rowDom[row._index].rendered	= true;
				this.rowDom[row._index].dom			= rowDom;
				if (this.listeners.onAfterRenderRow)
				{
					this.listeners.onAfterRenderRow.defer(this, row, rowDom);
				}
			}

			return rowDom;
		},

		/**
		 * scroll zu der entsprechende Zeile
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		scrollTo: function(row)
		{
			var dom = this.getRowDom(row);
			var scroller = this.dom.down(".jcGrid-scroller");

			scroller.scrollTop = 0;
			scroller.scrollTop = dom.getY() - scroller.getY() - (!Prototype.Browser.isIE ? dom.getBorderWidth("t") : 0);

			return this;
		},

		/**
		 * alles markieren
		 *
		 * @access public
		 * @return this
		 */
		selectAll: function()
		{
			if (!this.options.multiselect)
			{
				return this;
			}
			return this.selectValue(this.data);
		},

		/**
		 * nichts markieren
		 *
		 * @access public
		 * @return this
		 */
		selectNone: function()
		{
			if (this.value == null)
			{
				this.value = $A([]);
			}
			if (this.value.length == 0)
			{
				return this;
			}

			/* onBeforeChange */
			if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, $A([])) === false)
			{
				return this;
			}
			this.value = $A([]);

			/* onChange */
			if (this.listeners.onChange)
			{
				this.listeners.onChange.defer(this, this.value, this.initialized);
			}

			/* markierung setzen */
			this.setSelection();

			return this;
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @access public
		 * @return this
		 */
		selectValue: function(value)
		{
			if (!this.options.multiselect)
			{
				return this.setValue(value);
			}
			if (this.value == null)
			{
				this.value = $A([]);
			}

			if (Object.isArray(value))
			{
				value = value.findAll(function(newvalue)
				{
					return this.value.indexOf(typeof newvalue.id != "undefined" ? newvalue.id : newvalue) == -1;
				}, this);
				if (value.find(function(nvalue)
				{
					var newValue = this.getRow(nvalue);
					if (!newValue)
					{
						return;
					}

					/* onBeforeChange */
					if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, nvalue) === false)
					{
						return true;
					}

					/* wert setzen */
					var oldValue = this.value;
					this.value.push(newValue.id);
					this.renderValue(oldValue, this.value);

					/* onChange */
					if (this.listeners.onChange)
					{
						this.listeners.onChange.defer(this, value, this.initialized);
					}
				}, this)) return this;

				/* markierung setzen */
				this.setSelection();

			}
			else
			{
				var newValue = this.getRow(value);
				if (!newValue)
				{
					return;
				}
				if (this.value.find(function(selValue)
				{
					return selValue == newValue.id;
				}))
				{
					return this;
				}

				/* onBeforeChange */
				if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, value) === false)
				{
					return this;
				}

				/* wert setzen */
				var oldValue = this.value;
				this.value.push(newValue.id);
				this.renderValue(oldValue, this.value);

				/* onChange */
				if (this.listeners.onChange)
				{
					this.listeners.onChange.defer(this, value, this.initialized);
				}

				/* markierung setzen */
				this.setSelection();
			}

			return this;
		},

		/**
		 * setzt einen neuen wert für eine Zelle in einer zeile
		 *
		 * @param object row
		 * @param string column
		 * @param mixed value
		 * @param bool update
		 * @access public
		 * @return this
		 */
		setCellValue: function(row, column, value, update)
		{
			update = (typeof update == "undefined" ? true : update);

			this.options.data[row._index][column] = value;
			this.data[row._index][column] = value;

			if (this.options.data[row._index]._convertedData && this.options.data[row._index]._convertedData[column])
			{
				this.options.data[row._index]._convertedData[column] = convertToDataType(this.options.data[row._index][column].stripTags(), this.columns[column].type);
			}

			if (update)
			{
				if (this.sortBy != null && this.sortBy == column)
				{
					this.sort(this.sortBy, this.sortOrder);
				}
				else
				{
					this.updateRow(this.getRowDom(row), row._index, row);
				}
			}

			return this;
		},

		/**
		 * speichert neue cookie data ab
		 *
		 * @param object options
		 * @access public
		 * @return this
		 */
		setCookie: function($super, options)
		{
			if (!this.options.ajax)
			{
				options = Object.extend(options || {},
				{
					sorted:		this.sortBy != null,
					sortBy:		this.sortBy,
					sortOrder:	this.sortOrder
				})
			}

			return $super(options);
		},

		/**
		 * setzt die markierung
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		setSelection: function(row)
		{
			if (!this.rendered)
			{
				return this;
			}

			if (!this.options.multiselect)
			{
				if (!row)
				{
					row = this.value;
				}
				if (row != null)
				{
					row = this.getRow(row);
					if (!row)
					{
						return this;
					}

					this.dom.down(".jcGrid-row").removeClass("jcGrid-row-selected").nextSiblings().invoke("removeClass", "jcGrid-row-selected");
					var rowNode = (
									row._index == 0
										? this.dom.down(".jcGrid-body").down(".jcGrid-row")
										: this.dom.down(".jcGrid-body").down(".jcGrid-row").nextSiblings()[row._index - 1]
									);
					if (rowNode)
					{
						rowNode.addClass("jcGrid-row-selected");
					}

				}
				else
				{
					this.dom.down(".jcGrid-row").removeClass("jcGrid-row-selected").nextSiblings().invoke("removeClass", "jcGrid-row-selected");
				}
			}
			else if (Object.isArray(this.value))
			{
				this.dom.select(".jcGrid-row-selected").invoke("removeClass", "jcGrid-row-selected");
				if (!Object.isUndefined(this.name) && this.name != "")
				{
					this.dom.select(".jcGrid-input-multiselect").invoke("remove");
				}

				this.value.each(function(selValue)
				{
					var node = this.getRowDom(this.getRow(selValue));
					if (node)
					{
						node.addClass("jcGrid-row-selected");
					}

					if (!Object.isUndefined(this.name) && this.name != "")
					{
						new jControls.Container(
						{
							type:		"input",
							cls:		"jcGrid-input-multiselect",
							attributes:
							{
								type:	"hidden",
								name:	this.name,
								value:	selValue
							},
							renderTo:	this
						});
					}
				}, this);

			}
			else
			{
				this.dom.select(".jcGrid-row-selected").invoke("removeClass", "jcGrid-row-selected");
			}

			return this;
		},

		/**
		 * setzt die sortiertspalte
		 *
		 * @param string columnId
		 * @param string direction
		 * @acces public
		 * @return this
		 */
		setSortColumn: function(columnId, direction)
		{
			var headers = this.dom.down(".jcGrid-header");

			if (!headers)
			{
				return;
			}

			/* spaltentitel neu setzen */
			this.sortBy		= columnId;
			this.sortOrder	= direction;

			/* altes entfernen */
			headers.select(".jcSort-asc", ".jcSort-desc").invoke("removeClass", "jcSort-asc jcSort-desc");

			/* neue setzen */
			if (this.sortBy != null && this.sortOrder != null)
			{
				headers.down(".jcGrid-header-hd-" + this.sortBy).addClass("jcSort-" + this.sortOrder);
			}
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @param bool initialized
		 * @access public
		 * @return this
		 */
		setValue: function($super, value, initialized)
		{
			if (this.options.multiselect)
			{
				if (value && value.constructor === Array)
				{
					value.each(function(newvalue)
					{
						this.setValue(newvalue);
					}, this);
					return this;
				}
				else
				{
					return	(
								this.isRowSelected(this.getRow(value))
								? this.unselectValue(value)
								: this.selectValue(value)
							);
				}
			}

			if (value != null && !Object.isUndefined(value))
			{
				var newValue = (typeof value == "object" ? value : this.getRow(value));

				if (newValue)
				{
					$super(newValue.id, initialized);
					this.setSelection(newValue);
				}
				else
				{
					this.setValue(null);
				}

			}
			else
			{
				$super(value, initialized);
				this.setSelection(null);
			}

			return this;
		},

		/**
		 * die Daten nach einer spalte sortieren
		 *
		 * @param int|string	sortBy	nach diesem Key oder diesem Spaltenindex sortieren
		 * @param string		order	sortierrichtung ("asc" | "desc", default: "asc")
		 * @access public
		 */
		sort: function(sortBy, order)
		{
			if (this.listeners.onBeforeSort && this.listeners.onBeforeSort(this, sortBy, order) === false)
			{
				return;
			}

			this.sortBy		= sortBy;
			this.sortOrder	= (order || "asc").toLowerCase();

			if (this.rendered)
			{
				this.setCookie();
			}

			/* kein ajax dann normal sortieren */
			/* aber wenn ajax und alle seiten schon da sind dann doch sortieren */
			if (!this.options.ajax /*|| (this.options.ajax && this.pagelist.pageLoaded.length == this.pagelist.count)*/)
			{
				this.options.data.sort((function(left, right)
				{
					if (!left._convertedData)
					{
						left._convertedData = [];
					}
					if (!left._convertedData[this.sortBy])
					{
						left._convertedData[this.sortBy] = convertToDataType(String(left[this.sortBy]).stripTags(), this.columns[this.sortBy].type);
					}
					var a = left._convertedData[this.sortBy];

					if (!right._convertedData)
					{
						right._convertedData = [];
					}
					if (!right._convertedData[this.sortBy])
					{
						right._convertedData[this.sortBy] = convertToDataType(String(right[this.sortBy]).stripTags(), this.columns[this.sortBy].type);
					}
					var b = right._convertedData[this.sortBy];

					if (Object.isString(a) || Object.isString(b))
					{
						[
							{from: "Ä", to: "Ae"},
							{from: "ä", to: "ae"},
							{from: "Ö", to: "Oe"},
							{from: "ö", to: "oe"},
							{from: "Ü", to: "Ue"},
							{from: "ü", to: "ue"},
							{from: "ß", to: "ss"}
						].each(function(sign)
						{
							if (Object.isString(a))
							{
								a = a.replace(sign.from, sign.to);
							}
							if (Object.isString(b))
							{
								b = b.replace(sign.from, sign.to);
							}
						});
					}
					if (this.sortOrder == "asc")
					{
						return a < b ? -1 : a > b ? 1 : 0;
					}
					else
					{
						return a > b ? -1 : a < b ? 1 : 0;
					}
				}).bind(this));

				this.cloneData();
				if (this.listeners.onAfterSort)
				{
					this.listeners.onAfterSort.defer(this, sortBy, order);
				}

				this.update();
			}
			/* seiten sind mit ajax und wir haben eine sortier URL */
			else if (this.options.ajax && !Object.isUndefined(this.options.sortAjaxUrl) && this.sortBy && this.sortOrder)
			{
				window.location.href = this.options.sortAjaxUrl.interpolate(
				{
					columnId:	this.sortBy,
					direction:	this.sortOrder,
					pageId:		(this.pagelist ? this.pagelist.getPage() : "")
				});

				if (this.listeners.onAfterSort)
				{
					this.listeners.onAfterSort.defer(this, sortBy, order);
				}
			}
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @access public
		 * @return this
		 */
		unselectValue: function(value)
		{
			if (!this.options.multiselect)
			{
				return this.setValue(null);
			}
			if (this.value == null)
			{
				return this;
			}

			if (Object.isArray(value))
			{
				value.each(function(newvalue) { this.unselectValue(newvalue); }, this);
				return this;
			}

			var newValue = this.getRow(value);
			if (!newValue)
			{
				return;
			}
			if (!this.value.find(function(selValue)
			{
				return selValue == newValue.id;
			}))
			{
				return this;
			}

			/* onBeforeChange */
			if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, value) === false)
			{
				return this;
			}

			/* wert setzen */
			var oldValue = this.value;
			this.value = this.value.without(newValue.id);
			this.renderValue(oldValue, this.value);

			/* onChange */
			if (this.listeners.onChange)
			{
				this.listeners.onChange.defer(this, value, this.initialized);
			}

			/* markierung setzen */
			this.setSelection();
		},

		/**
		 * aktualisiert die Daten in der ansicht
		 *
		 * @access public
		 * @return this
		 */
		update: function()
		{
			if (!this.rendered)
			{
				return this;
			}

			if (this.listeners.onBeforeUpdate && this.listeners.onBeforeUpdate(this) === false)
			{
				return this;
			}

			/* spaltentitel neu setzen */
			this.setSortColumn(this.sortBy, this.sortOrder);

			/* Zelleninhalte neu setzen */
			this.dom.down(".jcGrid-body").select(".jcGrid-row").each(function(rowNode, rowIndex)
			{
				if (!rowNode.isDisplayed())
				{
					return;
				}
				this.updateRow(rowNode, rowIndex, this.data[rowIndex]);
			}, this);
			this.setSelection();

			if (this.listeners.onAfterUpdate)
			{
				this.listeners.onAfterUpdate.defer(this);
			}

			return this;
		},

		/**
		 * aktualisiert eine Zeile
		 *
		 * @param node rowNode
		 * @param int rowIndex
		 * @param object row
		 * @access private
		 */
		updateRow: function(rowNode, rowIndex, row)
		{
			if (this.listeners.onBeforeUpdateRow && this.listeners.onBeforeUpdateRow(this, row, rowNode) === false)
			{
				return;
			}

			/* alles an der Zeile entsprechend machen */
			rowNode.clearStyle(Object.keys(rowNode.collectStyles()).without("display"));
			if (row.style)
			{
				rowNode.setStyle(row.style);
			}
			rowNode.removeClass($w(rowNode.className).findAll(function(cls)
			{
				return !cls.startsWith("jc");
			}).join(" "));
			rowNode.addClass(row.cls);

			/* nun jede zelle durchlaufen */
			rowNode.select(".jcGrid-cell").each(function(cellNode, columnIndex)
			{
				var column = this.columnsId[columnIndex];
				var cell = {
					style:		{},
					visible:	true
				};
				if (row.cell && row.cell[column])
				{
					cell = row.cell[column] || {};
				}
				if (Object.isUndefined(cell.visible))
				{
					cell.visible = true;
				}

				var innerCell = cellNode.down(".jcGrid-cell-inner");
				if (innerCell)
				{
					innerCell.innerHTML = "";
					Element.insert(innerCell, this.columns[column].format ? this.columns[column].format(row[column]) : row[column]);
				}

				cellNode.id			= (cell.id ? " id='" + cell.id + "'" : "");
				cellNode.className	= 	"jcGrid-col " +
										"jcGrid-cell " +
										"jcGrid-cell-td-" + column + " " +
										"jcGrid-cell-td-" + columnIndex +
										(columnIndex == 0 ? " jcGrid-cell-td-first" : "") +
										(columnIndex == this.columns.length - 1 ? " jcGrid-cell-td-last" : "") +
										(cell.cls ? " " + cell.cls : "");
				cellNode.clearStyle(Object.keys(cellNode.collectStyles()));

				var columnData = this.columns[column] ||
				{
					styleText: 	"",
					style:		{}
				};
				cellNode.setStyle(
					columnData.styleText +
					" " + Object.toStyleString(cell.style) +
					" " + (!cell.visible ? "display:none;" : "") +
					(cell.visible && columnData.visible && !columnData.style.width ? "width:" + this._variableColumnWidth + "%;" : "")
				);
				cellNode.quickTip(cell.qtip ? cell.qtip : "" );
			}, this);

			if (this.listeners.onAfterUpdateRow)
			{
				this.listeners.onAfterUpdateRow.defer(this, row, rowNode);
			}
		}
	});

	/**
	 * xTyp
	 */
	jControls.Grid.xType = xTypes.Grid;

	/**
	 * autoloadfunktion für Grid
	 *
	 * @param string selector Selektor (see Prototype/$$) (standard "table:not([class~=nogrid])" alle tabellen ausser die mit der class "nogrid")
	 * @access public
	 */
	jControls.Grid.autoload = function(selector)
	{
		if (jControls.Grid._autoloaded)
		{
			return;
		}
		jControls.Grid._autoloaded = true;

		autoload(function()
		{
			$$(selector || "table:not([class~=nogrid])").each(function(element)
			{
				if (element.hasClassName("jcContainer"))
				{
					return;
				}
				jControls.Grid.initBy(element);
			});
		});
	};

	/**
	 * bestimmt das Format eines Elements anhand seiner klassen
	 *
	 * @param string|node element
	 * @access public
	 * @return string
	 */
	jControls.Grid.getElementFormat = function(element)
	{
		element = $(element);
		return DATATYPES.find(function(cls)
		{
			return element.hasClassName(cls);
		}) || "string";
	};

	/**
	 * initialisiert ein Grid anhand einer/mehrerer tabelle
	 * mit den classNames an den THs (DATATYPES) kann der datentyp einer spalte definiert werden
	 * mit den classNames an den THs (sorted) kann die spalte als die Sortierspalte definiert werden (ASC)
	 * mit den classNames an den THs (sorted-asc, sorted-desc) kann die spalte als die Sortierspalte definiert werden mit der angegebenenung
	 * mit den classNames an den THs (nosort) kann festgelegt werden, das die spalte nicht sortierbar ist
	 * mit den classNames an der TABLE (nosort) kann festgelegt werden, das die tabelle nicht sortierbar ist
	 * mit den classNames an einer TH (rowid) wird der Inhalt dieser Spalte als row.id verwendet
	 * mit den classNames an einer TR (footer) wird diese Zeile als Footer deklariert und immer angezeigt
	 * mit den classNames an einer TR (selected) wird diese Zeile als Markiert deklariert
	 *
	 * @param node element
	 * @param node element
	 * @param node ...
	 * @param object options initialoptions
	 * @access public
	 * @return new jControls.Grid
	 */
	jControls.Grid.initBy = function()
	{
		/**
		 * liefert die column daten zu einer tabelle
		 *
		 * @param node element
		 * @access private
		 * @return object
		 */
		var getColumnsData = function(element)
		{
			var result = {
				columns:		$A([]),	/* relevant TD/THs, welche für die Columns wichtig sind */
				columnStyles:	$A([]),	/* regeln für den bau des headers */
				lastHeaderTR:	null	/* erste Datenzeile */
			};

			var isComplexColumnStyle = false;

			/* alle TRs dieser Tabelle holen, welche relevant sind */
			/* haben wir einen thead dann nur diesen prüfen */
			var parentTR = element.down("thead");
			/* kein head dann auf body prüfen */
			if (!parentTR)
			{
				parentTR = element.down("tbody");
			}
			/* kein body dann das erste tr und davon den eltern node */
			if (!parentTR)
			{
				parentTR = element.down("tr:first");
				if (parentTR)
				{
					parentTR = parentTR.parent();
				}
			}

			/* nix gefunden das is ein problem... ist die Tabelle etwa leer? */
			if (!parentTR)
			{
				return result;
			}

			/* nun alle relevanten TRs finden, welche THs haben */
			var columnTRs = parentTR.select("tr th:first-child");
			/* keine THs enthalten dann die ersten TDs */
			if (columnTRs.length == 0)
			{
				columnTRs = $A([parentTR.down("tr td:first-child")]);
			}
			/* keine columns gefunden wech hier */
			if (columnTRs.length == 0)
			{
				return result;
			}

			/* nun die relevanten TRs finden */
			columnTRs = columnTRs.collect(function(childTR)
			{
				return childTR.parent();
			}).findAll(function(tr)
			{
				return tr.parent() == parentTR;
			});

			/* nix gefunden, das ist nicht gut */
			if (columnTRs.length == 0)
			{
				return result;
			}

			/* styles vorbereiten */
			result.columnStyles = $A($R(0, columnTRs.length - 1));

			/* alle TRs durchlaufen */
			columnTRs.each(function(columnTR, indexTR)
			{
				var columnIndex = 0;

				/* suchen bis wir eine noch nicht belegte spalte finden und */
				/* dort loslegen können, weil sonst rowspan überlappt */
				if (indexTR != 0 && Object.isArray(result.columnStyles[indexTR]))
				{
					/* ersten index in der zeile finden, wo noch keine Daten sind */
					for (columnIndex = 0; !Object.isUndefined(result.columnStyles[indexTR][columnIndex]) && columnIndex < result.columnStyles[indexTR].length; columnIndex++)
					{

					}
				}

				/* nun alle kinder der TR durchlaufen */
				columnTR.childElements().each(function(columnTH, indexTH)
				{
					var node = columnTH;
					var colspan = parseInt(node.readAttribute("colspan")) || 1;
					var rowspan = parseInt(node.readAttribute("rowspan")) || 1;

					if (colspan != 1 || rowspan != 1)
					{
						isComplexColumnStyle = true;
					}

					/* wenn diese Zeile bis zum untersten Rand des Spaltenkopfs ragt (Data Column) */
					/* dann ist diese Zeile eine der Spaltenköpfe, welche angeklickt */
					/* werden können und sortierbar erscheinen */
					/* aktueller Zeilenindex (indexTR) + rowspan der Zelle = Anzahl der TRs */
					var isDataCell = indexTR + rowspan == columnTRs.length;

					/* nun für die colspans und rowspans die Dummys anlegen */
					/* dabei wird (colspan, rowspan) angenommen */
					/* Style für die Zelle (0, 0) wird definitiv gezeichnet */

					/* cols */
					for (var i = 0; i < colspan; i++)
					{
						/* rows */
						for (var j = 0; j < rowspan; j++)
						{
							/* is data column aber nur merken, wenn wir in rowspan = 0 sind */
							if (isDataCell && j == 0)
							{
								result.columns[columnIndex] = node;
								node = node.cloneNode(false);
								if (node.id)
								{
									node.id = node.id + "." + i;
								}
							}

							if (!Object.isArray(result.columnStyles[indexTR + j]))
							{
								result.columnStyles[indexTR + j] = $A([]);
							}

							/* Style für die Zelle... dieser Teil wird definitiv gezeichnet */
							/* dies ist quasi index (0, 0) */
							result.columnStyles[indexTR + j][columnIndex] =
							{
								"colspan":		colspan,
								dataCell:		isDataCell && j == 0,
								element:		columnTH,
								render:			i == 0 && j == 0,
								"rowspan":		rowspan
							};
						}
						columnIndex++;
					}
				});
			});

			/* letzte header row ausgeben */
			result.lastHeaderTR = columnTRs.last();

			/* nix complex dann simple mode */
			if (!isComplexColumnStyle)
			{
				result.columnStyles = undefined;
			}

			/* fertig */
			return result;
		};

		/**
		 * liefert die Header Informationen
		 *
		 * @param object options
		 * @access private
		 * @return object
		 */
		var getColumnOptions = function(element, options)
		{
			var columnData = getColumnsData(element);
			if (columnData.columns.length == 0)
			{
				return options;
			}

			options.columnStyles = columnData.columnStyles;

			/* erste Datenzeile finden */
			/* von letzter TR des Kopfs das eltern teil */
			options.firstDataTR = columnData.lastHeaderTR.parent();
			/* wenn das eltern teil der Table Head ist */
			if (Object.isElement(columnData.lastHeaderTR) && columnData.lastHeaderTR.tagName.toLowerCase() == "thead")
			{
				/* dann ab zum nächsten hoffentlich is es Table Body */
				options.firstDataTR = options.firstDataTR.next();
				if (options.firstDataTR) /* zumindest ist es irgendwas */
				{
					/* schauen was da kommt */
					options.firstDataTR = options.firstDataTR.down();
				}
			}
			/* das elternteil ist entweder der Table Body oder die Table */
			else if (Object.isElement(options.firstDataTR) && options.firstDataTR.tagName.toLowerCase() == "tbody" || options.firstDataTR.tagName.toLowerCase() == "table")
			{
				/* also is das nächste element von letzter TR des Kopfs die erste Daten TR oder nix */
				options.firstDataTR = columnData.lastHeaderTR.next();
			}
			/* hm also die erste DatenTR ist nix oder was wir haben is kein element */
			if ((Object.isElement(options.firstDataTR) && !options.firstDataTR.tagName.toLowerCase() == "tr") || !Object.isElement(options.firstDataTR))
			{
				options.firstDataTR = null;
			}

			/* wenn header nicht definiert ist dann anhand der informationen die wir haben */
			/* dh. wenn die erste gefundene Zelle eine TH ist dann haben wir ein header */
			if (Object.isUndefined(options.header))
			{
				options.header = columnData.columns.first().tagName.toLowerCase() == "th";
			}
			/* nix mit header */
			if (!options.header)
			{
				options.firstDataTR = columnData.lastHeaderTR;
			}

			/* nun die elemente der spalten entsprechend parsen */
			options.columns = columnData.columns.collect(function(element, columnIndex)
			{
				if (Object.isUndefined(options.columnIsRowId) && element.hasClassName("rowid"))
				{
					options.columnIsRowId = columnIndex;
				}
				if (element.hasClassName("sorted") || element.hasClassName("sorted-asc"))
				{
					options.sortBy	= element.id || ("column" + columnIndex);
					options.sort	= "asc";
				}
				else if (element.hasClassName("sorted-desc"))
				{
					options.sortBy	= element.id || ("column" + columnIndex);
					options.sort	= "desc";
				}

				return {
					cls:		element.className,
					id:			element.id || ("column" + columnIndex),
					qtip:		(element.tagName.toLowerCase() == "th" ? element.getQtipText() || undefined : undefined),
					sortable:	!element.hasClassName("nosort"),
					style:		element.collectStyles(),
					title:		(element.tagName.toLowerCase() == "th" ? element.innerHTML.strip() : ""),
					type:		jControls.Grid.getElementFormat(element),
					visible:	(element.getStyle("display") != "none" && element.getStyle("visibility") != "hidden")
				};
			});

			return options;
		};

		arguments = $A(arguments).flatten();
		if (arguments.length < 1 && arguments[0] === undefined)
		{
			return;
		}

		if ((arguments.length == 1 && (Object.isElement(arguments[0]) || Object.isString(arguments[0])))	||	/* nur ein element */
			(arguments.length == 2 && (Object.isElement(arguments[0]) || Object.isString(arguments[0])) && !Object.isElement(arguments[1]) && !Object.isString(arguments[1])))	/* ein element und eine option */
		{
			var element = $(arguments[0]);
			var options = arguments[1] || {};

			if (!element)
			{
				return;
			}
			if (element.tagName.toUpperCase() != "TABLE")
			{
				return;
			}
			element = element.cleanWhitespaceRecursive();

			/* unsere options */
			options = Object.extend(
			{
				cls:			element.className,
				columns:		[],
				enterable:		false,
				id:				element.id || undefined,
				renderReplace:	element,
				sortable:		!element.hasClassName("nosort")
			}, options);

			if (!options.title && element.getQtipText())
			{
				options.title = element.getQtipText();
			}

			options.style = Object.extend(element.collectStyles(), options.style || {});
			options.columns = [];

			/* spalten holen */
			options = getColumnOptions(element, options);

			var trElements		= $A([]);
			var selectedValues	= $A([]);

			if (options.firstDataTR)
			{
				trElements = $A([options.firstDataTR]).concat(options.firstDataTR.nextSiblings());
			}

			/* nur wenn daten da sind */
			if (trElements.length != 0)
			{
				options.data = trElements.findAll(function(tr, index)
				{
					return !tr.hasClassName("footer");
				}).collect(function(tr, rowIndex)
				{
					var tdElements = $A([tr.down("td")]);
					tdElements = tdElements.concat(tdElements.first().nextSiblings());

					if (tr.hasClassName("selected"))
					{
						selectedValues.push(rowIndex);
						tr.removeClass("selected");
					}

					return tdElements.inject(
					{
						cls:		tr.className,
						domId:		tr.id || undefined,
						id:			"row" + rowIndex,
						qtip:		tr.getQtipText() || undefined,
						style:		tr.collectStyles(),
						visible:	(tr.getStyle("display") != "none" && tr.getStyle("visibility") != "hidden")
					}, function(row, td, columnIndex)
					{
						var columnId	= options.columns[columnIndex].id;
						if (!Object.isUndefined(options.columnIsRowId) && options.columnIsRowId == columnIndex)
						{
							row.id = td.innerHTML.strip();
						}

						if (Object.isUndefined(row.cell))
						{
							row.cell = {};
						}
						row.cell[columnId]			= {};
						row.cell[columnId].id		= td.id;
						row.cell[columnId].cls		= td.className;
						row.cell[columnId].qtip		= td.getQtipText();
						row.cell[columnId].style	= td.collectStyles();
						row.cell[columnId].visible	= (td.getStyle("display") != "none" && td.getStyle("visibility") != "hidden");

						row[columnId]				= td.innerHTML.strip();

						return row;
					});
				});
			}
			/* keine Daten */
			else
			{
				options.data = $A([]);
			}

			/* mit wert */
			if (selectedValues.length == 1)
			{
				options.value = options.data[selectedValues[0]].id;
			}
			/* mit werten */
			else if (selectedValues.length > 1)
			{
				options.multiselect	= true;
				options.value		= selectedValues.collect(function(rowIndex)
				{
					return options.data[rowIndex].id
				});
			}

			/* Footer finden */
			var footerTr = trElements.find(function(tr) /*element.down("tr[class~='footer']"); */
			{
				return tr.hasClassName("footer");
			});
			if (footerTr)
			{
				var tdElements = $A([footerTr.down("td")]);
				tdElements = tdElements.concat(tdElements.first().nextSiblings());

				options.footer = tdElements.inject( /* footerTr.select("td") */
				{
					cls:		footerTr.className,
					qtip:		footerTr.getQtipText() || undefined,
					style:		footerTr.collectStyles()
				}, function(footer, footerTd, columnIndex)
				{
					var columnId = options.columns[columnIndex].id;

					if (Object.isUndefined(footer.cell))
					{
						footer.cell = {};
					}
					footer.cell[columnId]			= {};
					footer.cell[columnId].id		= footerTd.id;
					footer.cell[columnId].cls		= footerTd.className;
					footer.cell[columnId].qtip		= footerTd.getQtipText();
					footer.cell[columnId].style		= footerTd.collectStyles();
					footer.cell[columnId].visible	= (footerTd.getStyle("display") != "none" && footerTd.getStyle("visibility") != "hidden");

					footer[columnId]				= footerTd.innerHTML.strip();

					return footer;
				});
			}

			return new jControls.Grid(options);

		/* mehrere element */
		}
		else
		{
			var options = arguments.last();
			options = (options.nodeType != 1 && typeof options != "string" ? arguments.pop() : {});
			return arguments.collect(function(element)
			{
				return jControls.Grid.initBy(element, options);
			}, this);
		}
	};

	/****************************************************************************************************
	 *																									*
	 *											QUICKTIP												*
	 *																									*
	 ****************************************************************************************************/

	var quicktipContainer		= null;	/* der container element */
	var quicktipTitle			= null;	/* das Title element */
	var quicktipText			= null;	/* das text element */
	var quicktipXY				= null;		/* position */
	var quicktipAutoloaded		= false;	/* autoload an? */
	var quicktipCurrent			= null;

	/**
	 * setzt die position des Quicktip
	 *
	 * @param object pos
	 * @access piravte
	 */
	var quicktipSetPosition = function(pos)
	{
		if (quicktipXY == null && !pos)
		{
			return;
		}
		if (pos)
		{
			quicktipXY = pos;
		}

		if (!quicktipContainer.isDisplayed())
		{
			return;
		}

		var size	= quicktipContainer.getSize();

		if (!Prototype.Browser.isIE || Prototype.Browser.isIE7)
		{
			var maxPos = document.viewport.getDimensions();
			var scrollOffset = document.viewport.getScrollOffsets();

			maxPos.width -= size.width + jControls.QuickTip.OFFSET.X;
			maxPos.height -= size.height + jControls.QuickTip.OFFSET.Y;

			if (maxPos.width + scrollOffset.left <= quicktipXY.x)
			{
				quicktipXY.x -= quicktipXY.x - maxPos.width;
			}
			if (maxPos.height + scrollOffset.top <= quicktipXY.y)
			{
				quicktipXY.y -= size.height + jControls.QuickTip.OFFSET.Y * 2;
			}

			if (quicktipXY.x < scrollOffset.left)
			{
				quicktipXY.x = scrollOffset.left;
			}
			if (quicktipXY.y < scrollOffset.top)
			{
				quicktipXY.y = scrollOffset.top;
			}
		}

		quicktipContainer.setXY(
		[
			quicktipXY.x + jControls.QuickTip.OFFSET.X,
			quicktipXY.y + jControls.QuickTip.OFFSET.Y
		]);
	};

	/**
	 * erstellt den quicktip im DOM
	 *
	 * @access private
	 */
	var quicktipCreate = function()
	{
		if (quicktipContainer != null)
		{
			return;
		}

		Element.insert(document.body,
		{
			bottom:
				"<div class='jcContainer jcQuickTip' style='display:none;'>" +
					"<div class='jcQuickTip-tl'>" +
						"<div class='jcQuickTip-tr'>" +
							"<div class='jcQuickTip-tc'>" +
								"<div class='jcQuickTip-header'>" +
									"<span class='jcQuickTip-header-text'>" +
									"</span>" +
								"</div>" +
							"</div>" +
						"</div>" +
					"</div>" +
					"<div class='jcQuickTip-bwrap'>" +
						"<div class='jcQuickTip-ml'>" +
							"<div class='jcQuickTip-mr'>" +
								"<div class='jcQuickTip-mc'>" +
									"<div class='jcQuickTip-body' style='height:auto;'>" +
									"</div>" +
								"</div>" +
							"</div>" +
						"</div>" +
						"<div class='jcQuickTip-bl'>" +
							"<div class='jcQuickTip-br'>" +
								"<div class='jcQuickTip-bc'>" +
								"</div>" +
							"</div>" +
						"</div>" +
					"</div>" +
				"</div>"
		});
		quicktipContainer	= document.body.down(".jcQuickTip");
		quicktipTitle		= quicktipContainer.down(".jcQuickTip-header-text");
		quicktipText		= quicktipContainer.down(".jcQuickTip-body");

		quicktipContainer.cleanWhitespace();
		Event.observe(document, "mousemove", function(e)
		{
			quicktipSetPosition(e.pointer());
		}.bindAsEventListener(this));
	};

	/**
	 * stellt einen quicktip bereit
	 *
	 *	Quicktip options
	 *		name					type			default		description
	 *		=====================================================================================================================
	 *		title		optional	string						title des tips
	 *		text					string						text des tips
	 *		width		optional	int				auto		breite des Tips
	 */
	jControls.QuickTip =
	{
		/**
		 * offset
		 */
		OFFSET: { X: 15, Y: 15},

		/**
		 * maximale breite des Tipps
		 */
		MAXWIDTH:	300,

		ATTRIBUTE:	"qtip",

		/**
		 * autoloadfunktion für Quicktips
		 *
		 * @param string selector Selektor (see Prototype/$$) (standard "[title]")
		 * @param string attribute diese Attribut wird gelesen für den QuickTipText (standard "title")
		 * @access public
		 */
		autoload: function(selector, attribute)
		{
			selector	= selector	|| '[title!=""]';
			attribute	= attribute	|| "title";

			var fnSearch = (function()
			{
				if (this._searchActive)
				{
					return Prototype.emptyFunction;
				}
				this._searchActive = true;

				return (function()
				{
					if (Object.isArray(selector))
					{
						selector.each(setQtip);
					}
					else
					{
						$(document.body).select("[qtip!='']", selector).each(setQtip);
					}
					this._searchActive = false;
				}).bind(this);
			}).bind(this);

			if (quicktipAutoloaded)
			{
				fnSearch().defer();
				return;
			}
			quicktipAutoloaded = true;

			/* setzt den qTip an einem element entsprechend des Attributes */
			function setQtip(element)
			{
				/* ein text ist definitiv dran dann nix tun aber im IE schon der ist doof */
				if (!Prototype.Browser.isIE && typeof element._qtipText != "undefined")
				{
					return;
				}

				if ($(element).hasClassName("no-qtip"))
				{
					return;
				}

				/* text aus title holen */
				var text = element.readAttribute(attribute);
				/* kein text eventuell im zusätzlichen hinzugefügten Attribut holen */
				if (!text)
				{
					text = element.readAttribute("qtip");
				}
				/* kein text kein tip */
				if (!text || !Object.isString(text))
				{
					return;
				}

				/* title löschen */
				element.writeAttribute(attribute, "");
				/* zusätzliches Attribut hinzufügen, fals Element anderweitig hinzugefügt wird */
				/* nachdem title schon gelöscht wurde */
				element.writeAttribute("qtip", text);
				/*element markieren das qtip dran hängt */
				element._qtipText = text;

				/* prüfen ob der Text escaped ist */
				if (text.match(/&lt;|&gt;/gi))
				{
					text = text.unescapeHTML();
				}

				/* Qtip setzen */
				jControls.QuickTip.set(element, text);

				return element;
			};

			/* bei autoload wird die Element.insert funktion von Prototype nun überschrieben und */
			/* durch eine eigene ersetz die das überschreiben zwar an prototype */
			/* durchreicht, aber nach dem einfügen das eingefügt nach den gewünschten selector */
			/* durchsucht */
			Element.insert = Element.insert.wrap(function(proceed, element, content)
			{
				var result = proceed(element, content);
				fnSearch().defer();
				return result;
			});

			/* alle finden die passen könnten */
			autoload(fnSearch());
		},

		/**
		 * liefert den qTip Text eines Elementes
		 *
		 * @param node element
		 * @access public
		 * @return string
		 */
		getQtipText: function(element)
		{
			try
			{
				return element.title || element.readAttribute(jControls.QuickTip.ATTRIBUTE);
			}
			catch(e)
			{
			}
		},

		/**
		 * versteckt den Quicktip wieder
		 *
		 * @acces public
		 */
		hide: function()
		{
			if (quicktipContainer == null)
			{
				return;
			}

			if (this.effect && this.effect.cancel)
			{
				this.effect.cancel();
			}

			this.effect = new Effect.Fade(quicktipContainer,
			{
				duration:	0.3,
				from:		quicktipContainer.getOpacity(),
				to:			0
			});
		},

		/**
		 * zeigt den quicktip an
		 *
		 * @param string|object options siehe oben wenn string dann nur text
		 * @access public
		 */
		show: function(options)
		{
			if (typeof options != "object")
			{
				options =
				{
					text: options
				};
			}
			if (options.text == "")
			{
				return;
			}

			quicktipCreate();

			quicktipContainer.setStyle(
			{
				width: ""
			});

			quicktipTitle.innerHTML = options.title || "";

			if (Prototype.Browser.isIE && !Prototype.Browser.isIE6)
			{
				if (options.title)
				{
					quicktipContainer.down(".jcQuickTip-header").show();
				}
				else
				{
					quicktipContainer.down(".jcQuickTip-header").hide();
				}
			}
			else if (Prototype.Browser.isIE6)
			{
				if (!options.title)
				{
					quicktipContainer.down(".jcQuickTip-header").setStyle(
					{
						fontSize: "0px"
					});
				}
				else
				{
					quicktipContainer.down(".jcQuickTip-header").clearStyle("fontSize");
				}
			}

			quicktipText.innerHTML = options.text;
			quicktipText.setStyle(
			{
				width: (options.width ? options.width + "px" : "auto")
			});

			/* sichtbarkeit merken */
			var displayed = quicktipContainer.isDisplayed();
			/* wenn unsichtbar -> sichtbar */
			if (!displayed)
			{
				quicktipContainer.show();
			}
			/* größe überschritten? */
			if (quicktipText.getWidth() > jControls.QuickTip.MAXWIDTH)
			{
				quicktipText.setWidth(jControls.QuickTip.MAXWIDTH);
			}
			/* IE Sonderbehandlung */
			if (Prototype.Browser.isIE)
			{
				quicktipContainer.setStyle(
				{
					width: (quicktipText.getWidth() + 20) + "px"
				});
			}
			/* wenn unsichtbar war -> unsichtbar machen */
			if (!displayed)
			{
				quicktipContainer.hide();
			}

			/* gibt es einen effekt? dann abbrechen */
			if (this.effect && this.effect.cancel)
			{
				this.effect.cancel();
			}

			/* neuer effekt */
			this.effect = new Effect.Appear(quicktipContainer,
			{
				duration:	(1 - (quicktipContainer.isDisplayed() ? quicktipContainer.getOpacity() : 0)) * 0.3,
				from:		(quicktipContainer.isDisplayed() ? quicktipContainer.getOpacity() : 0),
				to:			1
			});

			quicktipSetPosition();
		},

		/**
		 * bindet einen Quicktip an das angegebene element
		 *
		 * @param string|node element
		 * @param string|object options
		 * @access public
		 */
		set: function(element, options)
		{
			element = $(element);

			if (quicktipCurrent == element)
			{
				jControls.QuickTip.show(options);
			}

			element.on(
			{
				mouseover:
				{
					scope:	window,
					fn:		function(event)
					{
						quicktipCurrent = Event.element(event);
						jControls.QuickTip.show(options);
					}
				},
				mouseout:
				{
					scope:	window,
					fn:		function()
					{
						quicktipCurrent = null;
						jControls.QuickTip.hide();
					}
				}
			});

			return element;
		}
	};
	Element.addMethods(
	{
		quickTip:		jControls.QuickTip.set,
		getQtipText:	jControls.QuickTip.getQtipText
	});

	/****************************************************************************************************
	 *																									*
	 *												BUTTON												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * ein Button Control
	 *
	 * Button Control Class options (geerbte options von jControls.Control siehe oben)
	 *
	 *		name						type		default		description
	 *		=====================================================================================================================
	 *		checked			optional	bool		undefined	wenn angegeben hat der Button einen check Status und kann umgeschaltet werden
	 *		icon			optional	string					class name für das icon
	 *		iconDisabled	optional	string					class name für das icon wenn deaktiviert
	 *		iconHover		optional	string					class name für das icon wenn maus drüber
	 *		iconPressed		optional	string					class name für das icon wenn maus gedrückt
	 *		text			optional	string		""			text des buttons
	 *
	 * Hinweis: icon oder text müssen angegeben sein
	 *
	 * Default Event Names
	 *	deklaration											result type		description
	 * =====================================================================================================================
	 *	onAfterChecked(this, state)											nach dem checken
	 *	onBeforeChecked(this, state)						bool			vor dem checken
	 *
	 */
	jControls.Button = Class.create(jControls.Control,
	{
		/**
		 * checked?
		 *
		 * @var bool
		 * @access public
		 */
		checked: null,

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Button.xType;
			}

			options.icon			= options.icon			|| "";
			options.iconDisabled	= options.iconDisabled	|| options.icon;
			options.text			= options.text			|| "";

			if (!Object.isUndefined(options.checked))
			{
				this.checked = options.checked ? true : false;
			}

			if (Object.isBoolean(this.checked))
			{
				options.listeners = options.listeners || {};
				options.listeners.onAfterClick = (options.listeners.onAfterClick || Prototype.emptyFunction).wrap(function(proceed, btn)
				{
					if (btn.listeners.onAfterChecked)
					{
						btn.listeners.onAfterChecked(btn, btn.checked);
					}

					proceed(btn);
				});
				options.listeners.onBeforeClick = (options.listeners.onBeforeClick || Prototype.emptyFunction).wrap(function(proceed, btn)
				{
					if (!btn.listeners.onBeforeChecked || (btn.listeners.onBeforeChecked && btn.listeners.onBeforeChecked(btn, btn.checked) !== false))
					{
						btn.setChecked(!btn.checked);
					}

					proceed(btn);
				});
			}
			$super(options);
		},

		/**
		 * zeichnet den Button
		 *
		 * @access string|node|jControls.*
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			var html = "";

			if (this.rendered)
			{
				return this;
			}

			$super(renderTo);

			if (this.xType == jControls.Button.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			this.dom.addClass("jcBtn");
			this.dom.writeAttribute("onclick", "event.stop();");

			var container = Element.insert(this.dom,
			{
				bottom:
					"<div class='" +
									"jcBtn-btn" +
									(this.options.icon && !this.options.text ? " jcBtn-icon-only" : "") +
									(!this.options.icon && this.options.text ? " jcBtn-text-only" : "") +
									(this.options.icon && this.options.text ? " jcBtn-text-icon" : "") +
									(this.enabled ? " jcBtn-enabled" : " jcBtn-disabled") +
									(Object.isBoolean(this.checked) && this.checked && this.enabled ? " jcBtn-checked" : "") +
									(Object.isBoolean(this.checked) && this.checked && !this.enabled ? " jcBtn-checked-disabled" : "") +
								"'" +
					">" +
						"<div class='jcBtn-left'></div>" +
						"<div class='jcBtn-center'>" +
							"<div class='jcBtn-button jcBtn-text" + (this.enabled && this.options.icon ? " " + this.options.icon : "") + (!this.enabled && this.options.icon ? " " + this.options.iconDisabled : "") + "'>" +
								this.options.text +
							"</div>" +
						"</div>" +
						"<div class='jcBtn-right'></div>" +
					"</div>"
			}).down(".jcBtn-btn");

			/* icon Hover wenn da */
			if (this.options.iconHover)
			{
				Event.observe(container, "mouseover", (function(e)
				{
					if (!this.enabled)
					{
						return;
					}

					var node = this.dom.down(".jcBtn-button");
					if (node._pe)
					{
						node._pe.stop();
					}
					node._pe = new PeriodicalExecuter((function()
					{
						node._pe.stop();
						node.addClass(this.options.iconHover);
					}).bind(this), 0.1);
				}).bindAsEventListener(this));

				Event.observe(container, "mouseout", (function(e)
				{
					var node = this.dom.down(".jcBtn-button");
					if (node._pe)
					{
						node._pe.stop();
					}
					node._pe = new PeriodicalExecuter((function()
					{
						node._pe.stop();
						node.removeClass(this.options.iconHover);
					}).bind(this), 0.1);
				}).bindAsEventListener(this));
			}

			/* gedrückt icon */
			if (this.options.iconPressed)
			{
				Event.observe(container, "mousedown", (function(e)
				{
					if (!this.enabled)
					{
						return;
					}
					this.dom.down(".jcBtn-button").addClass(this.options.iconPressed);
				}).bindAsEventListener(this));

				Event.observe(container, "mouseup", (function(e)
				{
					if (!this.enabled)
					{
						return;
					}
					this.dom.down(".jcBtn-button").removeClass(this.options.iconPressed);
				}).bindAsEventListener(this));
			}

			/* clicken stoppen */
			Event.observe(container, "click", (function(e)
			{
				if (!this.enabled)
				{
					Event.stop(e);
				}
			}).bindAsEventListener(this));

			if (this.xType == jControls.Button.xType && this.listeners.onAfterRender)
			{
				this.listeners.onAfterRender.defer(this);
			}

			return this;
		},

		/**
		 * checked/unchecked setzen
		 *
		 * @param bool value
		 * @access public
		 * @return this
		 */
		setChecked: function(state)
		{
			this.checked = state;

			this.dom.down(".jcBtn-btn").removeClass("jcBtn-checked jcBtn-checked-disabled");
			if (this.checked && this.enabled)
			{
				this.dom.down(".jcBtn-btn").addClass("jcBtn-checked");
			}
			else if (this.checked && !this.enabled)
			{
				this.dom.down(".jcBtn-btn").addClass("jcBtn-checked-disabled");
			}

			return this;
		},

		/**
		 * aktivieren | deaktivieren
		 *
		 * @param bool value
		 * @access public
		 * @return this
		 */
		setEnabled: function($super, state)
		{
			$super(state);

			this.dom.down(".jcBtn-btn").removeClass("jcBtn-enabled jcBtn-disabled");
			this.dom.down(".jcBtn-btn").addClass(this.enabled ? "jcBtn-enabled" : "jcBtn-disabled");

			this.dom.down(".jcBtn-button").removeClass(this.options.icon + " " + this.options.iconDisabled + " " + this.options.iconHover + " " + this.options.iconPressed);
			this.dom.down(".jcBtn-button").addClass(this.enabled ? this.options.icon : this.options.iconDisabled);

			return this;
		}
	});
	/**
	 * diverse button titel
	 */
	jControls.Button.TITLE =
	{
			PAGEFIRST:			"erste Seite",
			PAGEPREV:			"vorherige Seite",
			PAGENEXT:			"nächste Seite",
			PAGELAST:			"letzte Seite",
			CLOSE:				"Schließen",
			EXPAND:				"Ausklappen",
			COLLAPSE:			"Einklappen",
			EXPANDALL:			"alle ausklappen",
			COLLAPSEALL:		"alle einklappen",
			EXPANDCOLLAPSE:		"Aus- und Einklappen",
			PINNED:				"Festhalten",
			SEARCH:				"Suche",
			SEARCHTIPP:			"Ab 5 Zeichen wird automatisch bei der Eingabe gesucht."
	};

	/**
	 * xType von Buttons
	 */
	jControls.Button.xType = xTypes.Button;

	/****************************************************************************************************
	 *																									*
	 *											PAGELIST												*
	 *																									*
	 ****************************************************************************************************/

	/**
	 * ein Page Listing Control
	 *
	 * Page Control Class options (geerbte options von jControls.Control siehe oben)
	 *
	 *		name						type		default		description
	 *		=====================================================================================================================
	 *		current			optional	int			1			Aktuelle Seite
	 *		count			optional	int						Anzahl der Seiten
	 *		entrycount		optional	int						gibt die Anzahl der Einträge zur Anzeige (wenn angegeben ist count unnötig)
	 *		maxperpage		optional	int						Anzahl der Einträge pro Seite (wenn count dann unnötig)
	 *		textformat		optional	string					Anzeigeformat "Seite x von y"
	 *
	 * Hinweis: count oder entrycount müssen angegeben sein
	 *
	 * Default Event Names
	 *	deklaration											result type			description
	 * =====================================================================================================================
	 *	onAfterPageFirst(pagelist, newPage)									nach anklicken des Buttons erste Seite
	 *	onAfterPageLast(pagelist, newPage)									nach anklicken des Buttons letzte seite
	 *	onAfterPageNext(pagelist, newPage)									nach anklicken des Buttons nächste
	 *	onAfterPagePrevious(pagelist, newPage)								nach anklicken des Buttons vorherige
	 *	onAfterPage(pagelist, newPage)										nach einem seiten wechsel
	 *	onBeforePageFirst(pagelist, oldPage, newPage)		bool			vor anklicken des Buttons erste Seite
	 *	onBeforePageLast(pagelist, oldPage, newPage)		bool			vor anklicken des Buttons letzte seite
	 *	onBeforePageNext(pagelist, oldPage, newPage)		bool			vor anklicken des Buttons nächste seite
	 *	onBeforePagePrevious(pagelist, oldPage, newPage)	bool			vor anklicken des Buttons vorherige seite
	 *	onBeforePage(pagelist, oldPage, newPage)			bool			vor einem seiten wechsel
	 *	onHideEntry(pagelist, index)
	 *	onShowEntry(pagelist, index)
	 */
	jControls.PageList = Class.create(jControls.Control,
	{
		/**
		 * aktuelle seite (start 1)
		 *
		 * @var int
		 * @access private
		 */
		current:	null,

		/**
		 * anzahl aller seiten
		 *
		 * @var int
		 * @access private
		 */
		count:		null,

		/**
		 * anzahl der einträge gesamt
		 *
		 * @var int
		 * @access private
		 */
		entrycount:	null,

		/**
		 * anzahl der eintröge pro seite
		 *
		 * @var int
		 * @access private
		 */
		maxperpage:	null,

		/**
		 * text formatierungs string für die anzeige (siehe jControls.PageList.DISPLAYTEXT)
		 *
		 * @var string
		 * @access private
		 */
		textformat:	null,

		/**
		 * formatiert den anzeige text
		 *
		 * @access public
		 * @return string
		 */
		formatText: function()
		{
			return (new Template(this.textformat || jControls.PageList.DISPLAYTEXT)).evaluate(
			{
				index:		this.current,
				count:		this.count,
				mindisplay:	this.getMinDisplay() + 1,
				maxdisplay:	this.getMaxDisplay() + 1,
				entrycount:	this.entrycount || ""
			});
		},

		/**
		 * liefert den größten aktuell angezeigtenwert
		 *
		 * @param int page diese oder aktuelle
		 * @access public
		 * @return int
		 */
		getMaxDisplay: function(page)
		{
			page = page || this.current;

			return (this.entrycount == null || page < this.count ? page * this.maxperpage - 1 : this.entrycount - 1);
		},

		/**
		 * liefert den kleinsten aktuell angezeigtenwert
		 *
		 * @param int page
		 * @access public
		 * @return int
		 */
		getMinDisplay: function(page)
		{
			return ((page || this.current) - 1) * this.maxperpage;
		},

		/**
		 * liefert die aktuelle seite
		 *
		 * @access public
		 * @return int
		 */
		getPage: function()
		{
			return this.current;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.PageList.xType;
			}

			this.current	= options.current			|| 1;
			this.count		= options.count				|| 1;
			this.maxperpage	= options.maxperpage		|| jControls.PageList.MAXPERPAGE;
			this.textformat	= options.textformat		|| jControls.PageList.DISPLAYTEXT;
			this.entrycount	= options.entrycount		|| null;

			if (this.entrycount)
			{
				this.count = Math.ceil(this.entrycount / this.maxperpage);
			}

			if (this.current < 1)
			{
				this.current = 1;
			}
			else if (this.current > this.count)
			{
				this.current = this.count;
			}

			this._button = {};

			$super(options);
		},

		/**
		 * prüft ob value im anzeigebereich ist
		 *
		 * @param int value
		 * @access public
		 * @return bool
		 */
		isBetween: function(value)
		{
			return this.getMinDisplay() <= value && value <= this.getMaxDisplay();
		},

		/**
		 * zeichnet das Control
		 *
		 * @access string|node|jControls.*
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			if (this.rendered)
			{
				return this;
			}

			if (this.xType == jControls.PageList.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			this.options.buttons = $A([
			{
				id:				this.id + "_first",
				icon:			"jcBtn-first",
				iconDisabled:	"jcBtn-first-disabled",
				enabled:		(this.current > 1),
				qtip:			jControls.Button.TITLE.PAGEFIRST,
				renderTo:		this,
				listeners:
				{
					onAfterClick: this.setFirstPage.bind(this)
				}
			},
			{
				id:				this.id + "_prev",
				icon:			"jcBtn-prev",
				iconDisabled:	"jcBtn-prev-disabled",
				enabled:		(this.current > 1),
				qtip:			jControls.Button.TITLE.PAGEPREV,
				renderTo:		this,
				listeners:
				{
					onAfterClick: this.setPreviousPage.bind(this)
				}
			},
			{
				id:				this.id + "_next",
				icon:			"jcBtn-next",
				iconDisabled:	"jcBtn-next-disabled",
				enabled:		(this.current < this.count),
				qtip:			jControls.Button.TITLE.PAGENEXT,
				renderTo:		this,
				listeners:
				{
					onAfterClick: this.setNextPage.bind(this)
				}
			},
			{
				id:				this.id + "_last",
				icon:			"jcBtn-last",
				iconDisabled:	"jcBtn-last-disabled",
				enabled:		(this.current < this.count),
				qtip:			jControls.Button.TITLE.PAGELAST,
				renderTo:		this,
				listeners:
				{
					onAfterClick: this.setLastPage.bind(this)
				}
			}]);

			$super(renderTo);

			this._button.first = jControls.$(this.id + "_first");
			this._button.prev = jControls.$(this.id + "_prev");
			this._button.next = jControls.$(this.id + "_next");
			this._button.last = jControls.$(this.id + "_last");

			/**
			 * folgende Struktur wird erzeugt
			 *
			 *	<div CONTAINER>
			 *		jControls.Button	FIRST
			 *		jControls.Button	PREV
			 *		jControls.Button	SEP
			 *		<div PAGEDISPLAY />
			 *		jControls.Button	SEP
			 *		jControls.Button	NEXT
			 *		jControls.Button	LAST
			 *	</div>
			 */

			this.dom.addClass("jcControl-panel jcControl-panel-bottom jcToolbar");

			this._button.text = new jControls.Container(
			{
				id:				this.id + "_text",
				type:			"div",
				cls:			"jcGrid-pageinfo",
				html:			this.formatText(),
				renderAfter:	this._button.prev
			});

			/* kleiner helper damit voll aufgezogen wird aber nur im IE und nur mit keinen einklapteil */
			if (Prototype.Browser.isIE)
			{
				Element.insert(this.dom, {bottom: "<div class='jcWidthFull'>&#160;</div>"});
			}

			if (this.xType == jControls.PageList.xType && this.listeners.onAfterRender)
			{
				this.listeners.onAfterRender.defer(this);
			}

			return this;
		},

		/**
		 * setzt die erste Seite
		 *
		 * @access public
		 * @return this
		 */
		setFirstPage: function()
		{
			if (this.listeners.onBeforePageFirst && this.listeners.onBeforePageFirst(this, this.current, 1) === false)
			{
				return this;
			}
			this.setPage(1);
			if (this.listeners.onAfterPageFirst)
			{
				this.listeners.onAfterPageFirst.defer(this, this.current);
			}
			return this;
		},

		/**
		 * setzt die letzte seite
		 *
		 * @access public
		 * @return this
		 */
		setLastPage: function()
		{
			if (this.listeners.onBeforePageLast && this.listeners.onBeforePageLast(this, this.current, this.count) === false)
			{
				return this;
			}
			this.setPage(this.count);
			if (this.listeners.onAfterPageLast)
			{
				this.listeners.onAfterPageLast.defer(this, this.current);
			}
			return this;
		},

		/**
		 * setzt die nächste seite
		 *
		 * @access public
		 * @return this
		 */
		setNextPage: function()
		{
			if (this.listeners.onBeforePageNext && this.listeners.onBeforePageNext(this, this.current, this.current < this.count ? this.current + 1 : this.count) === false)
			{
				return this;
			}
			this.setPage(this.current + 1);
			if (this.listeners.onAfterPageNext)
			{
				this.listeners.onAfterPageNext.defer(this, this.current);
			}
			return this;
		},

		/**
		 * seite anzeigen
		 *
		 * @param int page
		 * @access public
		 * @return this
		 */
		setPage: function(page)
		{
			if (page < 1)
			{
				page = 1;
			}
			if (page > this.count)
			{
				page = this.count;
			}

			if (this.listeners.onBeforePage && this.listeners.onBeforePage(this, this.current, page) === false)
			{
				return this;
			}

			if (this.listeners.onHideEntry)
			{
				for (var index = this.getMinDisplay(), max = this.getMaxDisplay(); index <= max; index++)
				{
					this.listeners.onHideEntry(this, index);
				}
			}

			this.current = page;

			if (this.listeners.onShowEntry)
			{
				for (var index = this.getMinDisplay(), max = this.getMaxDisplay(); index <= max; index++)
				{
					this.listeners.onShowEntry(this, index);
				}
			}

			if (this.rendered)
			{
				this._button.first.setEnabled(this.current > 1);
				this._button.prev.setEnabled(this.current > 1);
				this._button.text.dom.innerHTML = this.formatText();
				this._button.next.setEnabled(this.current < this.count);
				this._button.last.setEnabled(this.current < this.count);
			}

			if (this.listeners.onAfterPage)
			{
				this.listeners.onAfterPage.defer(this, this.current);
			}

			return this;
		},

		/**
		 * setzt die vorherige seite
		 *
		 * @access public
		 * @return this
		 */
		setPreviousPage: function()
		{
			if (this.listeners.onBeforePagePrevious && this.listeners.onBeforePagePrevious(this, this.current, this.current > 1 ? this.current - 1 : 1) === false)
			{
				return this;
			}
			this.setPage(this.current - 1);
			if (this.listeners.onAfterPagePrevious)
			{
				this.listeners.onAfterPagePrevious.defer(this, this.current);
			}
			return this;
		}
	});

	/**
	 * formatierung für die Seitenanzeige
	 * verwendete Variablen sind
	 *	index			aktuelle Seite
	 *	count			anzahl der Seiten
	 *	mindisplay		aktuell kleinste anzeigeposition
	 *	maxdisplay		aktuell größte anzeigeposition
	 *	entrycount		anzahl aller einträge
	 */
	jControls.PageList.DISPLAYTEXT = "Seite #{index} von #{count}";

	/**
	 * anzahl zeilen pro seite
	 */
	jControls.PageList.MAXPERPAGE =	10;

	/**
	 * xType
	 */
	jControls.PageList.xType = xTypes.PageList;

	/****************************************************************************************************
	 *																									*
	 *											NESTED SETS												*
	 *																									*
	 ****************************************************************************************************/
	/**
	 * generiert den tree aus nested sets
	 *
	 * @param jControls.NestedSetContainer nset
	 * @access private
	 */
	var nestedSetCreateTree = function(nset)
	{
		for (var i = 0; i < nset.length; i++)
		{
			var part = nset.partition(function(entry)
			{
				return nset[i].lft < entry.lft && entry.rgt < nset[i].rgt;
			});
			nset = part[1];
			if (part[0].length != 0)
			{
				nset[i].childs = part[0];
			}
		}
		nset.each(function(entry)
		{
			if (entry.childs)
			{
				entry.childs = nestedSetCreateTree(entry.childs);
			}
		});

		return nset;
	};

	/**
	 * erzeugt ein NestedSet und kann für die Konvertierung nach Treee struktur verwendet werden
	 *
	 *	options
	 *		name							type		default		description
	 *		=====================================================================================================================
	 */
	jControls.NestedSetContainer = Class.create(
	{
		/**
		 * die daten
		 *
		 * @var array
		 * @acces private
		 */
		data: null,

		/**
		 * eine tree struktur
		 *
		 * @var array
		 * @access private
		 */
		tree: null,

		/**
		 * xType
		 *
		 * @var string
		 * @access public
		 */
		xType:	null,

		/**
		 * gibt den tree back
		 *
		 * @access public
		 * @return array
		 */
		getTree: function()
		{
			if (!this.tree)
			{
				this.tree = nestedSetCreateTree(this.data);
			}

			return this.tree;
		},

		/**
		 * konstruktor
		 *
		 * @access public
		 */
		initialize: function(data)
		{
			if (!this.xType)
			{
				this.xType = jControls.NestedSetContainer.xType;
			}

			this.data = data;
		}
	});
	/**
	 * xType
	 */
	jControls.NestedSetContainer.xType = xTypes.NestedSetContainer;

	/****************************************************************************************************
	 *																									*
	 *												TREE												*
	 *																									*
	 ****************************************************************************************************/
	/**
	 * class alternate status setzen für den tree
	 *
	 * @param jControls.Tree tree
	 * @access private
	 */
	var treeUpdateRowClassAlternate = function(tree)
	{
		var index = 0;
		tree.dom.select(".jcTree-row").each(function(element)
		{
			if (element.isDisplayed())
			{
				if (index % 2)
				{
					element.addClass("jcTree-row-alt");
				}
				else
				{
					element.removeClass("jcTree-row-alt");
				}
				index++;
			}
		});
	};

	/**
	 * Daten der rows anpassen vom tree
	 *
	 * @param array rows
	 * @access public
	 */
	var treeCorrectRowData = function(rows, parentRow)
	{
		rows.each(function(row)
		{
			if (!Object.isUndefined(parentRow))
			{
				row.parent = parentRow;
			}

			if (!Object.isUndefined(row.data.id) && typeof row.data.id == "object" && !Object.isUndefined(row.data.id.value))
			{
				row.id	= row.data.id.value;
			}
			else if(!Object.isUndefined(row.data.id))
			{
				row.id 	= row.data.id;
			}
			if (row.childs && row.childs.length != 0)
			{
				treeCorrectRowData(row.childs, row);
			}
		});
	};

	/**
	 * erzeugt ein Tree
	 *
	 *	options
	 *		name								type		default					description
	 *		=====================================================================================================================
	 *		buttonCollapseAll	optional		bool		true					Button zum einklappen aller Nodes verwenden (siehe jControls.Button.TITLE.COLLAPSEALL)
	 *		buttonExpandAll		optional		bool		true					Button zum aufklappsen aller Nodes verwenden (siehe jControls.Button.TITLE.EXPANDALL)
	 *		columns								array|object						definiert die Column Struktur in den nodes
	 *		columnStyles		optional		array								Informationsarray mit den Definition, wie die Spaltenköpfe gezeichnet werden sollen
	 *		columnElbow			optional		string								In dieser Spalte den Tree zeichnen, wenn nicht angegeben, dann die erste sichtbare spalte
	 *		data				optional		array|jControls.NestedSetContainer	der Node Tree als Array... auf der ersten ebene können schon n Nodes angegeben werden
	 *		enterable			optional		bool		false					enterable Options wird per default = false
	 *		expandNodes			optional		bool								Alle Knoten auf/zu-klappen sofern nicht am Knoten
	 *		footer				optional		object								Daten für eine Footer Zeile, welche immer sichtbar ist und aufgebaut ist wie data[] Entry
	 *		header				optional		bool		true					Spaltentitel anzeigen
	 *		multiselect			optional		bool		false					mehrfachauswahl
	 *		nodesSelectable		optional		bool		true					Knoten selektierbar wenn element enterable ist
	 *		renderComplete		optional		bool		false					Unabhängig ob ein Child sichtbar ist oder nichzt... trotzdem rendern
	 *		searchColumn		optional		bool|string	true					Anzeige suchefeld und button wenn == true oder string... wird true angegeben wird die erste sichtbare spalte verwendet (siehe jControls.Button.TITLE.SEARCH)
	 *		sortable			optional		bool		true					sortieren
	 *		sortBy				optional		string								nach dieser Spalte sortieren ansonsten ignored
	 *		sortDir				optional		string		asc	| desc				sortierrichtung
	 *		style				optional		string|object						zusätzliche styles für den zugehörigen anzeigecontainer
	 *
	 *	Columns struktur definiert die Columns und daten Gestaltung in den NODES.DATA
	 *		wenn Multi Rows columns[Head Row][] oder wenn eine Zeile columns[]
	 *		name							type		default		description
	 *		=====================================================================================================================
	 *		cls			optional	string							zusätzliche css class für die spalte
	 *		format		optional	function						Formatierungsfunktion
	 *		id						string							id bzw key der spalte über welche nach data referenziert wird
	 *		qtip		optional	jControls.QuickTip.options		einen entsprechende Quicktip für den SPaltenkopf anzeigen
	 *		sortable	optional	bool				true		spalte sortierbar?
	 *		style		optional	string|object					zusätzliche styles
	 *		title		optional	string							Titel der spalte
	 *		type		optional	string				string		Datentyp der Spalte (DATATYPES)
	 *		visible		optional	bool				true		Sichtbar
	 *
	 *	columnStyles Entry (columnStyles[Head Row][Head Column])
	 *			pro Head Zeile ein Index in columnStyles
	 *			in jeder Index is ein Array mit allen verfügbaren spalten
	 *		name					type				default		description
	 *		=====================================================================================================================
	 *		colspan					int								anzahl der Col Spans dieser Spalte (die nachfolgenden zugehörigen Spalten sollten dann render = false sein)
	 *		dataCell				bool							definiert diese Zelle als Datenzelle womit diese ggf sortierbar ist
	 *		element					node							das original element welches die Spaltendefinitionen definierte
	 *		render					bool							wird diese Spalte gezeichnet (sind idR meinst von colspan und rowspan abghängig)
	 *		rowspan					int								anzahl der Row Spans dieser Spalte (die nachfolgenden zugehörigen Zeilen-Spalten sollten dann render = false sein)
	 *
	 *	Node struktur
	 *		name						type		default			description
	 *		=====================================================================================================================
	 *		childs			optional	array						Array mit allen Child Nodes
	 * 		cls				optional	string						CSS Class
	 *		data						object						die Daten
	 *		expanded		optional	bool		false			Aufgeklappt (ignored wenn keine Child Nodes)
	 *		id				optional	mixed						Node ID
	 *		qtip			optional	jControls.QuickTip.options	einen entsprechende Quicktip für den Zeile anzeigen
	 *		style			optional	object						styles
	 *		visible			optional	bool		true			Sichtbar
	 *
	 *	NODE.data struktur
	 *		name							type				default		description
	 *		=====================================================================================================================
	 * 		[column ids]					object|mixed					NODE.DATA[column id] struktur oder der wert ansich
	 *
	 *	NODE.DATA[column id] struktur
	 *		name							type		default		description
	 *		=====================================================================================================================
	 *		cls			optional	string							zusätzliche css class für die Zelle XYZ
	 *		id			optional	string							id der Zelle
	 *		qtip		optional	jControls.QuickTip.options		einen entsprechende Quicktip für den Zeile anzeigen
	 *		style		optional	string|object		""			zusätzliche styles
	 *		value		optional	string
	 *		visible		optional	bool				true		Sichtbar
	 *
	 *	Listeners
	 *		deklaration											result type			description
	 *		=====================================================================================================================
	 *		onAfterExpandNode(treeview, node)										nach dem aufklappen
	 *		onAfterCollapseNode(treeview, node)										nach dem zuklappen
	 *		onAfterClickColumn(treeview, column)									nach dem anklicken eines spaltentitels
	 *		onAfterClickNode(treeview, node)										nach dem anklicken eines nodes
	 *		onAfterRowMove(treeview, node, direction)								bei move einer Row
	 *		onAfterRenderNode(treeview, node)										nach dem rendern eines nodes
	 *		onAfterSort(treeview, sortBy, sortOrder)								nach dem sort
	 *		onBeforeExpandNode(treeview, node)						bool			vor dem aufklappen
	 *		onBeforeCollapseNode(treeview, node)					bool			vor dem zuklappen
	 *		onBeforeClickColumn(treeview, column)					bool			bei return === false wird abgebrochen
	 *		onBeforeClickNode(treeview, node)						bool			bei return === false wird abgebrochen
	 *		onBeforeRowMove(treeview, node, direction)				bool			bei move einer Row
	 *		onBeforeRenderNode(treeview, node)						bool			vor dem rendern eines nodes
	 *		onBeforeSort(treeview, sortBy, sortOrder)				bool			vor dem sort
	 */
	jControls.Tree = Class.create(jControls.Control,
	{
		/**
		 * die daten
		 *
		 * @var array
		 * @access private
		 */
		data: null,

		/**
		 * eingeklappte Knoten
		 *
		 * @var array
		 * @access private
		 */
		rowCollapsed: null,

		/**
		 * ROW Dom
		 *
		 * @var array
		 * @access private
		 */
		rowDom: null,

		/**
		 * aufgeklappte knoten
		 *
		 * @var array
		 * @access private
		 */
		rowExpanded: null,

		/**
		 * alterniert den Expand Status einer Row
		 *
		 * @param object row oder array mit dem pfad index
		 * @access public
		 */
		alternateRow: function(row)
		{
			if (Object.isUndefined(row))
			{
				return;
			}

			if (row.expanded)
			{
				this.collapseRow(row);
			}
			else
			{
				this.expandRow(row);
			}
			treeUpdateRowClassAlternate(this);
		},

		/**
		 * node zuklappen
		 *
		 * @param object row oder array mit dem pfad index
		 * @access public
		 */
		collapseRow: function(row, anim)
		{
			if (Object.isUndefined(row))
			{
				return;
			}

			if (this.listeners.onBeforeCollapseNode && this.listeners.onBeforeCollapseNode(this, row) === false)
			{
				return;
			}

			row.expanded = false;
			if (this.rowCollapsed.indexOf(row.id) == -1)
			{
				this.rowCollapsed.push(row.id);
			}
			this.rowExpanded	= this.rowExpanded.without(row.id);
			this.setCookie();

			this.setChildVisibleState(row, false, anim);
			this.rowDom[row._index].dom.removeClass("jcTree-row-expanded").addClass("jcTree-row-collapsed");

			if (this.listeners.onAfterCollapseNode)
			{
				this.listeners.onAfterCollapseNode(this, row);
			}
		},

		/**
		 * alle einklappen aufklappen
		 *
		 * @param object row ab diesem Knoten oder ohne angabe dann ab root
		 * @access public
		 */
		collapseRows: function(row)
		{
			if (Object.isUndefined(row))
			{
				this.data.each(function(child)
				{
					this.collapseRows(child);
				}, this);
				return;
			}

			if (row.expanded)
			{
				this.collapseRow(row, false);
			}
			if (row.childs && row.childs.length != 0)
			{
				row.childs.each(function(child)
				{
					this.collapseRows(child);
				}, this);
			}
		},

		/**
		 * eine zeile löschen mit kind knoten
		 *
		 * @param object row
		 * @access public
		 */
		deleteRow: function(row)
		{
			if (row.childs && row.childs.length != 0)
			{
				row.childs.each(function(child)
				{
					this.deleteRow(child);
				}, this);
			}

			if (!row.parent.xType && row.parent.childs.length == 1)
			{
				var dom = this.rowDom[row.parent._index].dom;
				dom.down(".jcTree-elbow-childs").remove();
				dom.down(".jcTree-symbol-node").removeClass("jcTree-symbol jcTree-symbol-node").addClass("jcTree-spacer").writeAttribute("onclick");
				dom.down(".jcTree-symbol-folder").removeClass("jcTree-symbol-folder").addClass("jcTree-symbol-item");
				dom.down(".jcTree-folder").removeClass("jcTree-folder").addClass("jcTree-item");
				dom.writeAttribute("ondblclick");
			}

			this.rowExpanded	= this.rowExpanded.without(row.id);
			this.rowCollapsed	= this.rowCollapsed.without(row.id);
			this.setCookie();

			if (!Object.isUndefined(row._index) && this.rowDom[row._index].rendered)
			{
				this.rowDom[row._index].dom.remove();
			}

			row.parent.childs.splice(row._childIndex, 1);
			row.parent.childs.each(function(child, index)
			{
				child._childIndex = index;
			});
		},

		/**
		 * node aufklappen
		 *
		 * @param object row oder array mit dem pfad index
		 * @access public
		 */
		expandRow: function(row, anim)
		{
			if (Object.isUndefined(row) || row.expanded)
			{
				return;
			}

			/* elternknoten auch expanden */
			if (row.parent && row.parent.xType != jControls.Tree.xType)
			{
				this.expandRow(row.parent, anim);
			}

			if (this.listeners.onBeforeExpandNode && this.listeners.onBeforeExpandNode(this, row) === false)
			{
				return;
			}

			row.expanded = true;
			this.rowCollapsed	= this.rowCollapsed.without(row.id);
			if (this.rowExpanded.indexOf(row.id) == -1)
			{
				this.rowExpanded.push(row.id);
			}
			this.setCookie();

			if (row.childs && row.expanded)
			{
				var prevRow = row;
				row.childs.each(function(child, childIndex)
				{
					child.deep			= row.deep + 1;
					child.parent		= row;
					child._childIndex	= childIndex;
					child.last			= (child._childIndex + 1) == row.childs.length;
					prevRow = this.renderRow(child, prevRow, false, true);
				}, this);
			}

			this.setChildVisibleState(row, true, anim);
			this.rowDom[row._index].dom.removeClass("jcTree-row-collapsed").addClass("jcTree-row-expanded");

			if (this.listeners.onAfterExpandNode)
			{
				this.listeners.onAfterExpandNode(this, row);
			}
		},

		/**
		 * alle Rows aufklappen
		 *
		 * @param object row ab diesem Knoten oder ohne angabe dann ab root
		 * @access public
		 */
		expandRows: function(row)
		{
			if (Object.isUndefined(row))
			{
				this.data.each(function(child)
				{
					this.expandRows(child);
				}, this);
				return;
			}

			if (!row.expanded)
			{
				this.expandRow(row, false);
			}
			if (row.childs && row.childs.length != 0)
			{
				row.childs.each(function(child)
				{
					this.expandRows(child);
				}, this);
			}
		},

		/**
		 * sucht nach einer oder mehrere
		 *	options
		 *		name				type		default				description
		 *		=====================================================================================================================
		 *		cls					string		jcTree-row-search	CSS Klasse für die gefundenen
		 *		expand				boolean		true				zu die gefundenen aufklappen
		 *		collapse			boolean		false				alle anderen zuklappen
		 *		match				boolean		false				wenn match dann exakt übereinstimmend ansonsten enthält
		 *		multi				boolean		true				einen oder mehrere finden
		 *		scrollTo			boolean		true				zum ersten gefundenen scrollen
		 *
		 * @param string column
		 * @param string value
		 * @param object options
		 * @access public
		 * @return array|object|bool
		 */
		findRows: function(column, value, options)
		{
			if (Object.isUndefined(options))
			{
				options = {};
			}
			if (Object.isUndefined(options.cls))
			{
				options.cls = "jcTree-row-search";
			}
			if (Object.isUndefined(options.match))
			{
				options.match = false;
			}
			if (Object.isUndefined(options.expand))
			{
				options.expand = true;
			}
			if (Object.isUndefined(options.collapse))
			{
				options.collapse = false;
			}
			if (Object.isUndefined(options.multi))
			{
				options.multi = true;
			}
			if (Object.isUndefined(options.scrollTo))
			{
				options.scrollTo = true;
			}

			if (options.cls)
			{
				this.dom.select("." + options.cls).invoke("removeClass", options.cls);
			}

			var result = $A([]);
			var findRowsSearch = function(rows)
			{
				rows.find(function(row)
				{
					if ((options.match && row.data[column].value == value) || (!options.match && String(row.data[column].value).toLowerCase().indexOf(value.toLowerCase()) != -1))
					{
						result.push(row);
					}

					if (!options.multi && result.length != 0)
					{
						return true;
					}
					if (row.childs && row.childs.length != 0)
					{
						findRowsSearch(row.childs);
					}
				});
			};

			findRowsSearch(this.data);

			/* nix gefunden */
			if (result.length == 0)
			{
				return false;
			}

			/* gefundene anzeigen */
			if (options.expand)
			{
				/* alle einklappen */
				if (options.collapse)
				{
					this.collapseRows();
				}

				result.each(function(row, index)
				{
					this.expandRow(row, false);
					if (options.cls)
					{
						this.rowDom[row._index].dom.addClass(options.cls);
					}
					if (index == 0 && options.scrollTo)
					{
						this.rowDom[row._index].dom.scrollTo();
					}
				}, this);
			}

			if (options.multi)
			{
				return result;
			}

			return result[0];
		},

		/**
		 * liefert die Row anhand der ID
		 *
		 * @param mixed ind
		 * @access public
		 * @return object
		 */
		getRowById: function(id, rows)
		{
			if (Object.isUndefined(rows))
			{
				rows = this.data;
			}
			var result = null;

			rows.find(function(row)
			{
				if (row.id == id)
				{
					result = row;
					return true;
				}
				if (row.childs && row.childs.length != 0)
				{
					var child = this.getRowById(id, row.childs);
					if (child)
					{
						result = child;
						return true;
					}
				}

				return false;
			}, this);

			return result;
		},

		/**
		 * liefert den angegebene Index
		 *
		 * @param long|string index der index oder id (id-match bevorzugt bie gleichem datentyp)
		 * @access public
		 * @return object
		 */
		getRow: function(index)
		{
			if (typeof index != "string" && typeof index != "number")
			{
				return index;
			}

			return this.getRowById(index);
		},

		/**
		 * Pfad anhand einer spalte
		 *
		 * @param object row
		 * @param string column
		 * @access public
		 * @return string
		 */
		getRowPathByColumn: function(row, column, result)
		{
			if (Object.isUndefined(result))
			{
				result = $A([]);
			}

			result.push(row.data[column].value);

			if (row.parent.xType != jControls.Tree.xType)
			{
				result = this.getRowPathByColumn(row.parent, column, result);
			}

			return result;
		},

		/**
		 * Konstruktor
		 *
		 * @param object options
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Tree.xType;
			}

			/* merken */
			this.options		= options;
			this.rowDom			= [];
			this.rowExpanded	= [];
			this.rowCollapsed	= [];

			/* fehighlightendes setzen */
			this.options.enterable			= (typeof this.options.enterable		!= "undefined" ? this.options.enterable			: false);
			this.options.header				= (typeof this.options.header			!= "undefined" ? this.options.header			: true);
			this.options.multiselect		= (typeof this.options.multiselect		!= "undefined" ? this.options.multiselect		: false);
			this.options.nodesSelectable	= (typeof this.options.nodesSelectable	!= "undefined" ? this.options.nodesSelectable	: true);
			this.options.renderComplete		= (typeof this.options.renderComplete	!= "undefined" ? this.options.renderComplete	: false);
			this.options.sortable			= (typeof this.options.sortable			!= "undefined" ? this.options.sortable			: true);

			if (this.options.data.xType == jControls.NestedSetContainer.xType)
			{
				this.data = this.options.data.getTree();
			}
			else
			{
				this.data = this.options.data;
			}

			/* ids der Spalten ermitteln/prüfen */
			this.columns	= $A(options.columns);
			this._variableColumnWidth = 0;
			var firstVisibleColumn = false;
			this.columnsId	= this.columns.collect(function(column, index)
			{
				column.type		= column.type || "string";
				column.visible	= (typeof column.visible != "undefined" ? column.visible : true);
				column.sortable	= (typeof column.sortable != "undefined" ? column.sortable : true);
				this.columns[column.id] = column;

				/* formatfunktionen machen und columns mit id setzen */
				if (column.format)
				{
					column.format = column.format.bind(this.options);
				}

				/* Column style */
				column.style = column.style || {};
				if (!column.visible)
				{
					column.style.display = "none";
				}
				if (column.visible && !column.style.width)
				{
					this._variableColumnWidth++;
				}
				column.firstVisible = false;
				if (column.visible && !firstVisibleColumn)
				{
					firstVisibleColumn = true;
					column.firstVisible = true;

					if (Object.isUndefined(this.options.searchColumn) || this.options.searchColumn === true)
					{
						this.options.searchColumn = column.id;
					}
					if (Object.isUndefined(this.options.columnElbow))
					{
						this.options.columnElbow = column.id;
					}
				}
				column.styleText = Object.toStyleString(column.style);

				return column.id;
			}, this);
			if (this._variableColumnWidth > 0)
			{
				this._variableColumnWidth = Math.floor(100 / this._variableColumnWidth);
			}
			if (this.options.multiselect)
			{
				this.value = $A([]);
			}

			treeCorrectRowData(this.data);

			if (Object.isUndefined(this.options.buttonCollapseAll))
			{
				this.options.buttonCollapseAll = true;
			}
			if (Object.isUndefined(this.options.buttonExpandAll))
			{
				this.options.buttonExpandAll = true;
			}

			if (this.options.searchColumn)
			{
				if (Object.isUndefined(this.options.buttons))
				{
					this.options.buttons = $A([]);
				}
				this.options.buttons.unshift(
				{
		 			icon:			"jcBtn-search",
		 			iconDisabled:	"jcBtn-search-disabled",
		 			enabled:		false,
		 			text:			jControls.Button.TITLE.SEARCH,
					qtip:			jControls.Button.TITLE.SEARCH,
					cls:			"jcBtn-search-container",
		 			listeners:
		 			{
		 				onAfterClick: (function(btn)
		 				{
		 					if (btn.dom.previous().value)
		 					{
		 						this.findRows(this.options.searchColumn, btn.dom.previous().value);
		 					}
		 				}).bind(this),
		 				onAfterRender: (function(btn)
		 				{
		 					Element.insert(btn.dom,
		 					{
		 						before: "<input type='text' class='jcBtn-search-value' title='" + jControls.Button.TITLE.SEARCHTIPP + "' onclick='event.stop();'/>"
		 					});
		 					btn.dom.previous().on("keyup", function(e)
		 					{
		 						var value = btn.dom.previous().value;
		 						btn.setEnabled(value != "");
		 						if (e.keyCode == 13)
		 						{
		 							this.findRows(this.options.searchColumn, value);
		 						}
		 						else if (value.length >= jControls.Tree.MINSEARCHSIGN)
		 						{
		 							this.findRows(this.options.searchColumn, value,
		 							{
		 								collapse:	true,
		 								scrollTo:	false
		 							});
		 						}
		 					}, this);
		 				}).bind(this)
		 			}
		 		});
			}
			if (this.options.buttonExpandAll)
			{
				if (Object.isUndefined(options.buttons))
				{
					this.options.buttons = $A([]);
				}
				this.options.buttons.unshift(
				{
		 			icon:			"jcBtn-expand-all",
		 			iconDisabled:	"jcBtn-expand-all-disabled",
		 			text:			jControls.Button.TITLE.EXPANDALL,
					qtip:			jControls.Button.TITLE.EXPANDALL,
		 			listeners:
		 			{
		 				onAfterClick: (function()
		 				{
	 						this.expandRows();
		 				}).bind(this)
		 			}
		 		});
			}
			if (this.options.buttonCollapseAll)
			{
				if (Object.isUndefined(this.options.buttons))
				{
					this.options.buttons = $A([]);
				}
				this.options.buttons.unshift(
				{
		 			icon:			"jcBtn-collapse-all",
		 			iconDisabled:	"jcBtn-collapse-all-disabled",
		 			text:			jControls.Button.TITLE.COLLAPSEALL,
					qtip:			jControls.Button.TITLE.COLLAPSEALL,
		 			listeners:
		 			{
		 				onAfterClick: (function()
		 				{
		 					this.data[0].childs.each(function(child)
		 					{
		 						this.collapseRows(child);
		 					}, this);
		 					this.dom.scrollTo();
		 				}).bind(this)
		 			}
		 		});
			}

			/* parent */
			$super(options);
		},

		/**
		 * auswahl umkehren
		 *
		 * @access public
		 * @return this
		 */
		invertSelection: function()
		{
			/* nur bei mulitselect */
			if (!this.options.multiselect)
			{
				return this;
			}
			if (this.value == null)
			{
				this.value = $A([]);
			}

			/* markiertes markieren und umgekehrt */
			var findAll = (function(rows)
			{
				rows.each(function(row)
				{
					if (this.value.indexOf(row.id) == -1)
					{
						this.selectValue(row);
					}
					else
					{
						this.unselectValue(row);
					}

					if (row.childs && row.childs.length != 0)
					{
						findAll(row.childs);
					}
				}, this);
			}).bind(this);

			findAll(this.data);

			return this;
		},

		/**
		 * prüft ob die Row gerendert ist
		 *
		 * @param object row
		 * @access public
		 * @return bool
		 */
		isRowRendered: function(row)
		{
			return (this.rowDom[row._index] ? this.rowDom[row._index].rendered : false);
		},

		/**
		 * row markiert?
		 *
		 * @param object row
		 * @access public
		 * @return bool
		 */
		isRowSelected: function(row)
		{
			if (!this.options.multiselect)
			{
				return this.value == row.id;
			}
			if (!this.value || this.value.constructor !== Array)
			{
				return false;
			}
			if (!row)
			{
				return false;
			}

			return (this.value.indexOf(row.id) != -1 ? true : false);
		},

		/**
		 * eine Row nach unten verschieben
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		moveRowDown: function(row)
		{
			if (row._childIndex == (row.parent.xType ? row.parent.data : row.parent.childs).length - 1)
			{
				return this;
			}
			var rowOther = (row.parent.xType ? row.parent.data : row.parent.childs)[row._childIndex + 1];

			if (this.listeners.onBeforeRowMove && this.listeners.onBeforeRowMove(this, row, "down") === false)
			{
				return this;
			}
			if (this.listeners.onBeforeRowMove && this.listeners.onBeforeRowMove(this, rowOther, "up") === false)
			{
				return this;
			}

			row.parent.childs[row._childIndex + 1]	= row;
			row.parent.childs[row._childIndex]		= rowOther;

			rowOther._childIndex	= row._childIndex;
			row._childIndex			= row._childIndex + 1;

			this.updateRows();

			if (this.listeners.onAfterRowMove)
			{
				this.listeners.onAfterRowMove(this, row, "down");
				this.listeners.onAfterRowMove(this, rowOther, "up");
			}

			return this;
		},

		/**
		 * eine Row nach links verschieben
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		moveRowLeft: function(row)
		{
			if (row.deep == 0)
			{
				return this;
			}

			var rowOtherDown = (row.parent.parent.xType ? this.data : row.parent.parent.childs).findAll(function(child)
			{
				return child._childIndex > row.parent._childIndex;
			});
			var rowOtherUp = row.parent.childs.findAll(function(child)
			{
				return child._childIndex > row._childIndex;
			});

			if (this.listeners.onBeforeRowMove)
			{
				if (this.listeners.onBeforeRowMove(this, row, "left") === false)
				{
					return this;
				}
				if (rowOtherDown.find(function(child)
				{
					return this.listeners.onBeforeRowMove(this, child, "down") === false;
				}, this))
				{
					return this;
				}
				if (rowOtherUp.find(function(child)
				{
					return this.listeners.onBeforeRowMove(this, child, "up") === false;
				}, this))
				{
					return this;
				}
			}

			/* unten entfernen */
			row.parent.childs.splice(row._childIndex, 1);
			row.parent.childs.each(function(child, index)
			{
				child._childIndex = index;
			});

			/* oben einfügen */
			(row.parent.parent.xType ? this.data : row.parent.parent.childs).splice(row.parent._childIndex + 1, 0, row);
			(row.parent.parent.xType ? this.data : row.parent.parent.childs).each(function(child, index)
			{
				child._childIndex = index;
			});
			row.parent = row.parent.parent;

			this.updateRows();

			if (this.listeners.onAfterRowMove)
			{
				this.listeners.onAfterRowMove(this, row, "left");
				rowOtherDown.each(function(child)
				{
					this.listeners.onAfterRowMove(this, child, "down");
				}, this);
				rowOtherUp.each(function(child)
				{
					this.listeners.onAfterRowMove(this, child, "up");
				}, this);
			}

			return this;
		},

		/**
		 * eine Row nach rechts verschieben
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		moveRowRight: function(row)
		{
			if (row._childIndex == (row.parent.xType ? row.parent.data : row.parent.childs).length - 1)
			{
				return this;
			}

			var rowOtherUp = (row.parent.xType ? row.parent.data : row.parent.childs).findAll(function(child)
			{
				return child._childIndex > row.parent._childIndex;
			});

			var rowInsert = row.parent.childs[row._childIndex + 1];
			if (!rowInsert.childs || !Object.isArray(rowInsert.childs))
			{
				rowInsert.childs = $A([]);
			}
			var rowOtherDown = rowInsert.childs;

			if (this.listeners.onBeforeRowMove)
			{
				if (this.listeners.onBeforeRowMove(this, row, "right") === false)
				{
					return this;
				}
				if (rowOtherUp.find(function(child)
				{
					return this.listeners.onBeforeRowMove(this, child, "up") === false;
				}, this))
				{
					return this;
				}
				if (rowOtherDown.find(function(child)
				{
					return this.listeners.onBeforeRowMove(this, child, "down") === false;
				}, this))
				{
					return this;
				}
			}

			/* oben entfernen */
			(row.parent.xType ? row.parent.data : row.parent.childs).splice(row._childIndex, 1);
			(row.parent.xType ? row.parent.data : row.parent.childs).each(function(child, index)
			{
				child._childIndex = index;
			});

			/* unten einfügen */
			rowInsert.childs.splice(0, 0, row);
			rowInsert.childs.each(function(child, index)
			{
				child._childIndex = index;
			});
			row.parent = rowInsert;

			this.updateRows();

			if (this.listeners.onAfterRowMove)
			{
				this.listeners.onAfterRowMove(this, row, "left");
				rowOtherDown.each(function(child)
				{
					this.listeners.onAfterRowMove(this, child, "down");
				}, this);
				rowOtherUp.each(function(child)
				{
					this.listeners.onAfterRowMove(this, child, "up");
				}, this);
			}

			return this;
		},

		/**
		 * eine Row nach oben verschieben
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		moveRowUp: function(row)
		{
			if (row._childIndex == 0)
			{
				return this;
			}
			var rowOther = row.parent.childs[row._childIndex - 1];

			if (this.listeners.onBeforeRowMove && this.listeners.onBeforeRowMove(this, row, "up") === false)
			{
				return this;
			}
			if (this.listeners.onBeforeRowMove && this.listeners.onBeforeRowMove(this, rowOther, "down") === false)
			{
				return this;
			}

			row.parent.childs[row._childIndex - 1]	= row;
			row.parent.childs[row._childIndex]		= rowOther;

			rowOther._childIndex	= row._childIndex;
			row._childIndex			= row._childIndex - 1;

			this.updateRows();

			if (this.listeners.onAfterRowMove)
			{
				this.listeners.onAfterRowMove(this, row, "up");
				this.listeners.onAfterRowMove(this, rowOther, "down");
			}

			return this;
		},

		/**
		 * rendert
		 *
		 * @param node renderTo
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			var parents		= null;
			var html		= "";
			var cookie		= null;

			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			if (this.xType == jControls.Tree.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			/* Cookies holen */
			cookie = this.getCookie();
			if (this.options.sortable && cookie.sorted && this.columns[cookie.sortBy])
			{
				this.options.sortBy		= cookie.sortBy;
				this.options.sort		= cookie.sortOrder;
			}
			if (cookie.rowExpanded && cookie.rowCollapsed)
			{
				this.rowExpanded	= $A(cookie.rowExpanded);
				this.rowCollapsed	= $A(cookie.rowCollapsed);
			}

			/* sortieren */
			if (this.options.sortable && this.options.sort)
			{
				this.sort(this.options.sortBy || this.columnsId[0], this.options.sort);
			}
			else
			{
/*				this.cloneData(); */
			}

			if (this.xType == jControls.Tree.xType)
			{
				parents = renderTo.toggleDisplayed();
			}

			/**
			 * folgende strutkur wird erzeugt
			 *
			 *	<div CONTAINER>
			 *		<input type="hidden" ...>
			 *		HEAD
			 *		BODY
			 *	</div>
			 */

			/* Container */
			$super(renderTo);

			/* input */
			if (this.name && this.name != "" && !this.options.multiselect)
			{
				html = html + "<input type='hidden' name='" + this.name + "' value='" + (this.value || "") + "' />";
			}

			/* Spaltenkopf */
			if (this.options.header)
			{
				if (Object.isArray(this.options.columnStyles))
				{
					html = html + renderHeaderComplex(this);
				}
				else
				{
					html = html + renderHeaderSimple(this);
				}
			}

			/* body */
			html = html + "<div class='jcTree-scroller'><div class='jcTree-body'></div></div>";

			/* Footer */
			if (this.options.footer)
			{
				html = html + renderFooter(this);
			}

			/* einfügen */
			this._gBody = Element.insert(this.dom, { bottom: html }).down(".jcTree-body");

			/* scroller anpassen wenn nötig */
			correctBodyHeight(this);

			/* row zeichnen */
			this.data.each(function(row, rowIndex)
			{
				row._childIndex	= rowIndex;
				row.last		= (row._childIndex + 1) == this.data.length;
				this.renderRow(row, this._gBody);
			}, this);

			/* input Container setzen */
			if (this.name && this.name != "" && !this.options.multiselect)
			{
				this.inputContainer = this.dom.down("input", 0);
			}

			/* sort column setzen */
			this.setSortColumn.bind(this).defer(this.options.sortBy, this.options.sort);

			if (this.xType == jControls.Tree.xType)
			{
				renderTo.toggleDisplayed(parents);
				if (this.listeners.onAfterRender)
				{
					this.listeners.onAfterRender.defer(this);
				}
			}

			return this;
		},

		/**
		 * rendert die Row an die entsprechende stelle
		 *
		 * @param object row
		 * @param node renderTo
		 * @param bool valueShow
		 * @param bool renderHidden
		 * @access public
		 * @return node
		 */
		renderRow: function(row, renderTo, valueShow, renderHidden)
		{
			/**
			 * folgende Struktur wird für eine zeile erzeugt
			 *
			 *	<div ROW>
			 *		<table>
			 *			<tbody>
			 *				<tr>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					<td></td>
			 *					...
			 *				<tr>
			 *			</tbody>
			 *		</table>
			 *	</div>
			 */

			row._index	= row._index	|| this.rowDom.length;
			row.deep	= row.deep		|| 0;
			row.parent	= row.parent	|| this;
			row.id		= row.id		|| this.rowDom.length;
			if (typeof this.rowDom[row._index] == "undefined")
			{
				this.rowDom[row._index] = {
					rendered:	false,
					dom:		null
				};
			}
			if (!valueShow && this.rowDom[row._index].rendered)
			{
				return;
			}
			row.visible	= (typeof row.visible != "undefined" ? row.visible : true);

			if (!valueShow && this.listeners.onBeforeRenderNode && this.listeners.onBeforeRenderNode(this, row) === false)
			{
				return;
			}
			if (!Object.isUndefined(this.options.expandNodes) && Object.isUndefined(row.expanded))
			{
				row.expanded = this.options.expandNodes;
			}
			if (this.rowCollapsed.indexOf(row.id) != -1)
			{
				row.expanded = false;
			}
			else if (this.rowExpanded.indexOf(row.id) != -1)
			{
				row.expanded = true;
			}

			var tdhtml = "";

			this.columns.each(function(column, columnIndex)
			{
				if (typeof row.data[column.id] != "object")
				{
					row.data[column.id] =
					{
						value: row.data[column.id]
					};
				}
				var cell = row.data[column.id];
				if (Object.isUndefined(cell.visible))
				{
					cell.visible = true;
				}

				var subHtmlSpacer	= "";	/* Platzhalter welcher den platz macht damit der Text entsprechend einrückt */
				var subHtmlelBow	= "";	/* zeichnet die knotenlinien */
				var subHtmlSymbols	= "";	/* zeichnet die Symbole */

				if (this.options.columnElbow == column.id) /*column.firstVisible) */
				{
					/* Platzer */
					subHtmlSpacer = "<div class='jcTree-spacer'></div><div class='jcTree-spacer'></div>";
					if (row.deep != 0)
					{
						for (var i = 0; i < row.deep; i++)
						{
							var parentRow = row.parent;
							for (var j = 0; j < (row.deep - i - 1); j++)
							{
								parentRow = parentRow.parent
							}

							subHtmlSpacer	= subHtmlSpacer + "<div class='jcTree-spacer'></div>";
							subHtmlSymbols	= subHtmlSymbols + "<div class='jcTree-spacer'></div>";
							if (parentRow.last)
							{
								subHtmlelBow = subHtmlelBow + "<div class='jcTree-spacer'></div>";
							}
							else
							{
								subHtmlelBow = subHtmlelBow + "<div class='jcTree-elbow jcTree-elbow-line'></div>";
							}
						}
					}

					subHtmlelBow	= subHtmlelBow +
										"<div class='jcTree-elbow jcTree-elbow-node" + (row.last ? "-end" : "") + "'></div>" +
										"<div class='jcTree-elbow jcTree-elbow-item'></div>";

					if (row.childs && row.childs.length != 0)
					{
						subHtmlelBow	= subHtmlelBow + "<div class='jcTree-elbow jcTree-elbow-childs'></div>";
						subHtmlSymbols	= subHtmlSymbols + "<div class='jcTree-symbol jcTree-symbol-node' onclick='jControls.$(\"" + this.id + "\").alternateRow($(this).up(\".jcTree-row\")._jcTreeRow);event.stop();'></div>";
					}
					else
					{
						subHtmlSymbols	= subHtmlSymbols + "<div class='jcTree-spacer'></div>";
					}
					subHtmlSymbols	= subHtmlSymbols + "<div class='jcTree-symbol jcTree-symbol-icon'></div>";

					subHtmlelBow	= "<div class='jcTree-container-elbow'>" + subHtmlelBow + "</div>";
					subHtmlSymbols	= "<div class='jcTree-container-symbol jcTree-symbol-" + (row.childs && row.childs.length != 0 ? "folder" : "item") + "'>" + subHtmlSymbols + "</div>";
				}

				tdhtml = tdhtml +
							"<td" +
								(cell.id ? " id='" + cell.id + "'" : "") +
								" class='" +
									"jcTree-col " +
									"jcTree-cell " +
									"jcTree-cell-td-" + column.id + " " +
									"jcTree-cell-td-" + columnIndex + " " +
									(columnIndex == 0 ? "jcTree-cell-td-first" : "") + " " +
									(columnIndex == this.columns.length - 1 ? " jcTree-cell-td-last" : "") + " " +
									(cell.cls ? cell.cls : "") + " " +
									(column.firstVisible && Object.isArray(row.childs) && row.childs.length != 0 ? "jcTree-folder" : "jcTree-item") +
								"'" +
								" style='" +
									column.styleText +
									" " + Object.toStyleString(cell.style) +
									" " + (!cell.visible ? "display:none;" : "") +
									(cell.visible && column.visible && !column.style.width ? "width:" + this._variableColumnWidth + "%;" : "") +
								"'" +
								(cell.qtip ? "title='" + cell.qtip + "'" : "" ) +
								">" +
									subHtmlSpacer +
									subHtmlelBow +
									subHtmlSymbols +
									"<div" +
										" class='jcTree-cell-inner jcTree-cell-inner-" + column.id + " jcTree-cell-inner-" + columnIndex + (columnIndex == 0 ? " jcTree-cell-inner-first" : "") + (columnIndex == this.columns.length - 1 ? " jcTree-cell-inner-last" : "") + "'" +
										">" +
											(column.format ? column.format(cell.value) : cell.value) +
									"</div>" +
							"</td>";
			}, this);

			var rowStyle = Object.clone(row.style || {});
			rowStyle.width = "100%";
			if (renderHidden || (!valueShow && !row.visible))
			{
				rowStyle.display = "none";
			}

			tdhtml = 	"<div" +
							(!valueShow && row.domId ? " id='" + row.domId + "'" : "") +
							" class='" +
									"jcTree-row" +
									" jcTree-level-" + row.deep +
									(row._childIndex == 0 ? " jcTree-row-first-child" : "") +
									(row.last ? " jcTree-row-last-child" : "") +
									(!valueShow && row._index % 2 ? " jcTree-row-alt" : "") +
									(row.childs && row.childs.length != 0 && row.expanded ? " jcTree-row-expanded" : "") +
									(row.childs && row.childs.length != 0 && !row.expanded ? " jcTree-row-collapsed" : "") +
									(!valueShow && this.isRowSelected(row) ? " jcTree-row-selected" : "") +
									(row.cls ? " " + row.cls : "") +
								"'" +
							(!Object.isUndefined(row.qtip) && !valueShow ? " title='" + row.qtip + "'" : "") +
							" style='" + Object.toStyleString(rowStyle) + "'" +
							(row.childs && row.childs.length != 0
								?	" ondblclick='jControls.$(\"" + this.id + "\").alternateRow(this._jcTreeRow);event.stop();'"
								:	""
							) +
							">" +
								"<table cellspacing='0' cellpadding='0' border='0' class='jcTree-row-table' style='width:100%;'>" +
									"<tbody>" +
										"<tr>" +
											tdhtml +
										"</tr>" +
									"</tbody>" +
								"</table>" +
						"</div>";

			if (Object.isElement(renderTo))
			{
				var rowDom = Element.insert(renderTo,
				{
					bottom:	tdhtml
				}).childElements().last();
			}
			else
			{
				Element.insert(this.rowDom[renderTo._index].dom,
				{
					after:	tdhtml
				});
				var rowDom = this.rowDom[renderTo._index].dom.next();
			}
			rowDom._jcTreeRow = row;

			if (!valueShow)
			{
				(function()
				{
					if (this.listeners.onMouseOverRow)
					{
						Event.observe(rowDom, "mouseover", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseOverRow(this, row);
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onMouseOutRow)
					{
						Event.observe(rowDom, "mouseout", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseOutRow(this, row);
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onMouseMoveRow)
					{
						Event.observe(rowDom, "mousemove", (function(e)
						{
							if (row._pe)
							{
								row._pe.stop();
							}
							row._pe = new PeriodicalExecuter((function()
							{
								row._pe.stop();
								this.listeners.onMouseMoveRow(e, this, row);
							}).bind(this), 0.1);
						}).bindAsEventListener(this));
					}

					if (this.listeners.onBeforeClickRow	|| this.listeners.onAfterClickRow || this.enterable)
					{
						Event.observe(rowDom, "click", (function(e)
						{
							if (ONCLICK_IGNORE_ELEMENTTAG.indexOf(e.element().tagName.toUpperCase()) != -1)
							{
								return;
							}
							if (this.listeners.onBeforeClickNode && this.listeners.onBeforeClickNode(this, row) === false)
							{
								return;
							}
							if (this.enterable && this.enabled)
							{
								this.setValue(row);
							}
							if (this.listeners.onAfterClickNode)
							{
								this.listeners.onAfterClickNode.defer(this, row);
							}
						}).bindAsEventListener(this));
					}
				}).bind(this).defer();
			}

			if (!valueShow)
			{
				this.rowDom[row._index].rendered	= true;
				this.rowDom[row._index].dom			= rowDom;
				if (this.listeners.onAfterRenderNode)
				{
					this.listeners.onAfterRenderNode.defer(this, row, rowDom);
				}
			}

			/* kinder da? */
			var prevRow = row;
			if (!valueShow && row.childs && (row.expanded || this.options.renderComplete))
			{
				row.childs.each(function(child, childIndex)
				{
					child.deep			= row.deep + 1;
					child.parent		= row;
					child._childIndex	= childIndex;
					child.last			= (child._childIndex + 1) == row.childs.length;

					if (this.options.renderComplete && !row.expanded)
					{
						prevRow = this.renderRow(child, prevRow, valueShow, true);
					}
					else
					{
						prevRow = this.renderRow(child, prevRow, valueShow, renderHidden);
					}
				}, this);
			}

			return prevRow;
		},

		/**
		 * scroll zu der entsprechende Zeile
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		scrollTo: function(row)
		{
			var row = this.getRow(row);
			if (Object.isUndefined(row._index) || Object.isUndefined(this.rowDom[row._index]) || !this.rowDom[row._index].dom)
			{
				return;
			}
			var dom = this.rowDom[row._index].dom;
			var scroller = this.dom.down(".jcTree-scroller");

			scroller.scrollTop = 0;
			scroller.scrollTop = dom.getY() - scroller.getY() - (!Prototype.Browser.isIE ? dom.getBorderWidth("t") : 0);

			return this;
		},

		/**
		 * alles markieren
		 *
		 * @access public
		 * @return this
		 */
		selectAll: function()
		{
			if (!this.options.multiselect)
			{
				return this;
			}

			/* markiertes markieren und umgekehrt */
			var findAll = (function(rows)
			{
				rows.each(function(row)
				{
					this.selectValue(row);

					if (row.childs && row.childs.length != 0)
					{
						findAll(row.childs);
					}
				}, this);
			}).bind(this);

			findAll(this.data);
		},

		/**
		 * nichts markieren
		 *
		 * @access public
		 * @return this
		 */
		selectNone: function()
		{
			if (this.value == null)
			{
				this.value = $A([]);
			}
			if (this.value.length == 0)
			{
				return this;
			}

			/* onBeforeChange */
			if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, $A([])) === false)
			{
				return this;
			}
			this.value = $A([]);

			/* onChange */
			if (this.listeners.onChange)
			{
				this.listeners.onChange.defer(this, this.value, this.initialized);
			}

			/* markierung setzen */
			this.setSelection();

			return this;
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @access public
		 * @return this
		 */
		selectValue: function(value)
		{
			if (!this.options.multiselect)
			{
				return this.setValue(value);
			}
			if (this.value == null)
			{
				this.value = $A([]);
			}

			if (Object.isArray(value))
			{
				value = value.findAll(function(newvalue)
				{
					return this.value.indexOf(typeof newvalue.id != "undefined" ? newvalue.id : newvalue) == -1;
				}, this);
				if (value.find(function(nvalue)
				{
					var newValue = this.getRow(nvalue);
					if (!newValue)
					{
						return;
					}

					if (!this.options.nodesSelectable && newValue.childs && newValue.childs.length != 0)
					{
						return;
					}

					/* onBeforeChange */
					if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, nvalue) === false)
					{
						return true;
					}

					/* wert setzen */
					var oldValue = this.value;
					this.value.push(newValue.id);
					this.renderValue(oldValue, this.value);

					/* onChange */
					if (this.listeners.onChange)
					{
						this.listeners.onChange.defer(this, value, this.initialized);
					}
				}, this)) return this;

				/* markierung setzen */
				this.setSelection();
			}
			else
			{
				var newValue = this.getRow(value);
				if (!newValue)
				{
					return;
				}
				if (!this.options.nodesSelectable && newValue.childs && newValue.childs.length != 0)
				{
					return;
				}

				if (this.value.find(function(selValue)
				{
					return selValue == newValue.id;
				}))
				{
					return this;
				}

				/* onBeforeChange */
				if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, value) === false)
				{
					return this;
				}

				/* wert setzen */
				var oldValue = this.value;
				this.value.push(newValue.id);
				this.renderValue(oldValue, this.value);

				/* onChange */
				if (this.listeners.onChange)
				{
					this.listeners.onChange.defer(this, value, this.initialized);
				}

				/* markierung setzen */
				this.setSelection();
			}

			return this;
		},

		/**
		 * sichtbarkeitstatus setzen von kind nodes
		 *
		 * @param object row
		 * @param bool visible
		 * @access public
		 */
		setChildVisibleState: function(row, visible, anim, elements)
		{
			if (Object.isUndefined(anim))
			{
				anim = true;
			}

			if (row.childs && row.childs.length != 0)
			{
				var els = elements || $A([]);
				row.childs.each(function(child, childIndex)
				{
					if (!Object.isUndefined(child._index) && !Object.isUndefined(this.rowDom[child._index]))
					{
						els.push(this.rowDom[child._index].dom);
						if (!visible || (visible && child.expanded))
						{
							this.setChildVisibleState(child, visible, anim, els);
						}
					}
				}, this);

				if (Object.isUndefined(elements))
				{
					els.each(function(element)
					{
						if (anim)
						{
							element[visible ? "blindDown" : "blindUp"](
							{
								duration:	0.5,
								transition: Effect.Transitions.sinoidal
							});
						}
						else
						{
							element[visible ? "show" : "hide"]();
						}
					});
				}
			}
		},

		/**
		 * speichert neue cookie data ab
		 *
		 * @param object options
		 * @access public
		 * @return this
		 */
		setCookie: function($super, options)
		{
			if (!this.options.ajax)
			{
				options = Object.extend(options || {},
				{
					sorted:			this.sortBy != null,
					sortBy:			this.sortBy,
					sortOrder:		this.sortOrder,
					rowCollapsed:	this.rowCollapsed,
					rowExpanded:	this.rowExpanded
				})
			}

			return $super(options);
		},

		/**
		 * setzt die markierung
		 *
		 * @param object row
		 * @access public
		 * @return this
		 */
		setSelection: function(row)
		{
			if (!this.rendered)
			{
				return this;
			}

			if (!this.options.multiselect)
			{
				if (!row)
				{
					row = this.value;
				}
				if (row != null)
				{
					row = this.getRow(row);
					if (!row)
					{
						return this;
					}

					this.dom.down(".jcTree-row").removeClass("jcTree-row-selected").nextSiblings().invoke("removeClass", "jcTree-row-selected");
					var rowNode = (
									row._index == 0
										? this.dom.down(".jcTree-body").down(".jcTree-row")
										: this.dom.down(".jcTree-body").down(".jcTree-row").nextSiblings()[row._index - 1]
									);
					if (rowNode)
					{
						rowNode.addClass("jcTree-row-selected");
					}

				}
				else
				{
					this.dom.down(".jcTree-row").removeClass("jcTree-row-selected").nextSiblings().invoke("removeClass", "jcTree-row-selected");
				}
			}
			else if (Object.isArray(this.value))
			{
				this.dom.select(".jcTree-row-selected").invoke("removeClass", "jcTree-row-selected");
				if (!Object.isUndefined(this.name) && this.name != "")
				{
					this.dom.select(".jcTree-input-multiselect").invoke("remove");
				}

				this.value.each(function(selValue)
				{
					var rowValue = this.getRow(selValue);
					if (!Object.isUndefined(rowValue._index) && !Object.isUndefined(this.rowDom[rowValue._index]))
					{
						this.rowDom[rowValue._index].dom.addClass("jcTree-row-selected");
					}

					if (!Object.isUndefined(this.name) && this.name != "")
					{
						new jControls.Container(
						{
							type:		"input",
							cls:		"jcTree-input-multiselect",
							attributes:
							{
								type:	"hidden",
								name:	this.name,
								value:	selValue
							},
							renderTo:	this
						});
					}
				}, this);

			}
			else
			{
				this.dom.select(".jcTree-row-selected").invoke("removeClass", "jcTree-row-selected");
			}

			return this;
		},

		/**
		 * setzt die sortiertspalte
		 *
		 * @param string columnId
		 * @param string direction
		 * @acces public
		 * @return this
		 */
		setSortColumn: function(columnId, direction)
		{
			var headers = this.dom.down(".jcTree-header");

			if (!headers)
			{
				return;
			}

			/* spaltentitel neu setzen */
			this.sortBy		= columnId;
			this.sortOrder	= direction;

			/* altes entfernen */
			headers.select(".jcSort-asc", ".jcSort-desc").invoke("removeClass", "jcSort-asc jcSort-desc");

			/* neue setzen */
			if (this.sortBy != null && this.sortOrder != null)
			{
				headers.down(".jcTree-header-hd-" + this.sortBy).addClass("jcSort-" + this.sortOrder);
			}
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @param bool initialized
		 * @access public
		 * @return this
		 */
		setValue: function($super, value, initialized)
		{
			if (this.options.multiselect)
			{
				if (value && value.constructor === Array)
				{
					value.each(function(newvalue)
					{
						this.setValue(newvalue);
					}, this);
					return this;
				}
				else
				{
					return	(
								this.isRowSelected(this.getRow(value))
								? this.unselectValue(value)
								: this.selectValue(value)
							);
				}
			}

			if (value != null && !Object.isUndefined(value))
			{
				var newValue = (typeof value == "object" ? value : this.getRow(value));

				if (newValue)
				{
					if (!this.options.nodesSelectable && newValue.childs && newValue.childs.length != 0)
					{
						return;
					}

					$super(newValue.id, initialized);
					this.setSelection(newValue);
				}
				else
				{
					this.setValue(null);
				}

			}
			else
			{
				$super(value, initialized);
				this.setSelection(null);
			}

			return this;
		},

		/**
		 * die Daten nach einer spalte sortieren
		 *
		 * @param int|string	sortBy	nach diesem Key oder diesem Spaltenindex sortieren
		 * @param string		order	sortierrichtung ("asc" | "desc", default: "asc")
		 * @access public
		 */
		sort: function(sortBy, order)
		{
			if (this.listeners.onBeforeSort && this.listeners.onBeforeSort(this, sortBy, order) === false)
			{
				return;
			}

			this.sortBy		= sortBy;
			this.sortOrder	= (order || "asc").toLowerCase();

			if (this.rendered)
			{
				this.setCookie();
			}
/*
			this.options.data.sort((function(left, right)
			{
				if (!left._convertedData)
				{
					left._convertedData = [];
				}
				if (!left._convertedData[this.sortBy])
				{
					left._convertedData[this.sortBy] = convertToDataType(String(left[this.sortBy]).stripTags(), this.columns[this.sortBy].type);
				}
				var a = left._convertedData[this.sortBy];

				if (!right._convertedData)
				{
					right._convertedData = [];
				}
				if (!right._convertedData[this.sortBy])
				{
					right._convertedData[this.sortBy] = convertToDataType(String(right[this.sortBy]).stripTags(), this.columns[this.sortBy].type);
				}
				var b = right._convertedData[this.sortBy];

				if (this.sortOrder == "asc")
				{
					return a < b ? -1 : a > b ? 1 : 0;
				}
				else
				{
					return a > b ? -1 : a < b ? 1 : 0;
				}
			}).bind(this));
*/
/*			this.cloneData();*/
			if (this.listeners.onAfterSort)
			{
				this.listeners.onAfterSort.defer(this, sortBy, order);
			}

/*			this.update(); */
		},

		/**
		 * setzt den wert
		 *
		 * @param mixed newValue
		 * @access public
		 * @return this
		 */
		unselectValue: function(value)
		{
			if (!this.options.multiselect)
			{
				return this.setValue(null);
			}
			if (this.value == null)
			{
				return this;
			}

			if (Object.isArray(value))
			{
				value.each(function(newvalue)
				{
					this.unselectValue(newvalue);
				}, this);
				return this;
			}

			var newValue = this.getRow(value);
			if (!newValue)
			{
				return;
			}
			if (!this.value.find(function(selValue)
			{
				return selValue == newValue.id;
			})) return this;

			/* onBeforeChange */
			if (this.listeners.onBeforeChange && this.listeners.onBeforeChange(this, this.value, value) === false)
			{
				return this;
			}

			/* wert setzen */
			var oldValue = this.value;
			this.value = this.value.without(newValue.id);
			this.renderValue(oldValue, this.value);

			/* onChange */
			if (this.listeners.onChange)
			{
				this.listeners.onChange.defer(this, value, this.initialized);
			}

			/* markierung setzen */
			this.setSelection();
		},

		/**
		 * aktuaslisiert alles noch einmal
		 *
		 * @access public
		 */
		updateRows: function()
		{
			this.dom.select(".jcTree-row").invoke("remove");
			this.rowDom = $A([]);

			/* row zeichnen */
			this.data.each(function(row, rowIndex)
			{
				row._childIndex	= rowIndex;
				row.last		= (row._childIndex + 1) == this.data.length;
				this.renderRow(row, this._gBody);
			}, this);
		}
	});

	/**
	 * suche ab 3 Zeichen starten
	 */
	jControls.Tree.MINSEARCHSIGN = 3;
	/**
	 * xType
	 */
	jControls.Tree.xType = xTypes.Tree;

	/****************************************************************************************************
	 *																									*
	 *												CALENDAR											*
	 *																									*
	 ****************************************************************************************************/
	/**
	 * bereitet den Slide nach rechts/links vor beim blättern zB
	 *
	 * @param jControls.Calendar calendar
	 * @param int displayView
	 * @param element element
	 * @param bool|int anim
	 * @access private
	 * @return element
	 */
	var calendarEffectSlidePrepare = function(calendar, displayView, element, anim)
	{
		if (Prototype.Browser.isIE6 || Math.abs(anim) != 1 || calendar.displayView != displayView)
		{
			return;
		}

		/* effect vorbereitung */
		calendar.effect = true; /* effect in proigress */

		/* effect Element */
		var effectElement = element.down(".jcCalendar-effect");
		/* Effect Element Parent */
		var effectElementParent = effectElement.up();

		/* effect element Parent positionieren */
		effectElementParent.setStyle(
		{
			width:		effectElementParent.getWidth() + "px",
			height:		effectElementParent.getHeight() + "px",
			position:	"relative"
		});

		/* Effect Element positionieren */
		effectElement.setStyle(
		{
			left:		"0px",
			top:		"0px",
			width:		effectElement.getWidth() + "px",
			height:		effectElement.getHeight() + "px",
			position:	"absolute"
		});

		/* Clone vom Effect Element erstellen und ausgeben */
		return $(effectElement.cloneNode(true));
	};

	/**
	 * führt den Slide nach rechts/links vor beim blättern zB
	 *
	 * @param jControls.Calendar calendar
	 * @param int displayView
	 * @param element element
	 * @param element effectElementClone
	 * @param bool|int anim
	 * @access private
	 */
	var calendarEffectSlideRun = function(calendar, displayView, element, effectElementClone, anim)
	{
		if (Prototype.Browser.isIE6 || Math.abs(anim) != 1 || calendar.displayView != displayView)
		{
			return;
		}

		/* effect Element */
		var effectElement = element.down(".jcCalendar-effect");
		/* Effect Element Parent */
		var effectElementParent = effectElement.up();

		/* Effect Clone einfügen in Effect Parent */
		effectElementParent.insertTop(effectElementClone.addClass("this-is-the-clone"));							/* clone einfügen */
		/* Effect Element links/rechts vom Effect clone packen */
		effectElement.setX(effectElementClone.getX() - 1 * anim * effectElementClone.getWidth());	/* Effect Element rechts vom Effect clone packen */
		/* und los imt dem effect */
		Effect.multiple([effectElementClone, effectElement], Effect.Move,
		{
			x:				effectElementClone.getWidth() * anim,
			y:				0,
			mode:			"relative",
			speed:			0,
			delay:			0,
			duration:		0.3,
			afterFinish:	function(effect)
			{
				/* Schon erledigt? */
				if (!calendar.effect)
				{
					return;
				}

				effectElementParent.clearStyle("width height position"); /* Effect element parent cleaning */
				effectElement.clearStyle("left top width height position"); /* Effect element cleaning */
				effectElementClone.remove(); /* Effect element clone wieder löschen */
				calendar.effect = false; /* erledigt */
			}
		});
	};

	/**
	 * von einer alten view zur neuen View nach oben (zB Month to Year)
	 *
	 * @param jControls.Calendar calendar
	 * @param int displayView
	 * @param element oldView
	 * @param element newView
	 * @param int newSelected
	 * @access private
	 */
	var calendarEffectSwitchViewDown = function(calendar, displayView, oldView, newView, newSelected)
	{
		if (Prototype.Browser.isIE6)
		{
			oldView.hide();
			newView.show();
			return;
		}
		if (calendar.effect)
		{
			return;
		}

		calendar.displayView	= displayView;
		calendar.effect			= true;
		newSelected				= newView.down(".jcCalendar-entry-index-" + Math.abs(newSelected));

		var effectContainer		= oldView.up(".jcCalendar-effect-parent");
		effectContainer.setStyle(
		{
			width:		effectContainer.getWidth() + "px",
			height:		effectContainer.getHeight() + "px",
			overflow:	"hidden"
		});

		newView.show().setStyle(
		{
			position:	"absolute",
			left:		(newSelected ? newSelected.getX()		: calendar.dom.getX() - 5) + "px",
			top:		(newSelected ? newSelected.getY()		: calendar.dom.getY() - 5) + "px",
			width:		(newSelected ? newSelected.getWidth()	: calendar.dom.getX() + 5)+ "px",
			height:		(newSelected ? newSelected.getHeight()	: calendar.dom.getY() + 5)+ "px",
			overflow:	"hidden"
		}).setOpacity(0);

		oldView.setStyle(
		{
			position:	"absolute",
			left:		"0px",
			top:		"0px",
			width:		oldView.getWidth() + "px",
			height:		oldView.getHeight() + "px",
			overflow:	"hidden"
		});

		/* und los mit dem effect */
		new Effect.Parallel([
			new Effect.Morph(oldView,
			{
				sync:	true,
				style:
				{
					left:	(-1 * oldView.getWidth() * 1.25 / 4) + "px",
					top:	(-1 * oldView.getHeight() * 1.25 / 4) + "px",
					width:	(oldView.getWidth() * 1.25) + "px",
					height:	(oldView.getHeight() * 1.25) + "px"
				}
			}),
			new Effect.Fade(oldView,
			{
				sync:	true,
				from:	1,
				to:		0
			}),
			new Effect.Morph(newView,
			{
				sync:	true,
				style:
				{
					left:		"0px",
					top:		"0px",
					width:		oldView.getWidth() + "px",
					height:		oldView.getHeight() + "px"
				}
			}),
			new Effect.Appear(newView,
			{
				sync:	true,
				from:	0,
				to:		1
			})
		],
		{
			duration:		0.5,
			delay:			0.0,
			beforeStart:	function()
			{
				effectContainer.down(".jcCalender-effect-overlay").show();
			},
			afterFinish:	function(effect)
			{
				/* Schon erledigt? */
				if (!calendar.effect)
				{
					return;
				}

				effectContainer.down(".jcCalender-effect-overlay").hide();
				oldView.hide();
				oldView.clearStyle("left top width height position overflow");
				newView.clearStyle("left top width height position overflow");
				effectContainer.clearStyle("width height overflow");

				calendar.effect = false;
			}
		});
	};

	/**
	 * von einer alten view zur neuen View nach oben (zB Year to Month)
	 *
	 * @param jControls.Calendar calendar
	 * @param int displayView
	 * @param element oldView
	 * @param element newView
	 * @param int oldSelected
	 * @access private
	 */
	var calendarEffectSwitchViewUp = function(calendar, displayView, oldView, newView, oldSelected)
	{
		if (Prototype.Browser.isIE6)
		{
			oldView.hide();
			newView.show();
			return;
		}
		if (calendar.effect)
		{
			return;
		}

		calendar.displayView	= displayView;
		calendar.effect			= true;
		oldSelected				= oldView.down(".jcCalendar-entry-index-" + Math.abs(oldSelected));

		var effectContainer		= oldView.up(".jcCalendar-effect-parent");
		effectContainer.setStyle(
		{
			width:		effectContainer.getWidth() + "px",
			height:		effectContainer.getHeight() + "px",
			overflow:	"hidden"
		});

		newView.show().setStyle(
		{
			position:	"absolute",
			left:		(-1 * newView.getWidth() * 1.25 / 4) + "px",
			top:		(-1 * newView.getHeight() * 1.25 / 4) + "px",
			width:		(newView.getWidth() * 1.25) + "px",
			height:		(newView.getHeight() * 1.25) + "px",
			overflow:	"hidden"
		}).setOpacity(0);

		oldView.setStyle(
		{
			position:	"absolute",
			left:		"0px",
			top:		"0px",
			width:		oldView.getWidth() + "px",
			height:		oldView.getHeight() + "px",
			overflow:	"hidden"
		});

		/* und los mit dem effect */
		new Effect.Parallel([
			new Effect.Morph(oldView,
			{
				sync:	true,
				style:
				{
					left:	(oldSelected ? oldSelected.getX()		: calendar.dom.getX() - 5) + "px",
					top:	(oldSelected ? oldSelected.getY()		: calendar.dom.getY() - 5) + "px",
					width:	(oldSelected ? oldSelected.getWidth()	: calendar.dom.getX() + 5) + "px",
					height:	(oldSelected ? oldSelected.getHeight()	: calendar.dom.getY() + 5) + "px"
				}
			}),
			new Effect.Fade(oldView,
			{
				sync:	true,
				from:	1,
				to:		0
			}),
			new Effect.Morph(newView,
			{
				sync:	true,
				style:
				{
					left:		"0px",
					top:		"0px",
					width:		oldView.getWidth() + "px",
					height:		oldView.getHeight() + "px"
				}
			}),
			new Effect.Appear(newView,
			{
				sync:	true,
				from:	0,
				to:		1
			})
		],
		{
			duration:		0.5,
			delay:			0.0,
			beforeStart:	function()
			{
				effectContainer.down(".jcCalender-effect-overlay").show();
			},
			afterFinish:	function(effect)
			{
				/* Schon erledigt? */
				if (!calendar.effect)
				{
					return;
				}

				effectContainer.down(".jcCalender-effect-overlay").hide();
				oldView.hide();
				oldView.clearStyle("left top width height position overflow");
				newView.clearStyle("left top width height position overflow");
				effectContainer.clearStyle("width height overflow");

				calendar.effect = false;
			}
		});
	};

	/**
	 * rendert die grundlagen
	 *
	 * @param object options
	 * @access private
	 * @return string
	 */
	var calendarRenderViewBasics = function(options)
	{
		Element.insert(options.calendar.dom.down(".jcCalendar-effect-parent"),
		{
			bottom:	"<div class='jcCalendar-view-" + options.suffix + "'>" +
						(
							options.calendar.options.selectable >= options.displayView
							?
								"<div class='jcCalendar-select-upper'>" +
									"<div class='jcCalendar-select-prev'></div>" +
									"<div class='jcCalendar-select-next'></div>" +
									"<div class='jcCalendar-select-current'></div>" +
									"<div class='jcClear'></div>" +
								"</div>" +
								"<div class='jcCalendar-effect-parent'>" +
									"<div class='jcCalendar-effect'>" +
										options.html +
									"</div>" +
								"</div>"
							:	""
						) +
					"</div>"
		});

		var element = options.calendar.dom.down(".jcCalendar-view-" + options.suffix);

		/* wenn anzeige nicht gewollt */
		if (options.calendar.options.selectable < options.displayView)
		{
			return element;
		}

		/* Blättern vorheriges */
		element.down(".jcCalendar-select-prev").on(
		{
			click:	function()
			{
				if (options.calendar.effect)
				{
					return;
				}

				var date = options.calendar.displayValue.sub(options.interval.type, options.interval.value);

				if (!date.between(options.calendar.options.date.min, options.calendar.options.date.max))
				{
					date = options.calendar.options.date.min.clone();
				}

				/* before day click */
				if (options.listeners.prevBeforeClick && options.listeners.prevBeforeClick(options.calendar, date) === false)
				{
					return;
				}

				/* wert setzen */
				options.calendar.renderValue(date.add(options.interval.type, options.interval.value), date, true);

				/* after day click */
				if (options.listeners.prevAfterClick)
				{
					options.listeners.prevAfterClick(options.calendar, date);
				}
			}
		});

		/* Blättern nächstes */
		element.down(".jcCalendar-select-next").on(
		{
			click:		function()
			{
				if (options.calendar.effect)
				{
					return;
				}

				var date = options.calendar.displayValue.add(options.interval.type, options.interval.value);

				if (!date.between(options.calendar.options.date.min, options.calendar.options.date.max))
				{
					date = options.calendar.options.date.max.clone();
				}

				/* before day click */
				if (options.listeners.nextAfterClick && options.listeners.nextAfterClick(options.calendar, date) === false)
				{
					return;
				}

				/* wert setzen */
				options.calendar.renderValue(date.sub(options.interval.type, options.interval.value), date, true);

				/* after day click */
				if (options.listeners.nextBeforeClick)
				{
					options.listeners.nextBeforeClick(options.calendar, date);
				}
			}
		});

		/* Tagesansicht - Day onclick */
		element.select(".jcCalendar-entry").invoke("on",
		{
			click:	function()
			{
				if (options.calendar.effect || this.hasClassName("jcCalendar-entry-disabled"))
				{
					return;
				}

				/* Datumsindex extrahieren */
				options.listeners.entryClick(Number($w(this.className).find(function(cls)
				{
					return cls.startsWith("jcCalendar-entry-index-");
				}).substr("jcCalendar-entry-index-".length)), element);
			}
		});

		/* wenn IE6 dann hover effect von hand machen */
		if (Prototype.Browser.isIE6)
		{
			element.select(".jcCalendar-entry").invoke("on",
			{
				mouseover:	function()
				{
					if (!this.hasClassName("jcCalendar-entry-disabled"))
					{
						this.addClass("jcCalendar-entry-hover");
					}
				},
				mouseout:	function()
				{
					this.removeClass("jcCalendar-entry-hover");
				}
			});
			element.down(".jcCalendar-select-prev").on(
			{
				mouseover:	function()
				{
					this.addClass("jcCalendar-select-prev-hover");
				},
				mouseout:	function()
				{
					this.removeClass("jcCalendar-select-prev-hover");
				}
			});
			element.down(".jcCalendar-select-next").on(
			{
				mouseover:	function()
				{
					this.addClass("jcCalendar-select-next-hover");
				},
				mouseout:	function()
				{
					this.removeClass("jcCalendar-select-next-hover");
				}
			});
		}

		return element.hide();
	};

	/**
	 * Rendert die Zeitansicht
	 *
	 * @param jControls.Calendar calendar
	 * @access private
	 */
	var calendarRenderViewTime = function(calendar)
	{
		/*jcCalendar-effect-parent */
		calendar.dayView.down(".jcCalendar-select-upper").insertAfter(
			"<div class='jcCalendar-view-time'>" +
				"<div class='jcCalendar-time-hour'></div>" +
				"<div class='jcCalendar-time-separator'>:</div>" +
				"<div class='jcCalendar-time-minute'></div>" +
				"<div class='jcClear'></div>" +
			"</div>"
		);

		calendar.timeHour	= new jControls.Select(
		{
			renderTo:		calendar.dayView.down(".jcCalendar-time-hour"),
			value:			calendar.options.value ? calendar.options.value.format("H") : null,
			data:
			{
				header:		false,
				cls:		"jcCalendar-select-list-time",
				columns:	[
				{
					id:		"value",
					type:	"string"
				}],
				data:		$R(0, 23).inject([], function(acc, index)
				{
					acc.push(
					{
						id: 	String.leftPad(index, 2, "0"),
						value:	String.leftPad(calendar.options.time.meridiem && index > 11 ? index - 12 : index, 2, "0") + (calendar.options.time.meridiem ? " " + (index <= 11 ? "am" : "pm") : "")
					});
					return acc;
				})
			},
			listeners:
	 		{
	 			onChange: function(select, value, initialized)
	 			{
	 				if (initialized && value != null)
	 				{
	 					if (calendar.value)
	 					{
		 					var date = calendar.displayValue.clone();
			 				date.setHours(Number(value));
			 				if (!date.isEqual(calendar.value))
			 				{
								calendar.setValue(date);
			 				}
	 					}
	 					else
	 					{
	 						calendar.displayValue.setHours(Number(value));
	 					}
					}
	 			}
	 		}
		});
		calendar.timeMinute	= new jControls.Select(
		{
			renderTo:		calendar.dayView.down(".jcCalendar-time-minute"),
			value:			calendar.options.value ? String.leftPad(Math.floor(calendar.options.value.getMinutes() / calendar.options.time.intervalMinute) * calendar.options.time.intervalMinute, 2, "0") : null,
			data:
			{
				header:		false,
				cls:		"jcCalendar-select-list-time",
				columns:	[
				{
					id:		"id",
					type:	"string"
				}],
				data:		$R(0, Math.floor(59 / calendar.options.time.intervalMinute)).inject([], function(acc, index)
				{
					acc.push(
					{
						id: String.leftPad(index * calendar.options.time.intervalMinute, 2, "0")
					});
					return acc;
				})
			},
			listeners:
	 		{
	 			onChange: function(select, value, initialized)
	 			{
	 				if (initialized && value != null)
	 				{
		 				if (calendar.value)
	 					{
	 						var date = calendar.displayValue.clone();
			 				date.setMinutes(Number(value));
			 				if (!date.isEqual(calendar.value))
			 				{
								calendar.setValue(date);
			 				}
	 					}
	 					else
	 					{
	 						calendar.displayValue.setMinutes(Number(value));
	 					}
	 				}
	 			}
	 		}
		});
	};

	/**
	 * Rendert die Tagesansicht
	 *
	 * @param jControls.Calendar calendar
	 * @access private
	 */
	var calendarRenderViewDays = function(calendar)
	{
		var element = calendarRenderViewBasics(
		{
			calendar:		calendar,
			suffix:			"days",
			displayView:	jControls.Calendar.SELECTABLE.DAY,
			interval:
			{
				type:	Date.MONTH,
				value:	1
			},
			listeners:
			{
				prevBeforeClick:	calendar.listeners.onBeforeMonthPrevClick,
				prevAfterClick:		calendar.listeners.onAfterMonthPrevClick,
				nextBeforeClick:	calendar.listeners.onBeforeMonthNextClick,
				nextAfterClick:		calendar.listeners.onAfterMonthNextClick,
				entryClick:			function(dateIndex)
				{
					/* datum berechnen über den ersten des monats davon den ersten der woche und dann */
					/* den Datumsindex dazurechnen */
					var date = calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().clearTime().add(Date.DAY, dateIndex);

					/* wenn der gefundene Tag ausserhalb unsere definierten Grenzen liegt dann nix tun */
					if (!date.between(calendar.options.date.min.clearTime(true), calendar.options.date.max.clearTime(true)))
					{
						return;
					}

					/* before day click */
					if (calendar.listeners && calendar.listeners.onBeforeDayClick && calendar.listeners.onBeforeDayClick(calendar, date) === false)
					{
						return;
					}

					/* wert setzen */
					if (calendar.value)
					{
						date.setHours(calendar.value.getHours());
						date.setMinutes(calendar.value.getMinutes());
					}
					else
					{
						date.setHours(calendar.displayValue.getHours());
						date.setMinutes(calendar.displayValue.getMinutes());
					}
	 				if (!calendar.value || !date.isEqual(calendar.value))
	 				{
						calendar.setValue(date);
	 				}

					/* after day click */
					if (calendar.listeners && calendar.listeners.onAfterDayClick)
					{
						calendar.listeners.onAfterDayClick(calendar, date);
					}
				}
			},
			html:
				"<div class='jcCalendar-week-line'>" +
					Date.dayNames.inject("", function(acc, dayName, index)
					{
						index = Date.firstDayOfWeek + index - (Date.firstDayOfWeek + index <= 6 ? 0 : 7);
						return acc + "<div class='jcCalendar-week-entry" + (index == 0 || index == 6 ? " jcCalendar-entry-weekend" : "") + "'>" + Date.getShortDayName(index).substring(0, 2) + "</div>";
					}) +
					"<div class='jcClear'></div>" +
				"</div>" +
				$R(0, 41).inject('', function(acc, dateIndex)
				{
					var index = dateIndex % 7;
					if (index == 0)
					{
						if (dateIndex != 0)
						{
							acc = acc + "<div class='jcClear'></div></div>";
						}
						acc = acc + "<div class='jcCalendar-entries-line'>";
					}
					index = Date.firstDayOfWeek + index - (Date.firstDayOfWeek + index <= 6 ? 0 : 7);
					return acc +
							"<div class='jcCalendar-entry jcCalendar-entry-index-" + dateIndex + (index == 0 || index == 6 ? " jcCalendar-entry-weekend" : "") + "'>" +
								"<div class='jcCalendar-entry-text'>" + (calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().add(Date.DAY, dateIndex).format("j")) + "</div>" +
							"</div>";
				}) +
				"<div class='jcClear'></div></div>"
		});

		/* Tagesansicht  - monatsauswahl onclick */
		element.down(".jcCalendar-select-current").on(
		{
			click:	function()
			{
				/* Animation von Days to Month */
				calendarEffectSwitchViewUp(calendar, jControls.Calendar.SELECTABLE.MONTH, element, calendar.monthView, ((calendar.displayValue.clearTime(true) - calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().clearTime()) / (1000 * 60 * 60 * 24)).round());
			}
		});

		return element;
	};

	/**
	 * Rendert die Monatsansicht
	 *
	 * @param jControls.Calendar calendar
	 * @access private
	 */
	var calendarRenderViewMonths = function(calendar)
	{
		var element = calendarRenderViewBasics(
		{
			calendar:		calendar,
			suffix:			"months",
			displayView:	jControls.Calendar.SELECTABLE.MONTH,
			interval:
			{
				type:	Date.YEAR,
				value:	1
			},
			listeners:
			{
				prevBeforeClick:	calendar.listeners.onBeforeYearPrevClick,
				prevAfterClick:		calendar.listeners.onAfterYearPrevClick,
				nextBeforeClick:	calendar.listeners.onBeforeYearNextClick,
				nextAfterClick:		calendar.listeners.onAfterYearNextClick,
				entryClick:			function(dateIndex)
				{
					/* datum berechnen */
					var date = Date.parseDate("01." + String.leftPad(dateIndex + 1, 2, "0") + "." + calendar.displayValue.getFullYear()).getLastDateOfMonth();
					/* wenn der gefundene Tag ausserhalb unsere definierten Grenzen liegt dann nix tun */
					if (date < calendar.options.date.min.clearTime(true) || calendar.options.date.max.clearTime(true) < date.getFirstDateOfMonth())
					{
						return;
					}

					/* nun passendes Datum machen */
					date = calendar.displayValue.clone();
					date.setMonth(dateIndex);
					while(date.getMonth() != dateIndex)
					{
						date = date.sub(Date.DAY, 1);
					}
					/* Prüfung ob noch in grenze */
					if (date < calendar.options.date.min.clearTime(true))
					{
						date = calendar.options.date.min.clone();
					}
					if (calendar.options.date.max.clearTime(true) < date)
					{
						date = calendar.options.date.max.clone();
					}

					/* before day click */
					if (calendar.listeners && calendar.listeners.onBeforeMonthClick && calendar.listeners.onBeforeMonthClick(calendar, date) === false)
					{
						return;
					}

					/* wert setzen */
					if (calendar.value)
					{
						date.setHours(calendar.value.getHours());
						date.setMinutes(calendar.value.getMinutes());
					}
					else
					{
						date.setHours(calendar.displayValue.getHours());
						date.setMinutes(calendar.displayValue.getMinutes());
					}
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.MONTH)
					{
						calendar.renderValue(calendar.displayValue, date);
					}
					else if (!calendar.value || !date.getFirstDateOfMonth().isEqual(calendar.value))
					{
						calendar.setValue(date.getFirstDateOfMonth());
					}

					/* after day click */
					if (calendar.listeners && calendar.listeners.onAfterMonthClick)
					{
						calendar.listeners.onAfterMonthClick(calendar, date);
					}

					/*  Animation von Month to Day */
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.MONTH)
					{
						calendarEffectSwitchViewDown(calendar, jControls.Calendar.SELECTABLE.DAY, element, calendar.dayView, ((calendar.displayValue.clearTime(true) - calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().clearTime()) / (1000 * 60 * 60 * 24)).round());
					}
				}
			},
			html: $R(0, 11).inject("", function(acc, monthIndex)
			{
				var index = monthIndex % 4;

				if (index == 0)
				{
					if (monthIndex != 0)
					{
						acc = acc + "<div class='jcClear'></div></div>";
					}
					acc = acc + "<div class='jcCalendar-entries-line'>";
				}
				return acc +
						"<div class='jcCalendar-entry jcCalendar-entry-index-" + monthIndex + "'>" +
							"<div class='jcCalendar-entry-text'>" +
								Date.getShortMonthName(monthIndex) +
							"</div>" +
						"</div>";
			}) +
			"<div class='jcClear'></div></div>"
		});

		/* Monatsansicht - jahresauswahl onclick */
		element.down(".jcCalendar-select-current").on(
		{
			click:	function()
			{
				/* Animation von Month to Year */
				calendarEffectSwitchViewUp(calendar, jControls.Calendar.SELECTABLE.YEAR, element, calendar.yearView, calendar.displayValue.getMonth());
			}
		});

		return element;
	};

	/**
	 * Rendert die Jahresansicht
	 *
	 * @param jControls.Calendar calendar
	 * @access private
	 */
	var calendarRenderViewYears = function(calendar)
	{
		var element = calendarRenderViewBasics(
		{
			calendar:		calendar,
			suffix:			"years",
			displayView:	jControls.Calendar.SELECTABLE.YEAR,
			interval:
			{
				type:	Date.YEAR,
				value:	9
			},
			listeners:
			{
				prevBeforeClick:	calendar.listeners.onBeforeYearsPrevClick,
				prevAfterClick:		calendar.listeners.onAfterYearsPrevClick,
				nextBeforeClick:	calendar.listeners.onBeforeYearsNextClick,
				nextAfterClick:		calendar.listeners.onAfterYearsNextClick,
				entryClick:			function(dateIndex, element)
				{
					/* datum berechnen */
					var date = Math.floor(calendar.displayValue.getFullYear() / 10) * 10 - 1 + dateIndex;

					/* wenn der gefundene Tag ausserhalb unsere definierten Grenzen liegt dann nix tun */
					if (date < calendar.options.date.min.getFullYear || calendar.options.date.max.getFullYear() < date)
					{
						return;
					}

					/* korrektes datum machen */
					date = Date.parseDate(calendar.displayValue.format("d.m.") + date);
					/* Prüfung ob noch in grenze */
					if (date < calendar.options.date.min.clearTime(true))
					{
						date = calendar.options.date.min.clone();
					}
					if (calendar.options.date.max.clearTime(true) < date)
					{
						date = calendar.options.date.max.clone();
					}

					/* before day click */
					if (calendar.listeners && calendar.listeners.onBeforeYearClick && calendar.listeners.onBeforeYearClick(calendar, date) === false)
					{
						return;
					}

					/* wert setzen */
					if (calendar.value)
					{
						date.setHours(calendar.value.getHours());
						date.setMinutes(calendar.value.getMinutes());
					}
					else
					{
						date.setHours(calendar.displayValue.getHours());
						date.setMinutes(calendar.displayValue.getMinutes());
					}
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.YEAR)
					{
						calendar.renderValue(calendar.displayValue, date);
					}
					else if (!calendar.value || !date.getFirstDateOfYear().isEqual(calendar.value))
	 				{
						calendar.setValue(date.getFirstDateOfYear());
					}

					/* after day click */
					if (calendar.listeners && calendar.listeners.onAfterYearClick)
					{
						calendar.listeners.onAfterYearClick(calendar, date);
					}

					/* animation von Year to Month */
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.YEAR)
					{
						calendarEffectSwitchViewDown(calendar, jControls.Calendar.SELECTABLE.MONTH, element, calendar.monthView, calendar.displayValue.getMonth());
					}
				}
			},
			html: $R(0, 11).inject("", function(acc, yearIndex)
			{
				var index = yearIndex % 4;

				if (index == 0)
				{
					if (yearIndex != 0)
					{
						acc = acc + "<div class='jcClear'></div></div>";
					}
					acc = acc + "<div class='jcCalendar-entries-line'>";
				}
				return acc +
						"<div class='jcCalendar-entry jcCalendar-entry-index-" + yearIndex +  (yearIndex == 0 || yearIndex == 11  ? " jcCalendar-entry-outofrange" : "") + "'>" +
							"<div class='jcCalendar-entry-text'>" +
								(yearIndex) +
							"</div>" +
						"</div>";
			}) +
			"<div class='jcClear'></div></div>"
		});

		/* Jahresansicht - jahresauswahl onclick */
		element.down(".jcCalendar-select-current").on(
		{
			click:	function()
			{
				/* Animation von Year to Decade */
				calendarEffectSwitchViewUp(calendar, jControls.Calendar.SELECTABLE.DECADE, element, calendar.decadeView, calendar.displayValue.getFullYear() - Math.floor(calendar.displayValue.getFullYear() / 10) * 10 - 1);
			}
		});

		return element;
	};

	/**
	 * Rendert die Dekadeansicht
	 *
	 * @param jControls.Calendar calendar
	 * @access private
	 */
	var calendarRenderViewDecade = function(calendar)
	{
		return calendarRenderViewBasics(
		{
			calendar:		calendar,
			suffix:			"decade",
			displayView:	jControls.Calendar.SELECTABLE.DECADE,
			interval:
			{
				type:	Date.YEAR,
				value:	100
			},
			listeners:
			{
				prevBeforeClick:	calendar.listeners.onBeforeDecadePrevClick,
				prevAfterClick:		calendar.listeners.onAfterDecadePrevClick,
				nextBeforeClick:	calendar.listeners.onBeforeDecadeNextClick,
				nextAfterClick:		calendar.listeners.onAfterDecadeNextClick,
				entryClick:			function(dateIndex, element)
				{
					/* datum berechnen */
					var date = Math.floor(calendar.displayValue.getFullYear() / 100) * 100 - 10 + dateIndex * 10;

					/* wenn der gefundene Tag ausserhalb unsere definierten Grenzen liegt dann nix tun */
					if (date < calendar.options.date.min.getFullYear || calendar.options.date.max.getFullYear() < date)
					{
						return;
					}

					/* korrektes datum machen */
					date = Date.parseDate(calendar.displayValue.format("d.m.") + date);
					/* Prüfung ob noch in grenze */
					if (date < calendar.options.date.min.clearTime(true))
					{
						date = calendar.options.date.min.clone();
					}
					if (calendar.options.date.max.clearTime(true) < date)
					{
						date = calendar.options.date.max.clone();
					}

					/* before day click */
					if (calendar.listeners && calendar.listeners.onBeforeYearClick && calendar.listeners.onBeforeYearClick(calendar, date) === false)
					{
						return;
					}

					/* wert setzen */
					if (calendar.value)
					{
						date.setHours(calendar.value.getHours());
						date.setMinutes(calendar.value.getMinutes());
					}
					else
					{
						date.setHours(calendar.displayValue.getHours());
						date.setMinutes(calendar.displayValue.getMinutes());
					}
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.DECADE)
					{
						calendar.renderValue(calendar.displayValue, date);
					}
					else if (!calendar.value || !date.getFirstDateOfYear().isEqual(calendar.value))
	 				{
						calendar.setValue(date.getFirstDateOfYear());
					}

					/* after day click */
					if (calendar.listeners && calendar.listeners.onAfterYearClick)
					{
						calendar.listeners.onAfterYearClick(calendar, date);
					}

					/* animation von Decade to Year */
					if (calendar.options.selectable > jControls.Calendar.SELECTABLE.DECADE)
					{
						calendarEffectSwitchViewDown(calendar, jControls.Calendar.SELECTABLE.YEAR, element, calendar.yearView, calendar.displayValue.getFullYear() - Math.floor(calendar.displayValue.getFullYear() / 10) * 10 - 1);
					}
				}
			},
			html: $R(0, 11).inject("", function(acc, yearIndex)
			{
				var index = yearIndex % 4;

				if (index == 0)
				{
					if (yearIndex != 0)
					{
						acc = acc + "<div class='jcClear'></div></div>";
					}
					acc = acc + "<div class='jcCalendar-entries-line'>";
				}
				return acc +
						"<div class='jcCalendar-entry jcCalendar-entry-index-" + yearIndex + (yearIndex == 0 || yearIndex == 11  ? " jcCalendar-entry-outofrange" : "") + "'>" +
							"<div class='jcCalendar-entry-text'>" +
								(yearIndex) +
							"</div>" +
						"</div>";
			}) +
			"<div class='jcClear'></div></div>"
		});
	};

	/**
	 * Tagesansicht aktualisieren
	 *
	 * @param jControls.Calendar calendar
	 * @param date oldValue
	 * @param bool|int anim
	 * @access private
	 */
	var calendarUpdateViewDays = function(calendar, oldValue, anim)
	{
		/* anicht TAG */
		/* nur so komplett zeichnen, wenn oldValue nicht gibt oder sich der Monat/Jahr geändert hat */
		if (oldValue == null || anim) /*oldValue == null || oldValue.getMonth() != calendar.displayValue.getMonth() || oldValue.getFullYear() != calendar.displayValue.getFullYear()) */
		{
			/* Monat / Jahr in der Ansicht TAG */
			calendar.dayView.down(".jcCalendar-select-current").applyTo(calendar.displayValue.format("F Y"));

			/* effect vorbereitung */
			var effectElementClone = calendarEffectSlidePrepare(calendar, jControls.Calendar.SELECTABLE.DAY, calendar.dayView, anim);

			/* wenn vorheriger Monat der letzte Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.dayView.down(".jcCalendar-select-prev")[
				calendar.displayValue.sub(Date.MONTH, 1).getLastDateOfMonth().between(calendar.options.date.min, calendar.options.date.max)
				? "show"
				: "hide"
			]().quickTip(calendar.displayValue.sub(Date.MONTH, 1).format("F Y")); /* prev tooltip anpassen */

			/* wenn nächster Monat der erste Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.dayView.down(".jcCalendar-select-next")[
				calendar.displayValue.add(Date.MONTH, 1).getFirstDateOfMonth().between(calendar.options.date.min, calendar.options.date.max)
				? "show"
				: "hide"
			]().quickTip(calendar.displayValue.add(Date.MONTH, 1).format("F Y")); /* next tooltip anpassen */

			/* Datumswerte aktualisieren in der ansicht TAG */
			/* alle zeilen erstmal verstecken */
			calendar.dayView.select(".jcCalendar-entries-line").invoke("hide");
			/* sonderklassen entfernen */
			calendar.dayView.select(".jcCalendar-entry").invoke("removeClass", "jcCalendar-entry-disabled jcCalendar-entry-outofrange jcCalendar-entry-selected jcCalendar-entry-now");
			/* nun vom ersten Tag der Woche in Bezug auf den Ersten des Monats */
			/* bis zum letzten Tag der Woche in Bezug auf den Letzten des Monats */
			/* alle Tage durchlaufen */
			$R(calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().clearTime(), calendar.displayValue.getLastDateOfMonth().getLastDateOfWeek().clearTime()).each(function(date, index)
			{
				/* Tag-Datum ins Element setzen */
				var element = calendar.dayView.down(".jcCalendar-entry-index-" + index);
				element.down(".jcCalendar-entry-text").applyTo(date.format("j"));

				/* elternzeile sichbar machen (dadurch dass alle eltern zeilen ausgeblendet wurden werden nur die eingeblendet, welche benötigt werden) */
				element.up(".jcCalendar-entries-line").show();

				/* ist der Tag nicht innerhalb des aktuellen Monats in Bezug auf den aktuellen wert, dann entsprechende Class setzen */
				if (!date.between(calendar.displayValue.getFirstDateOfMonth().clearTime(), calendar.displayValue.getLastDateOfMonth().clearTime()))
				{
					element.addClass("jcCalendar-entry-outofrange");
				}

				/* ist der Tag identisch mit heute, dann entsprechende Class setzen */
				if (date.isEqual((new Date()).clearTime()))
				{
					element.addClass("jcCalendar-entry-now");
				}

				/* ist der Tag identisch mit den aktuellen wert, dann entsprechende Class setzen */
				if (calendar.value && date.isEqual(calendar.value.clearTime(true)))
				{
					element.addClass("jcCalendar-entry-selected");
				}

				/* wenn der Tag außerhalb der definierten möglichen Grenzen liegt, dann deaktvieren */
				if (!date.between(calendar.options.date.min.clearTime(true), calendar.options.date.max.clearTime(true)))
				{
					element.addClass("jcCalendar-entry-disabled");
				}
			});

			/* wenn wertänderung dann EFFECT ausführen */
			calendarEffectSlideRun(calendar, jControls.Calendar.SELECTABLE.DAY, calendar.dayView, effectElementClone, anim);
		}
		/* da sich nur der entsprechende Tag geändert hat, welcher markiert wurde, dann brauchen wir auch diesen nur setzen */
		else if (oldValue != null && !oldValue.isEqual(calendar.displayValue))
		{
			if (calendar.dayView.down(".jcCalendar-entry-selected"))
			{
				calendar.dayView.down(".jcCalendar-entry-selected").removeClass("jcCalendar-entry-selected");
			}
			/* nun den entsprechend Date Index finden */
			/* die differenz von 2 Datumswerten sind millisekunden... die müssen noch in tage umgerechnet werden */
			/* achtung bei der berechnung werden Zeitumstellung ala Sommerzeit beachtet */
			/* daher muss das ergebnis gerundet werden */
			var index = ((calendar.displayValue.clearTime(true) - calendar.displayValue.getFirstDateOfMonth().getFirstDateOfWeek().clearTime()) / (1000 * 60 * 60 * 24)).round();
			/* dann das element finden */
			calendar.dayView.down(".jcCalendar-entry-index-" + index).addClass("jcCalendar-entry-selected");
		}
	};

	/**
	 * Monatsansicht aktualisieren
	 *
	 * @param jControls.Calendar calendar
	 * @param date oldValue
	 * @param bool|int anim
	 * @access private
	 */
	var calendarUpdateViewMonths = function(calendar, oldValue, anim)
	{
		/* anicht TAG */
		/* nur so komplett zeichnen, wenn oldValue nicht gibt oder sich der Monat/Jahr geändert hat */
		if (oldValue == null || oldValue.getFullYear() != calendar.displayValue.getFullYear())
		{
			/* Jahr in der Ansicht Monat */
			calendar.monthView.down(".jcCalendar-select-current").applyTo(calendar.displayValue.format("Y"));

			/* effect vorbereitung */
			var effectElementClone = calendarEffectSlidePrepare(calendar, jControls.Calendar.SELECTABLE.MONTH, calendar.monthView, anim);

			/* wenn vorheriges Jahr der letzte Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.monthView.down(".jcCalendar-select-prev")[
				Date.parseDate("31.12." + (calendar.displayValue.getFullYear() - 1)).between(calendar.options.date.min, calendar.options.date.max)
				? "show"
				: "hide"
			]().quickTip(calendar.displayValue.getFullYear() - 1); /* prev tooltip anpassen */

			/* wenn nächstes Jahr der erste Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.monthView.down(".jcCalendar-select-next")[
				Date.parseDate("01.01." + (calendar.displayValue.getFullYear() + 1)).between(calendar.options.date.min, calendar.options.date.max)
				? "show"
				: "hide"
			]().quickTip(calendar.displayValue.getFullYear() + 1); /* next tooltip anpassen */

			/* Datumswerte aktualisieren in der ansicht TAG */
			/* sonderklassen entfernen */
			calendar.monthView.select(".jcCalendar-entry").invoke("removeClass", "jcCalendar-entry-disabled jcCalendar-entry-selected jcCalendar-entry-now");
			/* nun alle Monate durchlaufen */
			$R(0, 11).each(function(index)
			{
				/* Tag-Datum ins Element setzen */
				var date = Date.parseDate("01." + String.leftPad(index + 1, 2, "0") + "." + calendar.displayValue.getFullYear()).clearTime();
				var element = calendar.monthView.down(".jcCalendar-entry-index-" + index).quickTip(date.format("F Y"));

				/* ist der Monat identisch mit heute, dann entsprechende Class setzen */
				if (date.isEqual((new Date()).getFirstDateOfMonth().clearTime()))
				{
					element.addClass("jcCalendar-entry-now");
				}

				/* ist der Monat identisch mit den aktuellen wert, dann entsprechende Class setzen */
				if (calendar.value && date.isEqual(calendar.value.getFirstDateOfMonth().clearTime(true)))
				{
					element.addClass("jcCalendar-entry-selected");
				}

				/* wenn der Monat außerhalb der definierten möglichen Grenzen liegt, dann deaktvieren */
				if (!date.between(calendar.options.date.min.getFirstDateOfMonth().clearTime(true), calendar.options.date.max.getLastDateOfMonth().clearTime(true)))
				{
					element.addClass("jcCalendar-entry-disabled");
				}
			});

			/* wenn wertänderung dann EFFECT ausführen */
			calendarEffectSlideRun(calendar, jControls.Calendar.SELECTABLE.MONTH, calendar.monthView, effectElementClone, anim);
		}
		/* da sich nur der entsprechende Tag/Monat geändert hat, welcher markiert wurde, dann brauchen wir auch diesen nur setzen */
		else if (oldValue != null && calendar.value && !oldValue.isEqual(calendar.value))
		{
			if (calendar.monthView.down(".jcCalendar-entry-selected"))
			{
				calendar.monthView.down(".jcCalendar-entry-selected").removeClass("jcCalendar-entry-selected");
			}
			/* nun den entsprechend Date Index finden */
			if (calendar.value.getFullYear() == calendar.displayValue.getFullYear())
			{
				/* dann das element finden */
				calendar.monthView.down(".jcCalendar-entry-index-" + calendar.value.getMonth()).addClass("jcCalendar-entry-selected");
			}
		}
	};

	/**
	 * Jahresansicht aktualisieren
	 *
	 * @param jControls.Calendar calendar
	 * @param date oldValue
	 * @param bool|int anim
	 * @access private
	 */
	var calendarUpdateViewYears = function(calendar, oldValue, anim)
	{
		var yearIndex = Math.floor(calendar.displayValue.getFullYear() / 10) * 10;

		/* anicht TAG */
		/* nur so komplett zeichnen, wenn oldValue nicht gibt oder sich der Monat/Jahr geändert hat */
		if (oldValue == null || yearIndex != Math.floor(oldValue.getFullYear() / 10) * 10)
		{
			/* Jahre in der Ansicht Jahr */
			calendar.yearView.down(".jcCalendar-select-current").applyTo(yearIndex + " - " + (yearIndex + 9));

			/* effect vorbereitung */
			var effectElementClone = calendarEffectSlidePrepare(calendar, jControls.Calendar.SELECTABLE.YEAR, calendar.yearView, anim);

			/* wenn vorherige Jahre der letzte Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.yearView.down(".jcCalendar-select-prev")[
				calendar.options.date.min.getFullYear() <= yearIndex - 1
				? "show"
				: "hide"
			]().quickTip((yearIndex - 10) + " - " + (yearIndex - 1)); /* prev tooltip anpassen */

			/* wenn nächstes Jahr der erste Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.yearView.down(".jcCalendar-select-next")[
				yearIndex + 10 <= calendar.options.date.max.getFullYear()
				? "show"
				: "hide"
			]().quickTip((yearIndex + 10) + " - " + (yearIndex + 19)); /* next tooltip anpassen */

			/* Datumswerte aktualisieren in der ansicht TAG */
			/* sonderklassen entfernen */
			calendar.yearView.select(".jcCalendar-entry").invoke("removeClass", "jcCalendar-entry-disabled jcCalendar-entry-selected jcCalendar-entry-now");
			/* nun alle anzeige jahre durchlaufen */
			$R(0, 11).each(function(index)
			{
				/* Tag-Datum ins Element setzen */
				var year = yearIndex - 1 + index;
				var element = calendar.yearView.down(".jcCalendar-entry-index-" + index);
				element.down(".jcCalendar-entry-text").applyTo(year);

				/* ist der Jahr identisch mit heute, dann entsprechende Class setzen */
				if (year == (new Date()).getFullYear())
				{
					element.addClass("jcCalendar-entry-now");
				}

				/* ist das Jahr identisch mit den aktuellen wert, dann entsprechende Class setzen */
				if (calendar.value && year == calendar.value.getFullYear())
				{
					element.addClass("jcCalendar-entry-selected");
				}

				/* wenn das Jahr außerhalb der definierten möglichen Grenzen liegt, dann deaktvieren */
				if (year < calendar.options.date.min.getFullYear() || calendar.options.date.max.getFullYear() < year)
				{
					element.addClass("jcCalendar-entry-disabled");
				}
			});

			/* wenn wertänderung dann EFFECT ausführen */
			calendarEffectSlideRun(calendar, jControls.Calendar.SELECTABLE.YEAR, calendar.yearView, effectElementClone, anim);
		}
		/* da sich nur der entsprechende Jahr geändert hat, welcher markiert wurde, dann brauchen wir auch diesen nur setzen */
		else if (oldValue != null && calendar.value && oldValue.getFullYear() != calendar.value.getFullYear())
		{
			if (calendar.yearView.down(".jcCalendar-entry-selected"))
			{
				calendar.yearView.down(".jcCalendar-entry-selected").removeClass("jcCalendar-entry-selected");
			}
			/* nun den entsprechend Date Index finden */
			if (0 <= calendar.value.getFullYear() - (yearIndex - 1) && calendar.value.getFullYear() - (yearIndex - 1) <= 11)
			{
				/* dann das element finden */
				calendar.yearView.down(".jcCalendar-entry-index-" + (calendar.value.getFullYear() - (yearIndex - 1))).addClass("jcCalendar-entry-selected");
			}
		}
	};

	/**
	 * Dekadenansicht aktualisieren
	 *
	 * @param jControls.Calendar calendar
	 * @param date oldValue
	 * @param bool|int anim
	 * @access private
	 */
	var calendarUpdateViewDecades = function(calendar, oldValue, anim)
	{
		var yearIndex = Math.floor(calendar.displayValue.getFullYear() / 100) * 100;

		/* anicht TAG */
		/* nur so komplett zeichnen, wenn oldValue nicht gibt oder sich der Monat/Jahr geändert hat */
		if (oldValue == null || yearIndex != Math.floor(oldValue.getFullYear() / 100) * 100)
		{
			/* Jahre in der Ansicht Jahr */
			calendar.decadeView.down(".jcCalendar-select-current").applyTo(yearIndex + " - " + (yearIndex + 99));

			/* effect vorbereitung */
			var effectElementClone = calendarEffectSlidePrepare(calendar, jControls.Calendar.SELECTABLE.DECADE, calendar.decadeView, anim);

			/* wenn vorherige Jahre der letzte Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.decadeView.down(".jcCalendar-select-prev")[
				calendar.options.date.min.getFullYear() <= yearIndex - 1
				? "show"
				: "hide"
			]().quickTip((yearIndex - 100) + " - " + (yearIndex - 1)); /* prev tooltip anpassen */

			/* wenn nächstes Jahr der erste Tag nicht im definerten bereich ist, dann ausblenden */
			calendar.decadeView.down(".jcCalendar-select-next")[
				yearIndex + 100 <= calendar.options.date.max.getFullYear()
				? "show"
				: "hide"
			]().quickTip((yearIndex + 100) + " - " + (yearIndex + 199)); /* next tooltip anpassen */

			/* Datumswerte aktualisieren in der ansicht TAG */
			/* sonderklassen entfernen */
			calendar.decadeView.select(".jcCalendar-entry").invoke("removeClass", "jcCalendar-entry-disabled jcCalendar-entry-selected jcCalendar-entry-now");
			/* nun alle anzeige jahre durchlaufen */
			$R(0, 11).each(function(index)
			{
				/* Tag-Datum ins Element setzen */
				var year = yearIndex - 10 + index * 10;
				var element = calendar.decadeView.down(".jcCalendar-entry-index-" + index);
				element.down(".jcCalendar-entry-text").applyTo(year + " - " + (year + 9));

				/* ist der Jahr identisch mit heute, dann entsprechende Class setzen */
				if (year <= (new Date()).getFullYear() && (new Date()).getFullYear() <= year + 10)
				{
					element.addClass("jcCalendar-entry-now");
				}

				/* ist das Jahr identisch mit den aktuellen wert, dann entsprechende Class setzen */
				if (calendar.value && year <= calendar.value.getFullYear() && calendar.value.getFullYear() <= year + 10)
				{
					element.addClass("jcCalendar-entry-selected");
				}

				/* wenn das Jahr außerhalb der definierten möglichen Grenzen liegt, dann deaktvieren */
				if (year + 10 < calendar.options.date.min.getFullYear() || calendar.options.date.max.getFullYear() < year)
				{
					element.addClass("jcCalendar-entry-disabled");
				}
			});

			/* wenn wertänderung dann EFFECT ausführen */
			calendarEffectSlideRun(calendar, jControls.Calendar.SELECTABLE.DECADE, calendar.decadeView, effectElementClone, anim);
		}
		/* da sich nur der entsprechende Jahr geändert hat, welcher markiert wurde, dann brauchen wir auch diesen nur setzen */
		else if (oldValue != null && calendar.value && oldValue.getFullYear() != calendar.value.getFullYear())
		{
			if (calendar.decadeView.down(".jcCalendar-entry-selected"))
			{
				calendar.decadeView.down(".jcCalendar-entry-selected").removeClass("jcCalendar-entry-selected");
			}
			/* nun den entsprechend Date Index finden */
			if (0 <= calendar.value.getFullYear() - (yearIndex - 1) && calendar.value.getFullYear() - (yearIndex - 1) <= 11)
			{
				/* dann das element finden */
				calendar.decadeView.down(".jcCalendar-entry-index-" + (calendar.value.getFullYear() - (yearIndex - 1))).addClass("jcCalendar-entry-selected");
			}
		}
	};

	/**
	 * erzeugt ein Calendar
	 *
	 *	options
	 *		name							type		default								description
	 *		=====================================================================================================================
	 *		allowEmpty			optional	boolean		false								Datumsauswahl leeren erlauben
	 *		date				optional	object											definiert den Datumsbereich von/bis
	 *		emptyText			optional	string
	 *		format				optional	string|bool	false								wenn false, dann als Date Object, ansonsten ein gültiges Datumsformat
	 *		time				optional	object|bool	false								Datumseingabe?
	 *		selectable			optional	string		jControls.Calendar.SELECTABLE.DAY	definiert bis zu welcher ebene ausgewählt werden darf (von Zeit bis Jahr) (jControls.Calendar.SELECTABLE...)
	 *
	 *	date-Object
	 *		name				type			default					description
	 *		=====================================================================================================================
	 *		min					date|string		01.01.1000 00:00		kleinste erlaubte datumswert
	 *		max					date|string		31.12.9999 23:59		größte erlaubte datumswert
	 *
	 *	time-Object
	 *		name				type			default					description
	 *		=====================================================================================================================
	 *		intervalMinute		int				1
	 *		meridiem			bool			false					AM/PM oder 24h
	 *		show				bool			false					anzeigen?
	 *
	 *	Listeners
	 *		deklaration											result type			description
	 *		=====================================================================================================================
	 *		onAfterDayClick(calendar, date)											nach dem anklicken eines Tages in der Tag-Ansicht
	 *		onAfterDecadeNextClick(calendar, date)									nach dem anklicken für "nächste Dekade" in der Dekadeansicht
	 *		onAfterDecadePrevClick(calendar, date)									nach dem anklicken für "vorheriger Dekade" in der Dekadeansicht
	 *		onAfterMonthClick(calendar, date)										nach dem anklicken eines Monats in der Monat-Ansicht
	 *		onAfterYearClick(calendar, date)										nach dem anklicken eines Jahres in der Jahres-Ansicht
	 *		onAfterMonthNextClick(calendar, date)				bool				nach dem anklicken für "nächster Monat" in der Tagesansicht
	 *		onAfterMonthPrevClick(calendar, date)									nach dem anklicken für "vorheriger Monat" in der Tagesansicht
	 *		onAfterYearNextClick(calendar, date)				bool				nach dem anklicken für "nächstes Jahr" in der Monatsansicht
	 *		onAfterYearPrevClick(calendar, date)									nach dem anklicken für "vorheriger Jahr" in der Monatsansicht
	 *		onAfterYearsNextClick(calendar, date)				bool				nach dem anklicken für "nächste Jahre" in der Jahresansicht
	 *		onAfterYearsPrevClick(calendar, date)									nach dem anklicken für "vorherige Jahre" in der Jahresansicht
	 *		onBeforeDayClick(calendar, date)					bool				vor dem anklick eines Tages
	 *		onBeforeDecadeNextClick(calendar, date)				bool				vor dem anklicken für "nächste Dekade" in der Dekadeansicht
	 *		onBeforeDecadePrevClick(calendar, date)				bool				vor dem anklicken für "vorheriger Dekade" in der Dekadeansicht
	 *		onBeforeMonthClick(calendar, date)					bool				vor dem anklick eines Monats
	 *		onBeforeYearClick(calendar, date)					bool				vor dem anklick eines Jahres
	 *		onBeforeMonthNextClick(calendar, date)				bool				vor dem anklicken für "nächster Monat" in der Tagesansicht
	 *		onBeforeMonthPrevClick(calendar, date)				bool				vor dem anklicken für "vorheriger Monat" in der Tagesansicht
	 *		onBeforeYearNextClick(calendar, date)				bool				vor dem anklicken für "nächstes Jahr" in der Monatsansicht
	 *		onBeforeYearPrevClick(calendar, date)				bool				vor dem anklicken für "vorheriges Jahr" in der Monatsansicht
	 *		onBeforeYearsNextClick(calendar, date)				bool				vor dem anklicken für "nächste Jahre" in der Jahresansicht
	 *		onBeforeYearsPrevClick(calendar, date)				bool				vor dem anklicken für "vorherige Jahre" in der Jahresansicht
	 */
	jControls.Calendar = Class.create(jControls.Control,
	{
		/**
		 * Anzeige wert
		 */
		displayValue:	null,

		/**
		 * Anzeige view
		 */
		displayView:	null,

		/**
		 * liefert nur den wert für select box
		 *
		 * @access public
		 * @return date
		 */
		getRow:	function()
		{
			return this.value;
		},

		/**
		 * liefert den Wert
		 *
		 * @param bool|string format
		 * @access public
		 * @return string
		 */
		getValue: function(format)
		{
			if (!this.value)
			{
				return null;
			}
			if (typeof format == "undefined")
			{
				format = this.options.format;
			}

			if (format === false)
			{
				return this.value.clone();
			}

			return this.value.format(format);
		},

		/**
		 * Konstruktor
		 *
		 * @param object options
		 * @access public
		 */
		initialize: function($super, options)
		{
			if (!this.xType)
			{
				this.xType = jControls.Calendar.xType;
			}

			/* merken */
			this.options			= options;
			this.options.value		= (typeof this.options.value		!= "undefined" ? this.options.value			: null);
			this.options.date		= (typeof this.options.date			!= "undefined" ? this.options.date			: {});
			this.options.date.min	= (typeof this.options.date.min		!= "undefined" ? this.options.date.min		: "01.01.1000 00:00:00");
			this.options.date.max	= (typeof this.options.date.max		!= "undefined" ? this.options.date.max		: "31.12.9999 23:59:59");
			this.options.selectable	= (typeof this.options.selectable	!= "undefined" ? this.options.selectable	: jControls.Calendar.SELECTABLE.DAY);
			this.options.time		= (typeof this.options.time			!= "undefined" ? this.options.time			: false);
			this.options.allowEmpty	= (typeof this.options.allowEmpty	!= "undefined" ? this.options.allowEmpty	: false);
			this.options.emptyText	= (typeof this.options.emptyText	!= "undefined" ? this.options.emptyText		: "");

			/* min date konvertieren */
			if (typeof this.options.date.min == "string")
			{
				this.options.date.min = Date.parseDate(this.options.date.min);
			}
			/* max date konvertieren */
			if (typeof this.options.date.max == "string")
			{
				this.options.date.max = Date.parseDate(this.options.date.max);
			}
			/* value konvertieren */
			if (typeof this.options.value == "string" && this.options.value == "")
			{
				this.options.value = null;
			}
			if (typeof this.options.value == "string")
			{
				this.options.value = Date.parseDate(this.options.value);
			}

			/* wenn wert dann diesen auf min/max prüfen und wenn nicht dann auf min setzen */
			if (this.options.value && !this.options.value.clearTime(true).between(this.options.date.min.clearTime(true), this.options.date.max.clearTime(true)))
			{
				this.options.value = this.options.date.min.clone();
			}
			/* wenn weiter unten als Jahr dann ist default auswhal Jahr */
			if (this.options.selectable < jControls.Calendar.SELECTABLE.YEAR)
			{
				this.options.selectable = jControls.Calendar.SELECTABLE.YEAR;
			}

			/* Zeit anzeigen */
			if (typeof this.options.time == "boolean")
			{
				this.options.time = {
					show:	this.options.time
				};
			}
			/* zeit anzeige optionen */
			this.options.time.meridiem			= (typeof this.options.time.meridiem		!= "undefined" ? this.options.time.meridiem			: false);
			this.options.time.intervalMinute	= (typeof this.options.time.intervalMinute	!= "undefined" ? this.options.time.intervalMinute	: 1);
			/* zeitanzeigen und value? dann value Minuten ggf. korrigieren */
			if (this.options.time.show && this.options.value)
			{
				this.options.value.setMinutes(Math.floor(this.options.value.getMinutes() / this.options.time.intervalMinute) * this.options.time.intervalMinute);
			}

			/* ein wert da? dann anzeigewert setzen */
			if (this.options.value)
			{
				this.displayValue = this.options.value.clone();
			}
			/* kein wert aber aktuelles datum in min/max */
			else if ((new Date()).between(this.options.date.min, this.options.date.max))
			{
				this.displayValue = new Date();
			}
			/* dann anzeigedatum auf min setzen */
			else
			{
				this.displayValue = this.options.date.min.clone();
			}
			/* zeitanzeige? und wert korrigieren bei anzeigewert */
			if (this.options.time.show)
			{
				this.displayValue.setMinutes(Math.floor(this.displayValue.getMinutes() / this.options.time.intervalMinute) * this.options.time.intervalMinute);
			}

			/* formate definieren */
			if (typeof this.options.format == "undefined")
			{
				switch (this.options.selectable)
				{
					case jControls.Calendar.SELECTABLE.DAY:
						this.options.format = false;
						break;

					case jControls.Calendar.SELECTABLE.MONTH:
						this.options.format = "m.Y";
						break;

					case jControls.Calendar.SELECTABLE.YEAR:
					case jControls.Calendar.SELECTABLE.DECADE:
						this.options.format = "Y";
						break;
				}
			}

			/* anzeigeansicht */
			this.displayView = this.options.selectable;

			/* empty value erlauben */
			if (this.options.allowEmpty)
			{
				this.options.buttons = [
				{
					icon:			"jcBtn-delete",
					listeners:
		 			{
		 				onAfterClick: (function(btn)
		 				{
		 					this.setValue(null);
		 				}).bind(this),
		 				onAfterRender: (function(btn)
		 				{
		 					Element.insert(btn.dom,
		 					{
		 						after: "<div class='jcCalendar-value'>" + (this.value ? this.getValue() : this.options.emptyText) + "</div>"
		 					});
		 				}).bind(this)
		 			}
				}];
			}

			/* parent */
			$super(options);
		},

		/**
		 * rendert
		 *
		 * @param node renderTo
		 * @access public
		 * @return this
		 */
		render: function($super, renderTo)
		{
			if (this.rendered)
			{
				return this;
			}

			renderTo = $(renderTo);
			renderTo = renderTo && renderTo.nodeType == 1 ? renderTo : renderTo.dom;

			if (this.xType == jControls.Calendar.xType && this.listeners.onBeforeRender && this.listeners.onBeforeRender(this) === false)
			{
				return this;
			}

			/* Container */
			$super(renderTo);

			/* input */
			if (this.name && this.name != "")
			{
				Element.insert(this.dom,
				{
					bottom: "<input type='hidden' name='" + this.name + "' value='' />"
				});
			}

			Element.insert(this.dom,
			{
				bottom:	"<div class='jcCalendar-effect-parent'><div class='jcCalender-effect-overlay' style='display:none;'></div></div>"
			});

			/* Ansichten rendern und merken */
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DAY)
			{
				this.dayView = calendarRenderViewDays(this);
				if (this.options.time.show)
				{
					calendarRenderViewTime(this);
				}
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.MONTH)
			{
				this.monthView = calendarRenderViewMonths(this);
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.YEAR)
			{
				this.yearView = calendarRenderViewYears(this);
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DECADE)
			{
				this.decadeView = calendarRenderViewDecade(this);
			}

			/* Mit Tagesauswahl */
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DAY)
			{
				this.dayView.show();
			}
			/* Mit Monatsauswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.MONTH)
			{
				this.monthView.show();
			}
			/* Mit Jahres auswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.YEAR)
			{
				this.yearView.show();
			}
			/* Mit Decade auswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.DECADE)
			{
				this.decadeView.show();
			}

			/* input Container setzen */
			if (this.name && this.name != "" && !this.options.multiselect)
			{
				this.inputContainer = this.dom.down("input", 0);
			}

			/* empty value dann nun rendern */
			if (this.options.value == null)
			{
				this.renderValue(null, this.displayValue, false);
			}

			if (this.xType == jControls.Calendar.xType && this.listeners.onAfterRender)
			{
				this.listeners.onBeforeRender(this);
			}

			return this;
		},

		/**
		 * rendert die Row für selectbox
		 *
		 * @param date date
		 * @param element element
		 * @access public
		 * @return jControls.Calendar
		 */
		renderRow: function(date, element)
		{
			element.applyTo(
				"<div class='jcCalendar-select-value'>" +
					date.format(this.options.format === false ? "d.m.Y H:i" : this.options.format) +
				"</div>"
			);

			return this;
		},

		/**
		 * Value render
		 *
		 * @param date oldValue
		 * @param date newValue
		 * @param bool anim
		 * @access public
		 * @return jControls.Calendar
		 */
		renderValue: function($super, oldValue, newValue, anim)
		{
			if (typeof anim == "undefined" && newValue)
			{
				anim = true;
			}

			$super(oldValue, newValue);

			var oldDisplayValue = this.displayValue.clone();

			if (newValue)
			{
				this.displayValue = newValue.clone();
			}
			else
			{
				this.dom.select(".jcCalendar-entry-selected").invoke("removeClass", "jcCalendar-entry-selected");
			}

			/* anischt aktualisieren */
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DAY)
			{
				calendarUpdateViewDays(this, oldValue, anim ? oldDisplayValue.getMonth() - newValue.getMonth() + 12 * (oldDisplayValue.getFullYear() - newValue.getFullYear()) : false);
				if (this.options.time.show)
				{
					if (newValue)
					{
						this.timeHour.setValue(newValue.format("H"));
						this.timeMinute.setValue(newValue.format("i"));
					}
					else
					{
						this.timeHour.setValue(null);
						this.timeMinute.setValue(null);
					}
				}
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.MONTH)
			{
				calendarUpdateViewMonths(this, oldValue, anim ? oldDisplayValue.getFullYear() - newValue.getFullYear() : false);
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.YEAR)
			{
				calendarUpdateViewYears(this, oldValue, anim ? Math.floor(oldDisplayValue.getFullYear() / 10) - Math.floor(newValue.getFullYear() / 10) : false);
			}
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DECADE)
			{
				calendarUpdateViewDecades(this, oldValue, anim ? Math.floor(oldDisplayValue.getFullYear() / 100) - Math.floor(newValue.getFullYear() / 100) : false);
			}

			return this;
		},

		/**
		 * Ansicht zurücksetzen
		 *
		 * @access public
		 * @return jControls.Calendar
		 */
		reset: function()
		{
			if (this.dayView)
			{
				this.dayView.hide();
			}
			if (this.monthView)
			{
				this.monthView.hide();
			}
			if (this.yearView)
			{
				this.yearView.hide();
			}
			if (this.decadeView)
			{
				this.decadeView.hide();
			}

			/* Mit Tagesauswahl */
			if (this.options.selectable >= jControls.Calendar.SELECTABLE.DAY)
			{
				this.dayView.show();
				this.displayView = jControls.Calendar.SELECTABLE.DAY;
			}
			/* Mit Monatsauswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.MONTH)
			{
				this.monthView.show();
				this.displayView = jControls.Calendar.SELECTABLE.MONTH;
			}
			/* Mit Jahres auswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.YEAR)
			{
				this.yearView.show();
				this.displayView = jControls.Calendar.SELECTABLE.YEAR;
			}
			/* Mit Decade auswahl */
			else if (this.options.selectable >= jControls.Calendar.SELECTABLE.DECADE)
			{
				this.decadeView.show();
				this.displayView = jControls.Calendar.SELECTABLE.DECADE;
			}

			this.renderValue(this.displayValue, this.value, false);

			return this;
		},

		/**
		 * scrollt für selectbox
		 *
		 * @access public
		 * @return jControls.Calendar
		 */
		scrollTo: function()
		{
			return this;
		},

		/**
		 * setzt den wert
		 *
		 * @param date value
		 * @access public
		 * @return jControls.Calendar
		 */
		setValue: function($super, value)
		{
			if (typeof value == "string" && value == "")
			{
				value = null;
			}
			/* value prüfung */
			if (typeof value == "string")
			{
				value = Date.parseDate(value);
			}

			/* wenn wert dann diesen auf min/max prüfen und wenn nicht dann auf min setzen */
			if (value && !value.clearTime(true).between(this.options.date.min.clearTime(true), this.options.date.max.clearTime(true)))
			{
				value = this.options.date.min.clone();
			}
			if (value)
			{
				value.setMinutes(Math.floor(value.getMinutes() / this.options.time.intervalMinute) * this.options.time.intervalMinute);
			}

			$super(value);

			if (value && this.name && this.name != "")
			{
				if (this.options.format === false)
				{
					this.inputContainer.value = this.value.format("Y-m-d H:i:s");
				}
				else
				{
					this.inputContainer.value = this.value.format(this.options.format);
				}
			}

			if (this.options.allowEmpty && this.dom.down(".jcCalendar-value"))
			{
				this.dom.down(".jcCalendar-value").applyTo(this.value ? this.getValue() : this.options.emptyText);
			}

			return this;
		}
	});
	/**
	 * xType
	 */
	jControls.Calendar.xType = xTypes.Calendar;
	/**
	 * SELECTABLE
	 */
	jControls.Calendar.SELECTABLE = {
		DAY:	3,
		MONTH:	2,
		YEAR:	1,
		DECADE:	0
	};
})();