var Environment =
{
	wwwUrl:			"http://www.fortbildung-bw.de",
	kurseUrl:		"http://kurse.fortbildung-bw.de",
	cookiedomain:	".fortbildung-bw.de",
	xiSwfUrl:		"http://kurse.fortbildung-bw.de/libs/swfobject/swf/expressInstall.swf"
};
document.domain="fortbildung-bw.de";



/**
 * Hauptgrund für die Existenz dieser Klasse ist, damit nicht mehr als nötig 
 * die $.httpData()-Funktion aufgerufen wird, denn die globalen Eventhandler
 * müssen sich ihr "data"-Objekt selbst erzeugen.
 * 
 * Ein weiterer Vorteil ist, dass auch gefakte Ajax-Request-Antworten behandelt
 * werden können, z.B. wenn der Server die JSON-Antwort an ein Flash schickt.
 */

var AjaxHook = (function()
{
	var callbacks = [];
	
	$(document).bind('ajaxSuccess', ajaxSuccessHandler);
	
	return {
		registerCallback: registerCallback,
		inject: inject
	};
	
	// Fügt eine Callback-Funktion hinzu, die bei eintreffenden Ajax-Antworten
	// benachrichtigt werden soll.
	function registerCallback(callback)
	{
		callbacks.push(callback);
	}
	
	function ajaxSuccessHandler(event, xmlHttpRequest, ajaxOptions)
	{
		var data = null;
		try
		{
			data = $.httpData(xmlHttpRequest, ajaxOptions.dataType, ajaxOptions);
		}
		catch (error)
		{
		}
		if (data == null) return;
		
		callCallbacks(data,ajaxOptions);
	}
	
	//function callCallbacks(json)
	function callCallbacks(json,ajaxOptions)
	{
		for (var i = 0, len = callbacks.length; i < len; ++i)
		{
			callbacks[i].call(ajaxOptions , json, "success");
		}
	}
	
	// Ruft alle registrierten Callbacks zur Behandlung der übergebenen Daten
	// auf.
	function inject(data)
	{
		callCallbacks(data);
	}
	
})();



/**
 * 
 * Das AjaxRequestMixin fügt einer Klasse Methoden zum Arbeiten mit Ajax hinzu.
 * Es können unterschiedliche Requestgruppen in Auftrag gegeben werden, indem
 * die Requests mit Namen versehen werden. Falls unter einem Namen ein Request
 * bereits läuft, wird der neue Request vorgemerkt und ersetzt einen evtl schon
 * vorgemerkten Request.
 * 
 * Normalerweise sollten die folgenden Methoden in der Komponente überschrieben
 * werden. Wenn eine Methode überschrieben wurde, muss denoch die überschriebene
 * Methode von AjaxRequestMixin aufgerufen werden:
 * - _ajaxHandler(data, request)
 * - _sendRequest(request, name)
 * 
 * Außerdem muss in der _init()-Methode der Komponente die
 * _initAjaxRequestMixin()-Methode aufgerufen werden. In der destroy()-Methode
 * der Komponente muss _destroyAjaxRequestMixin() aufgerufen werden.
 * 
 */


var AjaxRequestMixin = (function()
{
	var defaultRequestName = 'default';
	
	return {
		// Initialisert das AjaxRequestMixin; muss in der _init()-Methode der
		// Komponente aufgerufen werden
		_initAjaxRequestMixin: function()
		{
			this._ajaxDestroyed = false;
			this._ajaxRunningRequest = {};
			this._ajaxQueuedRequest = {};
			
			var self = this;
			this._ajaxSuccessDelegate = function(data, textStatus)
			{
				self._ajaxResponse(data, this);
			};
			this._ajaxErrorDelegate = function(xmlHttpRequest, textStatus, errorThrown)
			{
				self._ajaxResponse(null, this);
			};
		},
		// Zerstört das AjaxRequestMixin; muss in der destroy()-Methode der
		// Komponente aufgerufen werden
		_destroyAjaxRequestMixin: function()
		{
			this._ajaxDestroyed = true;
			this._runningRequest = null;
			this._queuedRequest = null;
			this._ajaxSuccessDelegate = null;
			this._ajaxErrorDelegate = null;
		},
		// Prüft, ob momentan ein Ajaxrequest läuft
		_ajaxIsLoading: function(name)
		{
			if (name == undefined || name == null)
			{
				name = defaultRequestName;
			}
			var runningRequest = this._runningRequest[name];
			return runningRequest != undefined && runningRequest != null;
		},
		// Ruft die _ajaxHandler()-Methode auf, wenn die Komponente noch aktiv
		// ist; sollte nicht überschrieben werden
		_ajaxResponse: function(data, request)
		{
			if (!this._ajaxDestroyed)
			{
				this._ajaxHandler(data, request);
			}
		},
		// Reagiert auf eine Ajax-Anfrage; wenn überschrieben, muss diese
		// Methode von der überschreibenden Komponenten-Methode aufgerufen
		// werden
		_ajaxHandler: function(data, request)
		{
			var name = request.name;
			this._ajaxRunningRequest[name] = null;
			var queuedRequest = this._ajaxQueuedRequest[name];
			if (queuedRequest != undefined && queuedRequest != null)
			{
				this._ajaxQueuedRequest[name] = null;
				this._sendRequest(queuedRequest, name);
			}
		},
		// Nimmt ein neues Request-Objekt auf; es handelt sich dabei um das
		// Options-Objekt der $.ajax()-Funktion
		_loadAjax: function(request, name)
		{
			if (name == undefined || name == null)
			{
				name = defaultRequestName;
			}
			var runningRequest = this._runningRequest[name];
			if (runningRequest == undefined || runningRequest == null)
			{
				this._sendRequest(request, name);
			}
			else
			{
				this._queuedRequest[name] = request;
			}
		},
		// Sendet einen Request ab; wenn überschrieben, muss diese Methode von
		// der überschreibenden Komponenten-Methode aufgerufen werden
		_sendRequest: function(request, name)
		{
			request.name = name;
			request.success = this._ajaxSuccessDelegate;
			request.error = this._ajaxErrorDelegate;
			this._runningRequest[name] = request;
			$.ajax(request);
		}
		
	};
})();


// Hilfetexte schnell und simpel abrufbar machen

function rebindHelptexts(){
	$(".aHelptext").each(function() {
		$(this).bind("click", function(event) {
	       event.preventDefault();
				$.getJSON('/HelpTexts/getHelpText?text=' + $(this).attr('href'),function(data){
				var params = {
					title : (data.success ? data.feedback : "Fehler"),
					display : data.feedback
				};
				confirmDialog(params);
			});
		});
	});
}



$(function(){
	rebindHelptexts();
});


/**
 * Cookie class
 * @author brendel
 * example:
 * Cookie.set('hello', 'world!', 60 * 60 * 24); // 1 day
 * if(Cookie.get('hello') != null) alert('Hello ' + Cookie.get('hello'));
 */

function Cookie()
{
}

Cookie.set = function(name, value, numSeconds)
{
	var cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value);
	if (numSeconds != undefined && numSeconds != 0)
	{
		var expire = new Date();
		expire.setTime(expire.getTime() + numSeconds * 1000);
		cookie += ';expires=' + expire.toGMTString();
	}
	cookie += ';domain=.' +Environment.cookiedomain
	cookie += ';path=/';
	document.cookie = cookie;
};

Cookie.get = function(name)
{
	var cookies = document.cookie.split(';');
	for (var i = 0, l = cookies.length; i < l; ++i)
	{
		var cookie = cookies[i];
		var p = cookie.indexOf('=');
		if (p == -1)
		{
			continue;
		}
		var cookieName = StringUtils.trim(decodeURIComponent(cookie.substring(0, p)));
		if (cookieName == name)
		{
			var cookieValue = StringUtils.trim(decodeURIComponent(cookie.substring(p + 1)));
			return cookieValue;
		}
	}
	return null;
};

Cookie.remove = function(name)
{
	if (Cookie.get(name) === null)
	{
		return;
	}
	document.cookie = encodeURIComponent(name) + '=;expires=Thu, 01-Jan-1970 00:00:01 GMT;path=/';
};



var Debugger = (function()
{
	
	var $frameTemplate = $(
			'<div class="debugger">'
		+		'<div class="message">'
		+		'</div>'
		+		'<div class="items">'
		+		'</div>'
		+		'<a href="#" class="close">Schließen</a>'
		+	'</div>'
		);
	
	var $itemTemplate = $(
			'<div class="item">'
		+		'<div class="info">'
		+			'<span class="call"></span>'
		+			'<span class="line-and-file">'
		+				' in line '
		+				'<span class="line"></span>'
		+				' of file '
		+				'<span class="file"></span>'
		+			'</span>'
		+		'</div>'
		+		'<div class="code"></div>'
		+	'</div>'
		);
	
	var $debugger = null;
	
	// Der Debugger hängt sich über das globale ajaxSuccess-Event an die
	// Anfragen.
	AjaxHook.registerCallback(successHandler);
	
	return {};
	
	function successHandler(data)
	{
		var xmlString = data.__debuggerXml;
		if (xmlString == undefined) return;
		
		var $xml = $.xmlDOM(xmlString);
		displayMessage($xml);
	}
	
	function destroy()
	{
		if ($debugger != null)
		{
			$debugger
				.find('.item').unbind('mouseenter mouseleave').end()
				.find('.close').unbind('click').end()
				.remove();
			$debugger = null;
		}		
	}
	
	function displayMessage($xml)
	{
		destroy();
		
		var messageHtml = '<span class="caption">Exception:</span> ' + StringUtils.htmlify($xml.find('message').text());
		var $infoSqlXml = $xml.find('info sql');
		if ($infoSqlXml.length)
		{
			messageHtml += '<br/>\n<span class="caption">Query:</span> ';
			messageHtml += StringUtils.htmlify($infoSqlXml.text());
		}
		
		var $frame = $frameTemplate.clone()
			.find('.message').html(messageHtml).end();
		var $items = $frame.find('.items');
		$xml.find('item').each(function()
		{
			var $this = $(this);
			var call = $this.find('class').text() + $this.find('type').text() + $this.find('function').text();
			var file = $this.find('file').text() || 'N/A';
			var line = $this.find('line').text() || 'N/A';
			var startLine = $this.find('startLine').text();
			var numLines = $this.find('numLines').text();
			var source = $this.find('source').text();
			var code;
			if (numLines != 0)
			{
				var skipNumLines = line - startLine;
				var regExp = new RegExp('((?:.*(?:\\r\\n|\\r|\\n)){' + skipNumLines + '}[\\x09\\x32]*)(.*)([\\S\\s]+)');
				var match = source.match(regExp);
				code = StringUtils.htmlify(match[1])
					+ '<span class="highlight">'
					+ StringUtils.htmlify(match[2])
					+ '</span>'
					+ StringUtils.htmlify(match[3]);
			}
			else
			{
				code = StringUtils.htmlify(source);
			}
			
			$itemTemplate.clone()
				.find('.line').text(line).end()
				.find('.file').text(file).end()
				.find('.call').text(call).end()
				.find('.code').html(code).hide().end()
				.appendTo($items);
		});
		$debugger = $frame
			.appendTo('body')
			.find('.item').hover(itemOverHandler, itemOutHandler).end()
			.find('.close').bind('click', closeClickHandler).end();
	}
	
	function closeClickHandler(event)
	{
		destroy();
	}
	
	function itemOverHandler(event)
	{
		$(this).children('.code').show();
	}
		
	function itemOutHandler(event)
	{
		$(this).children('.code').hide();
	}
	
})();


(function($){

/**
 * Bis jQuery 1.3.2 noch nicht integriert
 * seit jQuery 1.4.x nicht mehr nötig, da bereits integriert.
 */
$.fn.delay = function(time, callback){
    $.fx.step.delay = $.noop;
    return this.animate({delay:1}, time, callback);
}

$.fn.selectedIndex = function(value)
{
	return this.each
	(
		function()
		{
			this.selectedIndex = value;
		}
	);
};

$.fn.extractClassInt = function(name)
{
	name = name==null?'int':name;
	var el = this[0];
	var re = new RegExp('(?:^|\\s)' + name + '-(\\w+)(?:\\s|$)');
	var match = el.className.match(re);
	return match ? parseInt(match[1]) : null;
};

$.fn.addClassInt = function(name, value){
	name = name==null?'int':name;
	if(typeof value != 'number' || !value.toString().match(/^[0-9]+$/))
		throw new Error('Der Wert ist kein Integer!');
	this.addClass(name + '-' + value);
	return this;
};

$.fn.extractClassString = function(name) {
	name = name==null?'string':name;
	var el = this[0];
	var re = new RegExp('(?:^|\\s)' + name + '-([A-Za-z0-9\\+/=]+)(?:\\s|$)');
	var match = el.className.match(re);
	return match ? StringUtils.decode64(match[1]) : null;
};

$.fn.addClassString = function(name, value){
	name = name==null?'string':name;
	if(typeof value != 'string')
		throw new Error('Der Wert ist kein String!');
	
	// Um auszuschließen, dass Leerzeichen und andere anderweitig genutzte
	// Zeichen in das Class-Attribut kommen, wird in base64 kodiert.
	this.addClass(name + '-' + StringUtils.encode64(value));
	return this;
};

$.fn.wait = function(time, type) {
	time = time || 1000;
	type = type || "fx";
	return this.queue(type, function() {
		var self = this;
		setTimeout(function() {
			$(self).dequeue();
		}, time);
	});
};

$.fn.comboboxText = function()
{
	var this0, selectedIndex;
	return ((this0 = this[0]) && (selectedIndex = this0.selectedIndex) >= 0)
		? this0.options[selectedIndex].text
		: '';
};

//die ensureSingleElement()-Funktion stellt sicher, dass ein jQuery-Objekt genau ein DOM-Element enthält.
$.fn.ensureSingleElement = function()
{
	if (this.length != 1 || this[0] == null)
	{
		var msg = 'Expected a single dom element in this jQuery object. Selector: ' + this.selector;
		alert(msg);
		throw new Error(msg);
	}
	return this;
};

$.fn.identify = function(prefix) {
	if (prefix==null){prefix = '';}
	var i = 0;
	return this.each(function() {
		if($(this).attr('id')) return;
		do { 
			i++;
			var id = prefix + '_' + i;
		} while($('#' + id).length > 0);            
		$(this).attr('id', id);            
	});
};

$.fn.focusNextInputField = function() {
	return this.each(
		function() {
			var fields = $(this).parents('form:eq(0),body').find('button,input,textarea,select');
			var index = fields.index( this );
			if ( index > -1 && ( index + 1 ) < fields.length ) {
				fields.eq( index + 1 ).focus();
			}
			return false;
		}
	);
};

/**
 * Kleines Tool um Daten durch einen Key vom Server zu erhalten.
 * Intern wird der key mit base64 verschlüsselt. Ob das nötig ist,
 * sei mal dahingestellt ;-)
 */
$.fn.secretContent = function(s){
	var $this = this;
	s = $.extend($.secretContent, s || {});
	$this.bind('click', function(event){
		event.preventDefault();
		$.post(s.url, {key:StringUtils.encode64(s.key)}, function(data){
			var p = $('<div></div>');
			p.text(data);
			$this.hide().after(p);
		}, 'text');
	});
};

/**
 * Die Aufgabe des Dialog-Helpers ist es, eine Schnittstelle zwischen Link und Dialog
 * zu erzeugen. Hierbei wird der Hash einer URL verwendet, um diesen als Sprungmarke
 * zum Dialog zu realisieren.
 * 
 * Über CSS-Klassen ist es möglich Höhe und Breite zu erzwingen.
 * Dies geschieht über width-XX und height-YY, wobei XX und YY für beliebige Zahlen in
 * Pixel stehen.
 * 
 * Das folgende Beispiel ist eine Möglichkeit zur Implementierung.
 * Es wird nur ein Event-Handler auf ein tiefer liegendes Element registriert,
 * das alle Tags mit der CSS-Klasse "dialog" auswertet.
 * 
 * Anwendungsbeispiel (HTML):
 * 
 * <div class="linkliste">
 * 		<a class="dialog" href="#meineSprungmarke">Link</a>
 * </div>
 * 
 * <div id="meineSprungmarke" title="Titel meiner Sprungmarke" style="display:none;">
 * 		<p>Inhalt meiner Sprungmarke</p>
 * </div>
 * 
 * JavaScript:
 * 
 * $('.linkliste').dialogHelper(200,100);
 * 
 * alternativ um auch Event-Handler mit einzubinden:
 * 
 * $('.linkliste').dialogHelper({
 * 		width: 200,
 * 		height: 300,
 * 		open: function(){ ... },
 * 		close: function(){ ... }
 * });
 */

/**
 * TODO Es soll künftig auch möglich sein, das Dialog komplett zu manipulieren
 * @param object|null settings
 */
function dialogHelperComplex(settings){
	var self = this;
	self.data('dialogHelperOptions', $.extend({}, $.dialogHelper, {
		width: settings.width || 300,
		height: settings.height || 200,
		open: settings.open || null,
		close: settings.close || null
	},{}));
	if(jQuery.fn.dialog == undefined)
		throw new Error('Das UI-Modul "dialog" muss vorhanden sein, um den DialogHelper zu nutzen.');
	this.find('a.dialog').each(function(i,n){
		$(n.hash).dialog({
			autoOpen: false,
			width: $(n).extractClassInt('width') || self.data('dialogHelperOptions').width,
			height: $(n).extractClassInt('height') || self.data('dialogHelperOptions').height,
			resizable: true,
			modal: true,
			open: self.data('dialogHelperOptions').open || null,
			close: function(){
				if(self.data('dialogHelperOptions').close != null)
					self.data('dialogHelperOptions').close.call(this);
			}
		});
	});
	this.bind('click', dialogHelperClickHandler);
};

/**
 * @param integer|null width
 * @param integer|null height
 */
function dialogHelperSimple(width, height){
	// Methode überladen
	dialogHelperComplex.apply(this,[{width:width,height:height}]);
	this.bind('click', dialogHelperClickHandler);
};

function dialogHelperClickHandler(event){
	var a = $(event.target).closest('a');
	if(a.hasClass('dialog')){
		event.preventDefault();
		$(a[0].hash).dialog('open');
	}
};

$.fn.DialogHelper = $.fn.dialoghelper = $.fn.dialogHelper = dialogHelperComplex;

$.extend({
	dialogHelper:{width: null, height: null},
	secretContent:{url: null, key: 'secret'}
});

})(jQuery);


/*
 *
 * Copyright (c) 2010 C. F., Wong (<a href="http://cloudgen.w0ng.hk">Cloudgen Examplet Store</a>)
 * Licensed under the MIT License:
 * http://www.opensource.org/licenses/mit-license.php
 *
 */
﻿(function($,len,createRange,duplicate){
	$.fn.caret=function(options,opt2){
		var start,end,t=this[0],browser=$.browser.msie;
		if(typeof options==="object" && typeof options.start==="number" && typeof options.end==="number") {
			start=options.start;
			end=options.end;
		} else if(typeof options==="number" && typeof opt2==="number"){
			start=options;
			end=opt2;
		} else if(typeof options==="string"){
			if((start=t.value.indexOf(options))>-1) end=start+options[len];
			else start=null;
		} else if(Object.prototype.toString.call(options)==="[object RegExp]"){
			var re=options.exec(t.value);
			if(re != null) {
				start=re.index;
				end=start+re[0][len];
			}
		}
		if(typeof start!="undefined"){
			if(browser){
				var selRange = this[0].createTextRange();
				selRange.collapse(true);
				selRange.moveStart('character', start);
				selRange.moveEnd('character', end-start);
				selRange.select();
			} else {
				this[0].selectionStart=start;
				this[0].selectionEnd=end;
			}
			this[0].focus();
			return this
		} else {
			// Modification as suggested by Андрей Юткин
           if(browser){
				var selection=document.selection;
                if (this[0].tagName.toLowerCase() != "textarea") {
                    var val = this.val(),
                    range = selection[createRange]()[duplicate]();
                    range.moveEnd("character", val[len]);
                    var s = (range.text == "" ? val[len]:val.lastIndexOf(range.text));
                    range = selection[createRange]()[duplicate]();
                    range.moveStart("character", -val[len]);
                    var e = range.text[len];
                } else {
                    var range = selection[createRange](),
                    stored_range = range[duplicate]();
                    stored_range.moveToElementText(this[0]);
                    stored_range.setEndPoint('EndToEnd', range);
                    var s = stored_range.text[len] - range.text[len],
                    e = s + range.text[len]
                }
			// End of Modification
            } else {
				var s=t.selectionStart,
					e=t.selectionEnd;
			}
			var te=t.value.substring(s,e);
			return {start:s,end:e,text:te,replace:function(st){
				return t.value.substring(0,s)+st+t.value.substring(e,t.value[len])
			}}
		}
	}
})(jQuery,"length","createRange","duplicate");


// Ajax-Requests vorkonfigurieren
$.ajaxSetup(
	{
		cache: false, // Cache umgehen
		dataType: 'json', // wir möchten Antworten immer als JSON empfangen
		type: 'POST' // weil POST schöner als GET ist
	}
);


// Normalerweise fängt der Debugger den größten Teil von serverseitigen Fehlern.
// Für HTTP-Fehler oder fatale PHP-Fehler können wir leider nichts machen, als
// eine penetrante MessageBox anzuzeigen :P
$(document).bind(
	'ajaxError',
	function (event, xmlHttpRequest, ajaxOptions, thrownError)
	{
		//if (!ajaxOptions.error)
		//{
		//	alert('Es konnten keine Daten im JSON-Format vom Server angefragt werden!\n' + xmlHttpRequest.responseText);
		//}
	}
);

//die ensureSingleElement()-Funktion stellt sicher, dass ein jQuery-Objekt genau ein DOM-Element enthält.
$.fn.ensureSingleElement = function()
{
	if (this.length != 1 || this[0] == null)
	{
		var msg = 'Expected a single dom element in this jQuery object. Selector: ' + this.selector;
		alert(msg);
		throw new Error(msg);
	}
	return this;
};


$.fn.jsElProp = function(name, value)
{
	if (value == undefined)
	{
		return this[0] && this[0][name];
	}
	return this.each(function()
	{
		//alert(''+this+' '+name+' = '+value);
		this[name] = value;
	});
};


// Sicherstellen, dass der $.kru-Namespace existiert, damit wir HIER schon statische Hilfsmethoden
// hinzufügen können. Normalerweise wird dieser Namespace von der UI automatisch mit dem ersten
// $.widget("kru.meincontrol", ...) angelegt.
if ($.kru == undefined)	$.kru = {};

/*
 * Hilfsfunktion, die aus einer "Parametervariable" einen Hash liefert.
 * Eine "Parametervariable" kann ein String oder ein Objekt sein, oder ein Array aus beiden.
 * 
 * Diese Methode wird "global" definiert, damit alle Komponenten davon Gebrauch machen
 * können, um die Datenübergabe an Funktionen einheitlich und flexibel zu machen.
 * 
 *	$.kru.getHashFromMixedParameters(null) == {}
 *	$.kru.getHashFromMixedParameters(undefined) == {}
 *	$.kru.getHashFromMixedParameters('a=1&b=2') == {a:1,b:2}
 *	$.kru.getHashFromMixedParameters({m:123}) == {m:123}
 *	$.kru.getHashFromMixedParameters(['a=1&b=2', {m:123}]) == {a:1,b:2,m:123}
 */

$.kru.getHashFromMixedParameters = function(parameters)
{
	var hash = {};
	if (parameters != undefined && parameters != null) // ein Vergleich hätte genügt, da dummerweise? in JS null == undefined ist
	{
		if (!$.isArray(parameters)) parameters = [parameters];
		for (var i = 0, len = parameters.length; i < len; ++i)
		{
			var object = parameters[i];
			if (typeof object == 'string')
			{
				if (object.length != 0)
				{
					var pairs = object.split('&');
					for (var j = 0; j < pairs.length; ++j)
					{
						var pair = pairs[j].split('=');
						if (pair.length != 2) throw new Error('Could not split query string.');
						hash[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
					}
				}
			}
			else
			{
				for (var name in object)
				{
					hash[name] = String(object[name]);
				}
			}
		}
	}
	return hash;
};




var StringUtils = (function()
{
	var htmlReplaceMap = {
		'&': '&amp;',
		'\"': '&quot;',
		'\'': '&#39;',
		'<': '&lt;',
		'>': '&gt;',
		'\r\n': '<br/>\n',
		'\r': '<br/>\n',
		'\n': '<br/>\n',
		'\t': '<span style="white-space: pre-wrap;">\t</span>'
	};
	
	var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	
	return {
		repeat: repeat,
		trim: trim,
		htmlchars: htmlchars,
		htmlcharsNl2br: htmlcharsNl2br,
		htmlify: htmlify,
		escapeForCssAttributeSelector: escapeForCssAttributeSelector,
		toDecimal: toDecimal,
		parseDecimal: parseDecimal,
		toCurrency: toCurrency,
		parseCurrency: parseCurrency,
		evalJsonString: evalJsonString,
		encode64: encode64,
		decode64: decode64,
		utf8_encode: utf8_encode,
		utf8_decode: utf8_decode
	};
	
	function repeat(str, count)
	{
		return new Array(count + 1).join(str);
	}
	
	function trim(str)
	{
		return $.trim(str);
		//return str.match(/^\s*(.*?)\s*$/)[1];
	}
	
	function htmlchars(str)
	{
		return str.replace(/&|"|'|<|>/g, _htmlReplace); // ");
	}
	
	function htmlcharsNl2br(str)
	{
		return str.replace(/&|"|'|<|>|\r\n|\r|\n/g, _htmlReplace); // ");
	}
	
	function htmlify(str)
	{
		return str.replace(/&|"|'|<|>|\r\n|\r|\n|\t/g, _htmlReplace); // ");
	}

	function _htmlReplace(str)
	{
		return htmlReplaceMap[str];
	}
	
	function escapeForCssAttributeSelector(str)
	{
		return str.replace(/(\W)/g, '\\$1');
	}

	function toCurrency(num) {
		var match = num.toFixed(2).match(/^(-?)(\d+)\.(\d+)$/);
		if (!match) return '0,00';
		var sign = match[1];
		var euro = match[2];
		var cents = match[3];
		
		var i = euro.length % 3;
		var r = sign + euro.substr(0, i);
		for (; i < euro.length; i += 3)
		{
			if (i != 0) r += '.';
			r += euro.substr(i, 3);
		}
		r += ',';
		r += cents;
		return r;
	}

/**
	function toCurrency(number)
	{
		var cent = Math.round(number * 100);
		var remainder = cent % 100;
		var s = '' + ((cent - remainder) / 100) + ',';
		remainder = Math.abs(remainder);
		return s + (remainder < 10 ? '0' + remainder : '' + remainder);
	}
*/
	
	function parseCurrency(string)
	{
		var match = string.match(/^([+-]?)(\d\d?\d?(?:(?:\.\d\d\d)*|(?:\d\d\d)*))(?:,(\d\d))?$/);
		if (!match) return null;
		return +(match[1] + match[2].replace(/\./, '') + (match[3] ? '.' + match[3] : ''));
	}
	
	function parseDecimal(string)
	{
		return Number(string.replace('.', '').replace(',', '.'));
	}
	
	function toDecimal(number,precision)
	{
		return number.toFixed(precision).replace('.', ',');
	}
	
	function evalJsonString(string)
	{
		var json = null;
		try
		{
			json = eval('(' + string + ')');
		}
		catch (error)
		{
		}
		return json;
	}

	
	// This code was written by Tyler Akins and has been placed in the
	// public domain.  It would be nice if you left this header intact.
	// Base64 code from Tyler Akins -- http://rumkin.com
	function encode64(input) {
		var output = "";
		var chr1, chr2, chr3;
		var enc1, enc2, enc3, enc4;
		var i = 0;
	
		do {
			chr1 = input.charCodeAt(i++);
	      chr2 = input.charCodeAt(i++);
	      chr3 = input.charCodeAt(i++);
	
	      enc1 = chr1 >> 2;
	      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
	      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
	      enc4 = chr3 & 63;
	
	      if (isNaN(chr2)) {
	         enc3 = enc4 = 64;
	      } else if (isNaN(chr3)) {
	         enc4 = 64;
	      }
	
	      output = output + keyStr.charAt(enc1) + keyStr.charAt(enc2) + 
	         keyStr.charAt(enc3) + keyStr.charAt(enc4);
	   } while (i < input.length);
	   
	   return output;
	}
	
	function decode64(input) {
	   var output = "";
	   var chr1, chr2, chr3;
	   var enc1, enc2, enc3, enc4;
	   var i = 0;
	
	   // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
	   input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
	
	   do {
	      enc1 = keyStr.indexOf(input.charAt(i++));
	      enc2 = keyStr.indexOf(input.charAt(i++));
	      enc3 = keyStr.indexOf(input.charAt(i++));
	      enc4 = keyStr.indexOf(input.charAt(i++));
	
	      chr1 = (enc1 << 2) | (enc2 >> 4);
	      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
	      chr3 = ((enc3 & 3) << 6) | enc4;
	
	      output = output + String.fromCharCode(chr1);
	
	      if (enc3 != 64) {
	         output = output + String.fromCharCode(chr2);
	      }
	      if (enc4 != 64) {
	         output = output + String.fromCharCode(chr3);
	      }
	   } while (i < input.length);
	
		//FIXME small hack
	   return utf8_decode(output);
	}
	
	// public method for url encoding
	function utf8_encode(string) {
		string = string.replace(/\r\n/g,"\n");
		var utftext = "";
 
		for (var n = 0; n < string.length; n++) {
 
			var c = string.charCodeAt(n);
 
			if (c < 128) {
				utftext += String.fromCharCode(c);
			}
			else if((c > 127) && (c < 2048)) {
				utftext += String.fromCharCode((c >> 6) | 192);
				utftext += String.fromCharCode((c & 63) | 128);
			}
			else {
				utftext += String.fromCharCode((c >> 12) | 224);
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
				utftext += String.fromCharCode((c & 63) | 128);
			}
 
		}
 
		return utftext;
	}
 
	// public method for url decoding
	function utf8_decode(utftext) {
		var string = "";
		var i = 0;
		var c = c1 = c2 = 0;
 
		while ( i < utftext.length ) {
 
			c = utftext.charCodeAt(i);
 
			if (c < 128) {
				string += String.fromCharCode(c);
				i++;
			}
			else if((c > 191) && (c < 224)) {
				c2 = utftext.charCodeAt(i+1);
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
				i += 2;
			}
			else {
				c2 = utftext.charCodeAt(i+1);
				c3 = utftext.charCodeAt(i+2);
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
				i += 3;
			}
 
		}
 
		return string;
	}
	
})();



/**
 * Timer Klasse
 *
 * Erleichtert die Arbeit mit Timeouts oder Intervallen.
 *
 * Siehe kru.timer.js um den Timer als jQuery UI Komponente zu benutzen.
 *
 */


function Timer(callback, delay, numTicks)
{
	if (callback == undefined) callback = null;
	if (delay == undefined) delay = 1000;
	if (numTicks == undefined) numTicks = 1;
	
	this._callback = callback;
	this._delay = delay;
	this._numTicks = numTicks;
	this._tickCount = 0;
	this._timerHandle = null;
	this._startTime = 0;
	var self = this;
	this._onTickDelegate = function() { self._onTick(); };
}

//Stoppt den Timer
Timer.prototype.stop = function()
{
	if (this._timerHandle !== null)
	{
		if (this._numTicks != 1)
		{
			clearInterval(this._timerHandle);
		}
		else if (this._tickCount == 0)
		{
			clearTimeout(this._timerHandle);
		}
		this._timerHandle = null;
	}
};

// Startet den Timer
Timer.prototype.start = function(callback, delay, numTicks)
{
	if (callback == undefined) callback = null;
	if (delay == undefined) delay = -1;
	if (numTicks == undefined) numTicks = -1;
	
	this.stop();
	
	if (callback == null && (callback = this._callback) == null)
	{
		throw new Error('Timer callback ist not set.');
	}
	if (delay < 0 && (delay = this._delay) < 0)
	{
		throw new Error('Timer delay ist not set.');
	}
	if (numTicks < 0 && (numTicks = this._numTicks) < 0)
	{
		throw new Error('Number of ticks is not set.');
	}
	
	this._callback = callback;
	this._delay = delay;
	this._numTicks = numTicks;
	this._tickCount = 0;
	this._startTime = new Date().getTime();
	
	if (numTicks != 1)
	{
		this._timerHandle = setInterval(this._onTickDelegate, delay);
	}
	else
	{
		this._timerHandle = setTimeout(this._onTickDelegate, delay);
	}
	
};

Timer.prototype._onTick = function()
{
	this._tickCount += 1;
	if (this._numTicks != 0 && this._tickCount == this._numTicks)
	{
		this.stop();
	}
	
	this._callback.call(window, this);
};

// Gibt die Anzahl Ticks zurück
Timer.prototype.getTickCount = function()
{
	return this._tickCount;
};

Timer.prototype.getStartTime = function()
{
	return this._startTime;
};

Timer.prototype.getDelay = function()
{
	return this._delay;
};

Timer.prototype.getNumTicks = function()
{
	return this._numTicks;
};



var SingleFileUploader = {};

$.widget("kru.singlefileuploader", (function()
{
	var nextId = 0;
	var singleFileUploaders = [];
	
	SingleFileUploader.externalInterfaceCall = function(action, id, data)
	{
		var singleFileUploader = singleFileUploaders[id];
		if (singleFileUploader == undefined) return;
		
		singleFileUploader._externalInterfaceCall(action, data);
	};
	
	return {
		_init: function()
		{
			var o = this.options;
			if (!o.url)
			{
				throw new Error('Could not create SingleFileUploader. Please specify the url.');
			}
			
			this.id = nextId++;
			singleFileUploaders[this.id] = this;
			
			var $swf = $(document.createElement('div'))
				.attr('id', '__singleFileUploader' + this.id)
				.css(
					{
						width: o.width,
						height: o.height
					}
				)
				.appendTo(this.element);
			
			var flashVars =
			{
				id: this.id,
				browseDescription: encodeURIComponent(o.browseDescription),
				browseExtension: encodeURIComponent(o.browseExtension),
				url: encodeURIComponent(o.url),
				data: encodeURIComponent(typeof o.data == 'string' ? o.data : $.param(o.data)),
				uploadDataFieldName: encodeURIComponent(o.uploadDataFieldName),
				uploaderButtonImageUrl: encodeURIComponent(o.uploaderButtonImageUrl)
			};
			var params =
			{
				//allowScriptAccess: 'sameDomain',
				allowScriptAccess: 'always', // der Shop benutzt die Komponente mit; das swf liegt dann auf resources
				bgColor: o.bgColor,
				wmode: o.wmode
			};
			var attributes =
			{
				'class': 'lookThisIsMyFlash'
			};
		
			swfobject.embedSWF(
				o.swfUrl, $swf.attr('id'), o.width, o.height,
				"10.0.0", o.xiSwfUrl,
				flashVars, params, attributes
			);
		},
		
		destroy: function()
		{
			delete singleFileUploaders[this.id];
			this.element.find('.lookThisIsMyFlash').remove();
			$.widget.prototype.destroy.call(this);
		},
		
		_externalInterfaceCall: function(action, data)
		{
			switch (action)
			{
			case "UploaderEvent:finished":
				this._trigger('finished', null /* event */, data);
				break;
			}
		}
		
	};
	
})());



$.kru.singlefileuploader.getter = [];

$.kru.singlefileuploader.defaults = {
	browseDescription: 'Alle Dateien',
	browseExtension: '*.*',
	url: null,
	data: '',
	uploadDataFieldName: 'Filedata',
	uploaderButtonImageUrl: '/resources/images/upload-button.png',
	width: 100,
	height: 100,
	bgColor: '#000000',
	wmode: 'transparent',
	swfUrl: '/resources/swf/SingleFileUploader.swf',
	xiSwfUrl: Environment.xiSwfUrl
};



/**
 * 
 * Timer-Wrapper für die jQuery UI; verwendet intern die Timer Klasse
 * 
 * Optionen des Timers:
 *	callback: Callback-Funktion, die mit jedem Tick aufgerufen werden soll.
 *		Diese Funktion wird mit dem Element (als ersten Parameter) aufgerufen,
 *		auf dem der Timer erzeugt wurde.
 *	delay: Verzögerung in ms
 *	numTicks: Anzahl Ticks, die der Timer ausführen soll; ein Wert von 0 führt
 *		den Timer endlos aus
 * 
 * 
 * Beispiel: Timer erzeugen und verwenden
 * 
 * function test()
 * {
 * 	alert('Der wievielte Aufruf des Timers? ' + $(this).timer('getTickCount'));
 * }
 * 
 * $('body')
 * 	.timer({callback: test, delay: 1000, numTicks: 4}) // erzeugt den Timer; es wird ein Options-Objekt übergeben
 * 	.timer('start'); // ruft die Methode start() auf, es wird ein String (Name der Methode) übergeben
 * 
 * 
 */

$.widget("kru.timer",
{
	// Konstruktor
	_init: function()
	{
		this._timer = new Timer();
		this._callback = null;
		var self = this;
		this._onTickDelegate = function(timer) { self._onTick(); };
	},
	
	// Wird von der UI aufgerufen, wenn das zur Komponente gehörige Element
	// aus dem DOM entfernt wird bzw die Komponente vom DOM-Element gelöst wird.
	destroy: function()
	{
		this._timer.stop();
		this._timer = null;
		$.widget.prototype.destroy.call(this);
	},
	
	// Stoppt den Timer
	stop: function()
	{
		this._timer.stop();
	},
	
	// Startet den Timer mit den aktuell gesetzten Optionen
	start: function()
	{
		var callback = this.options.callback;
		var delay = this.options.delay;
		var numTicks = this.options.numTicks;
		
		this._callback = callback;
		this._timer.start(this._onTickDelegate, delay, numTicks);
	},
	
	_onTick: function()
	{
		this._callback.call(this.element[0]);
	},
	
	// Gibt die Anzahl Ticks zurück
	getTickCount: function()
	{
		return this._timer.getTickCount();
	}
	
});

// Hier werden Methoden gelistet, die einen Wert zurückgeben
$.kru.timer.getter = ['getTickCount'];

// Standardwerte des Timers
$.kru.timer.defaults =
{
	callback: null,
	delay: 1000,
	numTicks: 0
};



$.widget("kru.ajaxrequest", (function()
{
	return {
		_init: function()
		{
			this.xhr = null;
		},
		
		_reset: function()
		{
			if (this.xhr != null)
			{
				this.xhr.abort();
				this.xhr = null;
			}
		},
		
		destroy: function()
		{
			this._reset();
		},
		
		load: function(ajaxOptions)
		{
			if (ajaxOptions == undefined) ajaxOptions = null;
			
			var extendedAjaxOptions = $.extend({}, this.options.ajaxOptions, ajaxOptions);
			var container = this.options.container;
			if (container != null)
			{
				var success = extendedAjaxOptions.success;
				extendedAjaxOptions.success = function(data, textStatus)
				{
					if (data.htmlContent == undefined) return;
					
					$(container).html(data.htmlContent);
					
					if (success)
					{
						success(data, textStatus);
					}
				};
			}
			
			
			this._reset();
			this.xhr = $.ajax(extendedAjaxOptions);
		}
		
	};
	
})());

$.kru.ajaxrequest.getter = [];
$.kru.ajaxrequest.defaults = {
	ajaxOptions: {},
	container: null
};



$.widget("kru.autoimage", (function()
{
	
	return {
		_init: function()
		{
			if (this.element[0].complete)
			{
				this.applyBoxSize();
			}
			else
			{
				var self = this;
				this.element.bind('load.autoimage', function(event) { self._imageLoadHandler(event); });
			}
		},
		
		destroy: function()
		{
			this.element.unbind('.autoimage');
		},
		
		_boxSize: function(imageSize, boxSize)
		{
			if (imageSize.x <= boxSize.x && imageSize.y <= boxSize.y)
			{
				return {
					x: imageSize.x,
					y: imageSize.y
				};
			}
			else if (imageSize.x / boxSize.x > imageSize.y / boxSize.y)
			{
				return {
					x: boxSize.x,
					y: Math.round(imageSize.y * (boxSize.x / imageSize.x))
				};
			}
			else
			{
				return {
					x: Math.round(imageSize.x * (boxSize.y / imageSize.y)),
					y: boxSize.y
				};
			}
		},

		_imageLoadHandler: function(event)
		{
			this.applyBoxSize();
		},
		
		applyBoxSize: function()
		{
			var img = this.element[0];
			var imageSize = {x: img.width, y: img.height};
			var boxSize = {x: this.options.width, y: this.options.height};
			
			var size = this._boxSize(imageSize, boxSize);
			
			if (this.options.pad)
			{
				var left = (boxSize.x - size.x) >> 1;
				var top = (boxSize.y - size.y) >> 1;
				this.element.css('padding',
						top + 'px '
						+ (boxSize.x - size.x - left) + 'px '
						+ (boxSize.y - size.y - top) + 'px '
						+ left + 'px'
					);
			}
			
			this.element
				.width(size.x)
				.height(size.y)
				.show();
		}
		
	};
	
})());

$.kru.autoimage.getter = [];
$.kru.autoimage.defaults = {
	width: 100,
	height: 100,
	pad: false
};


/**
Ziel des Datacontrols:

	Es wird eine verbindung zu einem PHP-seitigem Datenliefarent zur Verfügung 
	gestellt. Die Komponente auf der das Control registriert wird ist auch 
	gleich der Container für ggf. zurückgegebenes HTML.
	
	Optionen
		numPerPage
			integer[1>=x<∞]=10
			Anzahl Elemente die Pro Seite ausgegeben werden sollen 
		page
			integer[1>=x<∞]=1
			Seite die geladen wird wenn das control initialisiert wird 
		noAutoload
			boolean=false
			verhindert das automatisch die 
		url
			string[URL]
			Ressourcenlieferant bei dem die Daten angefragt werden
			string
		additional
			object
			enthält Key-Value-Paare die zusätzlich für das Datacontrol gelten sollen
		
	Zustände	
		numItems 
			Anzahl der Elemente laut dem letzten erfolgreichen Request
			Ist null falls noch kein Request durchgeführt wurde

	Events
		retrievedata
			wenn Daten abgerufen werden. Daten sollten last-chance modifiziert 
			werden können. Ladevorgang kann verhindert werden 
		retrievedatafailed
			Beim laden der Daten ist nicht das erwartete zurückgekommen oder 
			der Request ist gänzlich gescheitert
		retrievedatasucceeded
			wenn Daten abgerufen wurden wird gefeuert, bevor das DataControl 
			die Daten liest empfangene Daten können modifiziert werden Vorgang 
			kann abgebrochen werden
		retrievedataupdated
			wenn Daten abgerufen wurden wird gefeuert, nachdem das DataControl
			die empfangenen Daten verarbeitet hat	
		retrievedatacompleted
			wenn der Ajaxrequest abgeschlossen wurde unabhängig von Erfolg 
			oder Fehler
		numitemschanged
			wird ausgelöst wenn die gesamte Anzahl der Elemente sich geändert 
			hat oder zum ersten mal bekannt wird 
		pagechanged
			wenn sich die Seite geändert hat. Vorgang kann abgebrochen werden.
			wird bei einer Änderung gefeuert
		numperpagechanged
			Anzahl angezeigter Elemente pro Seite hat sich geändert Vorgang kann
			abgebrochen werden

	Methoden
		updateContents
			refreshed die aktuell angezeigte Seite
		setPage
			um eine Seite anzusteuern. Wenn die selbe Seite übergeben wird wie 
			aktuell dargestellt dann passiert nichts!  
		setNumPerPage
			Wenn die gleiche Anzahl angegeben wird wie bereit gesetzt dann 
			passiert nichts!		
		
	Funktionen
		getPage
			gibt die aktuell dargestellte Seite zurück. 
		getNumItems
			gibt die anzahl der elemente zurück. kann null sein wenn es noch 
			keine erfolgreiche anfrage gegeben hat. 
		getNumPerPage
			gibt zurück wieviele Elemente auf jeder Seite geladen werden
		getUrl
			gibt einfach die Url zum Ressourcenlieferanten zurück
			
	Listeners
		keine
*/

$.widget("kru.datacontrol",
{
	_debug: function(x){},
	_init: function()
	{
		var self = this;
		this._debug('es wird das Datacontrol auf das Element '+this.element+' initialisiert');
		// um den mehrfachlauf von requests zu verhindern
		this.runningRequest = null;
		this.queuedRequest = null;
		// startwerte
		this.destroyed = false;
		this._setData('numItems', null);
		if (this._getData('noAutoload')) return; 
		// wenn dom ready 
		$(function(){ self._loadPage(); });
	},
	destroy: function()
	{
		this._debug('es wurde das Datacontrol auf dem Element '+this.element+' zerstört')
		// falls noch ajaxrequests laufen konnen diese hiermit festellendas sie 
		// nichts mehr zu tun haben. 
		this.destroyed = true;
		
		$.widget.prototype.destroy.call(this);
	},
	_pageReceived: function(data,request)
	{
		this._debug('Datacontrol._pageReceived('+data+','+request.customData.page+')');
		// die Komponente wurde mittlerweile entladen
		if (this.destroyed) return;
		var event = $.Event();
		//event.data = data; // kann null sein
		if (
			data==null
			||
			data.numItems == undefined
			||
			(
				data.data == undefined
				&&
				data.HTML == undefined
			)	
		)
		{
			this._trigger('retrievedatafailed',event,data);
		} 
		else
		{
			this._trigger('retrievedatasucceeded', event,data);
			if (!event.isDefaultPrevented())
			{
				// die daten verarbeiten
				if (data.numItems != this._getData('numItems'))
				{
					var data2 = {oldNumItems : this._getData('numItems')};
					this._setData('numItems', data.numItems);
					this._trigger('numitemschanged', 0,	data2);
				}
				if (data.HTML!=undefined) this.element.html(data.HTML);
				this._trigger('retrievedataupdated', event,data);
			}
		}
		this._trigger('retrievedatacompleted', event,data);
		this.runningRequest = null;
		// prüfen ob nicht bereits ein neues Request ausgeführt werden soll
		if (this.queuedRequest)
		{
			var queuedRequest = this.queuedRequest;
			this.queuedRequest = null;
			this._internalLoad(queuedRequest);
		}
	},
	_internalLoad: function(request)
	{
  		this._debug('Datacontrol._internalLoad('+request.customData.page+')');
  		var event = $.Event();
  		this._trigger('retrievedata', event, request);
  		if (event.isDefaultPrevented()) return;
		this.runningRequest = request;
		$.ajax(request);
	},
	_loadPage: function(parameters)
	{
		if (parameters== undefined)parameters = {};
		this._debug('Datacontrol._loadpage()');
		var self = this;
		var submitData = $.kru.getHashFromMixedParameters([parameters,this._getData('additional')]);
		submitData.page = this.getPage();
		submitData.numPerPage = this.getNumPerPage();
		var request = 
		{
			url: this.getUrl(),
			data: submitData, 
			customData: submitData, 
			success: function(data, textStatus){ self._pageReceived(data,this); },
			error: function(XMLHttpRequest, textStatus, errorThrown){ self._pageReceived(null,this); }
		};
		if (this.runningRequest != null)this.queuedRequest = request;else this._internalLoad(request);
	},
	updateContents: function(parameters)
	{
		this._debug('Datacontrol.updateContens()');
		this._loadPage($.kru.getHashFromMixedParameters(parameters));
	},
	getPage: function() { return this._getData('page'); },
	setPage: function(newpage,parameters) 
	{
		this._debug('Datacontrol.setPage('+newpage+')');
		if (newpage == this._getData('page')) return; 
		var data = {oldpage : this._getData('page')};
		this._setData('page',newpage); 
		var event = $.Event();
		this._trigger('pagechanged', event, data);
		if (event.isDefaultPrevented()) return;
		this.updateContents(parameters);
	},
	getNumItems: function () { return this._getData('numItems'); },
	getNumPerPage: function() { return this._getData('numPerPage'); },
	setNumPerPage: function(newNumPerPage)
	{
		this._debug('Datacontrol.setNumPerPage('+newNumPerPage+')');
		newNumPerPage = Math.max(1,newNumPerPage);
		if (newNumPerPage == this.getNumPerPage())return ;
		var event = $.Event();
		var data = {oldNumPerPage : this._getData('numPerPage')};
		this._setData('numPerPage', newNumPerPage);
		this._trigger('numperpagechanged',event,data);
		if (event.isDefaultPrevented()) return;
		this.updateContents();
	},
	getUrl: function () { return this._getData('url'); }
});
$.kru.datacontrol.getter = ['getNumPerPage','getNumItems','getPage','getUrl'];
$.kru.datacontrol.defaults = 
{ 
	numPerPage	: 10, 
	page		: 1,
	noAutoload	: false,
	url			: null,
	additional	: null
};

/**
Ziel des Browsecontrols:
	
	Die Komponente auf der das Control registriert wird mit elementen zum 
	Blättern befüllt. Sie reagiert auf events die auf dieses Element losgelassen 
	werden.
	Wenn geblättert werden soll dann wird dies auch per Events mitgeteilt
	
	Optionen
		connectTo
			DomElement
			Das Element welches das Datacontrol enthält bzw enthalten wird
		mode 
			string['logaritmisch'|'linear'|'delphi']
			Modus wie die entfernten seiten ermittelt werden
			linear
				traditionelle liste mit inc 1 zu jedem Blatt
			logaritmisch
				incrementiert logarithmisch die Blätter
			delphi
				links und rechts den Browserange zu füllen und eine über den 
				gesamten Bereich gehende liste präsentieren
			
		browseRange
			integer[1>=x<=∞]
			Wie viele Element zum Blättern links und rechts von der aktuellen 
			Seite zu zeichen sind, vorausgesetzt es gibt diese Seiten auch
		logBase
			integer[2>=x<=∞]
			beistimmt bei mode == 'logaritmisch' die Schrittweite
		labels
			Callbackfunction welche verwendet werden kann um statt der 
			Seitenzahlen z.B. Monate zu schreiben
			
		
	Zustände
		pages
			merkt sich die aktuell dargestellten Seiten
			
	Events
		keine
		
	Methoden 
		keine
		
	Funktionen
		keine
			
	Listeners (fest verdrahtet)
		numitemschanged
			erwartet als daten ein Object das wenigstens folgende Informationen 
			enthält: {numItems:a, numPerPage:b, page:c, newNumItems:d}
		numperpagechanged
			erwartet als daten ein Object das wenigstens folgende Informationen 
			enthält: {numItems:a, numPerPage:b, page:c, newNumPerPage:d}
		pagechanged
			erwartet als daten ein Object das wenigstens folgende Informationen 
			enthält: {numItems:a, numPerPage:b, page:c, newPage:d}
*/

$.widget("kru.browsecontrol",function(){
	var $blattTemplate = $('<div class="blatt"></div>');
	
	return {
		_debug: function(x){},
		_init: function()
		{
			var self = this;
			this.pages = [];
			this.blaetter = [];
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			connectTo.bind('datacontrolnumitemschanged', function(evt,data){ self._numItemsChangedHandler(evt, data); });
			connectTo.bind('datacontrolnumperpagechanged', function(evt,data){ self._numPerPageChangedHandler(evt, data); });
			connectTo.bind('datacontrolpagechanged', function(evt,data){ self._pageChangedHandler(evt, data); });
			
			// leeren
			this.element.empty();
			this.element.addClass('browsecontrol');
			// Elemente
			var element = $blattTemplate.clone().text('|<');
			this.element.append(element);
			element.bind('click', function(){ self._setPageFirst();});
			
			element = $blattTemplate.clone().text('<');
			this.element.append(element);
			element.bind('click', function(){ self._setPagePrevious();});
			
			// anhand des Broserange die elemente vorerzeugen
			var browseRange = this._getData('browseRange');
			for(var i=0; i<=browseRange*2+1;i++){
				element = $blattTemplate.clone().text('');//
				this.element.append(element);
				this.blaetter.push(element);
				element.bind('click', {elementIndex:i}, function(event){ self._setPageHandler(event);});
			}
			
			element = $blattTemplate.clone().text('>');
			this.element.append(element);
			element.bind('click', function(){ self._setPageNext();});
			
			element = $blattTemplate.clone().text('>|');
			this.element.append(element);
			element.bind('click', function(){ self._setPageLast();});
			
			this.element.append('<div class="clear"/>');
		},
		destroy: function()
		{
			this.element.removeClass('browsecontrol');
			this.element.empty();
			
			$.widget.prototype.destroy.call(this);
		},
		_getMaxPage : function()
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			var numItems = connectTo.datacontrol('getNumItems');
			var numPerPage = connectTo.datacontrol('getNumPerPage');
			return Math.max(1,Math.ceil(numItems/numPerPage));
		},
		
		_setPageHandler : function(evt)
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			connectTo.datacontrol('setPage',this.pages[evt.data.elementIndex]);
		},
		
		_setPageFirst : function(evt)
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			connectTo.datacontrol('setPage',1);
		},
		_setPagePrevious : function(evt)
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			connectTo.datacontrol('setPage',Math.max(1,connectTo.datacontrol('getPage')-1));
		},
		_setPageNext : function(evt)
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			var maxPage = this._getMaxPage();
			
			connectTo.datacontrol('setPage', Math.min(maxPage,connectTo.datacontrol('getPage')+1));
		},
		_setPageLast : function(evt)
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			var maxPage = this._getMaxPage();
			connectTo.datacontrol('setPage', maxPage);
		},
		_setPages : function()
		{
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			var page = connectTo.datacontrol('getPage');
			this.pages = this._pagesToHave();
			
			if (typeof(this._getData('labels'))=='function'){
				labels = this._getData('labels');
			}else{
				labels = null;
			}
			
			for(var i = 0 , laenge = this.blaetter.length; i < laenge;i++){
				if(this.pages[i]){
					var blatt = this.blaetter[i];
					// falls eine Callbackfunction gesetzt ist <-> ansonsten einfach die Zahl
					blatt.text(labels==undefined?this.pages[i]:labels(this.pages[i]));
					blatt.css({display:'block'});
					if (page == this.pages[i]){
						blatt.addClass('active');
					}else{
						blatt.removeClass('active');
					}
				}else{
					this.blaetter[i].css({display:'none'});
				}
			}
			if (page > this.pages[this.pages.length -1]){
				this._setPageLast();
			}
		},
		_numItemsChangedHandler : function(event,data)
		{
			this._debug('browsecontrol._numItemsChangedHandler');
			this._setPages();
		},
		_numPerPageChangedHandler : function(event,data)
		{
			this._debug('browsecontrol._numPerPageChangedHandler');
			this._setPages();
		},
		_pageChangedHandler : function(event,data)
		{
			this._debug('browsecontrol._pageChangedHandler');
			this._setPages();
		},
		_pagesToHave: function (currentpage)
		{
			var pages = [];
			var connectTo = $(this._getData('connectTo')).ensureSingleElement();
			var browseRange = this._getData('browseRange');
			var logBase = this._getData('logBase');
			var mode = this._getData('mode');
			
			var page = connectTo.datacontrol('getPage');
			var maxPage = this._getMaxPage();
			switch(mode)
			{
			case 'logaritmisch':
				// um aktuelle position
				for (var i = Math.min(browseRange -1 ,Math.ceil(Math.log(page)/Math.log(logBase))) ; i>=0;i--) 
				{
					pages.push(page - Math.pow(logBase,i));
				}
				
				
				
				
				pages.push(page);
				for (var i = 0; i<= Math.min(browseRange -1,Math.ceil(Math.log(maxPage-page)/Math.log(logBase))); i++)
				{
					pages.push(page + Math.pow(logBase,i));
				}
				break;
			case 'linear':
				for (var i = browseRange + Math.max(0 , browseRange - (maxPage - (page))); i>=1;i--)
				{
					pages.push(page - i);
				}
				
				
				
				
				pages.push(page);
				for (var i = 1; i <= browseRange + Math.max(0, browseRange - (page-1));i++)
				{
					pages.push(page + i);
				}
				break;
				
			case 'delphi':
				var potentielLinks = Math.max(0,page -1);
				var potentielRechts = Math.max(0,maxPage - page);
				var vorschlagLinks = this._delphiCalcCntBlaetter(potentielLinks,browseRange);
				var vorschlagRechts = this._delphiCalcCntBlaetter(potentielRechts,browseRange);
				// wenn eine Seite nicht benötigt wird wegen anfang oder ende
				// dann darf dem anderen eine Seite dazugegeben werden
				// jetzt noch den vorschlag korrigieren damit die maximal anzuzeigenden elemente nicht überschritten werden
				var vorschlagLinksEntgueltig = Math.min(vorschlagLinks, browseRange - 1       + Math.max(0,(browseRange-1)-vorschlagRechts)   +  (vorschlagRechts==0&&potentielRechts==0?1:0));
				var vorschlagRechtsEntgueltig = Math.min(vorschlagRechts, browseRange - 1     +  Math.max(0,(browseRange-1)-vorschlagLinks)   +  (vorschlagLinks==0&&potentielLinks==0?1:0));
				// es gibt nur einen Fall wo der vorschlag scheitert und das ist 
				// bei potentiel = 1. Hier würde die Basisberechnung nicht 
				// funktionieren. Deshalb hier das Trick mit dem Math.max
				for (var i = vorschlagLinksEntgueltig; i >= 0; i--)
				{
					pages.push(page - Math.ceil(Math.pow( potentielLinks, i/Math.max(1,vorschlagLinksEntgueltig) )));
				}
				pages.push(page);
				for (var i = 0; i <= vorschlagRechtsEntgueltig; i++)
				{
					pages.push(page + Math.ceil(Math.pow(potentielRechts,i/Math.max(1,vorschlagRechtsEntgueltig) )));
				}
				// um die aktuelle Seite füllen und zwar immer auf der Schwächeren Seite  
				// arr.splice(2,0,"Lene");
				break;
			}
			// entferne Elemente die ausserhalb der möglichen Seiten sind
			for (var i = 0, temp = pages, pages = [], length = temp.length; i < length; i++){
				var cmp = temp[i]; 
				if ( cmp<= maxPage && cmp >= 1){
					pages.push(cmp);
				}
			}
			return pages;
		},
		
		_delphiCalcCntBlaetter : function(platz, browseRange)
		{
			for (var b=1; b<=Math.min(platz,browseRange*2);b++)
			{
				var max = 0;
				for(var i = 1; i<=b ; i++)
				{
					var minpages = Math.pow(i,b/i); 
					max = Math.max(minpages,max);
				}
				// dieses Maximum muss kleiner oder gleich dem verfügbaren platz sein!
				if (max>=platz)
				{
					return b-1;
				}
			}
			return b-1;
		}
		
	}
}());

$.kru.browsecontrol.getter = [];

$.kru.browsecontrol.defaults = {
	connectTo		: null,
	mode			: 'linear',
	browseRange		: 5,
	logBase			: 5,
	labels			: null
};


/*!
 * jQuery Form Plugin
 * version: 2.43 (12-MAR-2010)
 * @requires jQuery v1.3.2 or later
 *
 * Examples and documentation at: http://malsup.com/jquery/form/
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
;(function($) {

/*
	Usage Note:
	-----------
	Do not use both ajaxSubmit and ajaxForm on the same form.  These
	functions are intended to be exclusive.  Use ajaxSubmit if you want
	to bind your own submit handler to the form.  For example,

	$(document).ready(function() {
		$('#myForm').bind('submit', function() {
			$(this).ajaxSubmit({
				target: '#output'
			});
			return false; // <-- important!
		});
	});

	Use ajaxForm when you want the plugin to manage all the event binding
	for you.  For example,

	$(document).ready(function() {
		$('#myForm').ajaxForm({
			target: '#output'
		});
	});

	When using ajaxForm, the ajaxSubmit function will be invoked for you
	at the appropriate time.
*/

/**
 * ajaxSubmit() provides a mechanism for immediately submitting
 * an HTML form using AJAX.
 */
$.fn.ajaxSubmit = function(options) {
	// fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
	if (!this.length) {
		log('ajaxSubmit: skipping submit process - no element selected');
		return this;
	}

	if (typeof options == 'function')
		options = { success: options };

	var url = $.trim(this.attr('action'));
	if (url) {
		// clean url (don't include hash vaue)
		url = (url.match(/^([^#]+)/)||[])[1];
   	}
   	url = url || window.location.href || '';

	options = $.extend({
		url:  url,
		type: this.attr('method') || 'GET',
		iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
	}, options || {});

	// hook for manipulating the form data before it is extracted;
	// convenient for use with rich editors like tinyMCE or FCKEditor
	var veto = {};
	this.trigger('form-pre-serialize', [this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
		return this;
	}

	// provide opportunity to alter form data before it is serialized
	if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSerialize callback');
		return this;
	}

	var a = this.formToArray(options.semantic);
	if (options.data) {
		options.extraData = options.data;
		for (var n in options.data) {
		  if(options.data[n] instanceof Array) {
			for (var k in options.data[n])
			  a.push( { name: n, value: options.data[n][k] } );
		  }
		  else
			 a.push( { name: n, value: options.data[n] } );
		}
	}

	// give pre-submit callback an opportunity to abort the submit
	if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
		log('ajaxSubmit: submit aborted via beforeSubmit callback');
		return this;
	}

	// fire vetoable 'validate' event
	this.trigger('form-submit-validate', [a, this, options, veto]);
	if (veto.veto) {
		log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
		return this;
	}

	var q = $.param(a);

	if (options.type.toUpperCase() == 'GET') {
		options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
		options.data = null;  // data is null for 'get'
	}
	else
		options.data = q; // data is the query string for 'post'

	var $form = this, callbacks = [];
	if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
	if (options.clearForm) callbacks.push(function() { $form.clearForm(); });

	// perform a load on the target only if dataType is not provided
	if (!options.dataType && options.target) {
		var oldSuccess = options.success || function(){};
		callbacks.push(function(data) {
			var fn = options.replaceTarget ? 'replaceWith' : 'html';
			$(options.target)[fn](data).each(oldSuccess, arguments);
		});
	}
	else if (options.success)
		callbacks.push(options.success);

	options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
		for (var i=0, max=callbacks.length; i < max; i++)
			callbacks[i].apply(options, [data, status, xhr || $form, $form]);
	};

	// are there files to upload?
	var files = $('input:file', this).fieldValue();
	var found = false;
	for (var j=0; j < files.length; j++)
		if (files[j])
			found = true;

	var multipart = false;
//	var mp = 'multipart/form-data';
//	multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);

	// options.iframe allows user to force iframe mode
	// 06-NOV-09: now defaulting to iframe mode if file input is detected
   if ((files.length && options.iframe !== false) || options.iframe || found || multipart) {
	   // hack to fix Safari hang (thanks to Tim Molendijk for this)
	   // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
	   if (options.closeKeepAlive)
		   $.get(options.closeKeepAlive, fileUpload);
	   else
		   fileUpload();
	   }
   else
	   $.ajax(options);

	// fire 'notify' event
	this.trigger('form-submit-notify', [this, options]);
	return this;


	// private function for handling file uploads (hat tip to YAHOO!)
	function fileUpload() {
		var form = $form[0];

		if ($(':input[name=submit]', form).length) {
			alert('Error: Form elements must not be named "submit".');
			return;
		}

		var opts = $.extend({}, $.ajaxSettings, options);
		var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);

		var id = 'jqFormIO' + (new Date().getTime());
		var $io = $('<iframe id="' + id + '" name="' + id + '" src="'+ opts.iframeSrc +'" onload="(jQuery(this).data(\'form-plugin-onload\'))()" />');
		var io = $io[0];

		$io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });

		var xhr = { // mock object
			aborted: 0,
			responseText: null,
			responseXML: null,
			status: 0,
			statusText: 'n/a',
			getAllResponseHeaders: function() {},
			getResponseHeader: function() {},
			setRequestHeader: function() {},
			abort: function() {
				this.aborted = 1;
				$io.attr('src', opts.iframeSrc); // abort op in progress
			}
		};

		var g = opts.global;
		// trigger ajax global events so that activity/block indicators work like normal
		if (g && ! $.active++) $.event.trigger("ajaxStart");
		if (g) $.event.trigger("ajaxSend", [xhr, opts]);

		if (s.beforeSend && s.beforeSend(xhr, s) === false) {
			s.global && $.active--;
			return;
		}
		if (xhr.aborted)
			return;

		var cbInvoked = false;
		var timedOut = 0;

		// add submitting element to data if we know it
		var sub = form.clk;
		if (sub) {
			var n = sub.name;
			if (n && !sub.disabled) {
				opts.extraData = opts.extraData || {};
				opts.extraData[n] = sub.value;
				if (sub.type == "image") {
					opts.extraData[n+'.x'] = form.clk_x;
					opts.extraData[n+'.y'] = form.clk_y;
				}
			}
		}

		// take a breath so that pending repaints get some cpu time before the upload starts
		function doSubmit() {
			// make sure form attrs are set
			var t = $form.attr('target'), a = $form.attr('action');

			// update form attrs in IE friendly way
			form.setAttribute('target',id);
			if (form.getAttribute('method') != 'POST')
				form.setAttribute('method', 'POST');
			if (form.getAttribute('action') != opts.url)
				form.setAttribute('action', opts.url);

			// ie borks in some cases when setting encoding
			if (! opts.skipEncodingOverride) {
				$form.attr({
					encoding: 'multipart/form-data',
					enctype:  'multipart/form-data'
				});
			}

			// support timout
			if (opts.timeout)
				setTimeout(function() { timedOut = true; cb(); }, opts.timeout);

			// add "extra" data to form if provided in options
			var extraInputs = [];
			try {
				if (opts.extraData)
					for (var n in opts.extraData)
						extraInputs.push(
							$('<input type="hidden" name="'+n+'" value="'+opts.extraData[n]+'" />')
								.appendTo(form)[0]);

				// add iframe to doc and submit the form
				$io.appendTo('body');
				$io.data('form-plugin-onload', cb);
				form.submit();
			}
			finally {
				// reset attrs and remove "extra" input elements
				form.setAttribute('action',a);
				t ? form.setAttribute('target', t) : $form.removeAttr('target');
				$(extraInputs).remove();
			}
		};

		if (opts.forceSync)
			doSubmit();
		else
			setTimeout(doSubmit, 10); // this lets dom updates render
	
		var domCheckCount = 100;

		function cb() {
			if (cbInvoked) 
				return;

			var ok = true;
			try {
				if (timedOut) throw 'timeout';
				// extract the server response from the iframe
				var data, doc;

				doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
				
				var isXml = opts.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
				log('isXml='+isXml);
				if (!isXml && (doc.body == null || doc.body.innerHTML == '')) {
				 	if (--domCheckCount) {
						// in some browsers (Opera) the iframe DOM is not always traversable when
						// the onload callback fires, so we loop a bit to accommodate
				 		log('requeing onLoad callback, DOM not available');
						setTimeout(cb, 250);
						return;
					}
					log('Could not access iframe DOM after 100 tries.');
					return;
				}

				log('response detected');
				cbInvoked = true;
				xhr.responseText = doc.body ? doc.body.innerHTML : null;
				xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
				xhr.getResponseHeader = function(header){
					var headers = {'content-type': opts.dataType};
					return headers[header];
				};

				if (opts.dataType == 'json' || opts.dataType == 'script') {
					// see if user embedded response in textarea
					var ta = doc.getElementsByTagName('textarea')[0];
					if (ta)
						xhr.responseText = ta.value;
					else {
						// account for browsers injecting pre around json response
						var pre = doc.getElementsByTagName('pre')[0];
						if (pre)
							xhr.responseText = pre.innerHTML;
					}			  
				}
				else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
					xhr.responseXML = toXml(xhr.responseText);
				}
				data = $.httpData(xhr, opts.dataType);
			}
			catch(e){
				log('error caught:',e);
				ok = false;
				xhr.error = e;
				$.handleError(opts, xhr, 'error', e);
			}

			// ordering of these callbacks/triggers is odd, but that's how $.ajax does it
			if (ok) {
				opts.success(data, 'success');
				if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
			}
			if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
			if (g && ! --$.active) $.event.trigger("ajaxStop");
			if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');

			// clean up
			setTimeout(function() {
				$io.removeData('form-plugin-onload');
				$io.remove();
				xhr.responseXML = null;
			}, 100);
		};

		function toXml(s, doc) {
			if (window.ActiveXObject) {
				doc = new ActiveXObject('Microsoft.XMLDOM');
				doc.async = 'false';
				doc.loadXML(s);
			}
			else
				doc = (new DOMParser()).parseFromString(s, 'text/xml');
			return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
		};
	};
};

/**
 * ajaxForm() provides a mechanism for fully automating form submission.
 *
 * The advantages of using this method instead of ajaxSubmit() are:
 *
 * 1: This method will include coordinates for <input type="image" /> elements (if the element
 *	is used to submit the form).
 * 2. This method will include the submit element's name/value data (for the element that was
 *	used to submit the form).
 * 3. This method binds the submit() method to the form for you.
 *
 * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
 * passes the options argument along after properly binding events for submit elements and
 * the form itself.
 */
$.fn.ajaxForm = function(options) {
	return this.ajaxFormUnbind().bind('submit.form-plugin', function(e) {
		e.preventDefault();
		$(this).ajaxSubmit(options);
	}).bind('click.form-plugin', function(e) {
		var target = e.target;
		var $el = $(target);
		if (!($el.is(":submit,input:image"))) {
			// is this a child element of the submit el?  (ex: a span within a button)
			var t = $el.closest(':submit');
			if (t.length == 0)
				return;
			target = t[0];
		}
		var form = this;
		form.clk = target;
		if (target.type == 'image') {
			if (e.offsetX != undefined) {
				form.clk_x = e.offsetX;
				form.clk_y = e.offsetY;
			} else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
				var offset = $el.offset();
				form.clk_x = e.pageX - offset.left;
				form.clk_y = e.pageY - offset.top;
			} else {
				form.clk_x = e.pageX - target.offsetLeft;
				form.clk_y = e.pageY - target.offsetTop;
			}
		}
		// clear form vars
		setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
	});
};

// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
$.fn.ajaxFormUnbind = function() {
	return this.unbind('submit.form-plugin click.form-plugin');
};

/**
 * formToArray() gathers form element data into an array of objects that can
 * be passed to any of the following ajax functions: $.get, $.post, or load.
 * Each object in the array has both a 'name' and 'value' property.  An example of
 * an array for a simple login form might be:
 *
 * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
 *
 * It is this array that is passed to pre-submit callback functions provided to the
 * ajaxSubmit() and ajaxForm() methods.
 */
$.fn.formToArray = function(semantic) {
	var a = [];
	if (this.length == 0) return a;

	var form = this[0];
	var els = semantic ? form.getElementsByTagName('*') : form.elements;
	if (!els) return a;
	for(var i=0, max=els.length; i < max; i++) {
		var el = els[i];
		var n = el.name;
		if (!n) continue;

		if (semantic && form.clk && el.type == "image") {
			// handle image inputs on the fly when semantic == true
			if(!el.disabled && form.clk == el) {
				a.push({name: n, value: $(el).val()});
				a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
			}
			continue;
		}

		var v = $.fieldValue(el, true);
		if (v && v.constructor == Array) {
			for(var j=0, jmax=v.length; j < jmax; j++)
				a.push({name: n, value: v[j]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: n, value: v});
	}

	if (!semantic && form.clk) {
		// input type=='image' are not found in elements array! handle it here
		var $input = $(form.clk), input = $input[0], n = input.name;
		if (n && !input.disabled && input.type == 'image') {
			a.push({name: n, value: $input.val()});
			a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
		}
	}
	return a;
};

/**
 * Serializes form data into a 'submittable' string. This method will return a string
 * in the format: name1=value1&amp;name2=value2
 */
$.fn.formSerialize = function(semantic) {
	//hand off to jQuery.param for proper encoding
	return $.param(this.formToArray(semantic));
};

/**
 * Serializes all field elements in the jQuery object into a query string.
 * This method will return a string in the format: name1=value1&amp;name2=value2
 */
$.fn.fieldSerialize = function(successful) {
	var a = [];
	this.each(function() {
		var n = this.name;
		if (!n) return;
		var v = $.fieldValue(this, successful);
		if (v && v.constructor == Array) {
			for (var i=0,max=v.length; i < max; i++)
				a.push({name: n, value: v[i]});
		}
		else if (v !== null && typeof v != 'undefined')
			a.push({name: this.name, value: v});
	});
	//hand off to jQuery.param for proper encoding
	return $.param(a);
};

/**
 * Returns the value(s) of the element in the matched set.  For example, consider the following form:
 *
 *  <form><fieldset>
 *	  <input name="A" type="text" />
 *	  <input name="A" type="text" />
 *	  <input name="B" type="checkbox" value="B1" />
 *	  <input name="B" type="checkbox" value="B2"/>
 *	  <input name="C" type="radio" value="C1" />
 *	  <input name="C" type="radio" value="C2" />
 *  </fieldset></form>
 *
 *  var v = $(':text').fieldValue();
 *  // if no values are entered into the text inputs
 *  v == ['','']
 *  // if values entered into the text inputs are 'foo' and 'bar'
 *  v == ['foo','bar']
 *
 *  var v = $(':checkbox').fieldValue();
 *  // if neither checkbox is checked
 *  v === undefined
 *  // if both checkboxes are checked
 *  v == ['B1', 'B2']
 *
 *  var v = $(':radio').fieldValue();
 *  // if neither radio is checked
 *  v === undefined
 *  // if first radio is checked
 *  v == ['C1']
 *
 * The successful argument controls whether or not the field element must be 'successful'
 * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
 * The default value of the successful argument is true.  If this value is false the value(s)
 * for each element is returned.
 *
 * Note: This method *always* returns an array.  If no valid value can be determined the
 *	   array will be empty, otherwise it will contain one or more values.
 */
$.fn.fieldValue = function(successful) {
	for (var val=[], i=0, max=this.length; i < max; i++) {
		var el = this[i];
		var v = $.fieldValue(el, successful);
		if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
			continue;
		v.constructor == Array ? $.merge(val, v) : val.push(v);
	}
	return val;
};

/**
 * Returns the value of the field element.
 */
$.fieldValue = function(el, successful) {
	var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
	if (typeof successful == 'undefined') successful = true;

	if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
		(t == 'checkbox' || t == 'radio') && !el.checked ||
		(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
		tag == 'select' && el.selectedIndex == -1))
			return null;

	if (tag == 'select') {
		var index = el.selectedIndex;
		if (index < 0) return null;
		var a = [], ops = el.options;
		var one = (t == 'select-one');
		var max = (one ? index+1 : ops.length);
		for(var i=(one ? index : 0); i < max; i++) {
			var op = ops[i];
			if (op.selected) {
				var v = op.value;
				if (!v) // extra pain for IE...
					v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
				if (one) return v;
				a.push(v);
			}
		}
		return a;
	}
	return el.value;
};

/**
 * Clears the form data.  Takes the following actions on the form's input fields:
 *  - input text fields will have their 'value' property set to the empty string
 *  - select elements will have their 'selectedIndex' property set to -1
 *  - checkbox and radio inputs will have their 'checked' property set to false
 *  - inputs of type submit, button, reset, and hidden will *not* be effected
 *  - button elements will *not* be effected
 */
$.fn.clearForm = function() {
	return this.each(function() {
		$('input,select,textarea', this).clearFields();
	});
};

/**
 * Clears the selected form elements.
 */
$.fn.clearFields = $.fn.clearInputs = function() {
	return this.each(function() {
		var t = this.type, tag = this.tagName.toLowerCase();
		if (t == 'text' || t == 'password' || tag == 'textarea')
			this.value = '';
		else if (t == 'checkbox' || t == 'radio')
			this.checked = false;
		else if (tag == 'select')
			this.selectedIndex = -1;
	});
};

/**
 * Resets the form data.  Causes all form elements to be reset to their original value.
 */
$.fn.resetForm = function() {
	return this.each(function() {
		// guard against an input with the name of 'reset'
		// note that IE reports the reset function as an 'object'
		if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
			this.reset();
	});
};

/**
 * Enables or disables any matching elements.
 */
$.fn.enable = function(b) {
	if (b == undefined) b = true;
	return this.each(function() {
		this.disabled = !b;
	});
};

/**
 * Checks/unchecks any matching checkboxes or radio buttons and
 * selects/deselects and matching option elements.
 */
$.fn.selected = function(select) {
	if (select == undefined) select = true;
	return this.each(function() {
		var t = this.type;
		if (t == 'checkbox' || t == 'radio')
			this.checked = select;
		else if (this.tagName.toLowerCase() == 'option') {
			var $sel = $(this).parent('select');
			if (select && $sel[0] && $sel[0].type == 'select-one') {
				// deselect all other options
				$sel.find('option').selected(false);
			}
			this.selected = select;
		}
	});
};

// helper fn for console logging
// set $.fn.ajaxSubmit.debug to true to enable debug logging
function log() {
	if ($.fn.ajaxSubmit.debug) {
		var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
		if (window.console && window.console.log)
			window.console.log(msg);
		else if (window.opera && window.opera.postError)
			window.opera.postError(msg);
	}
};

})(jQuery);



/**
 * TODO für dieses Plugin ist:
 * - Aufbau auf einer UL-Liste
 * - Ajax nur bei Anforderung, sonst alles aus der UL-Liste herleiten
 * - Rubrik-ID sollte in ein Input-Feld eingetragen werden, dessen Namen man selbst bestimmen kann
 * - Keine festen HTML-IDs mehr nutzen
 * - ggf. als UI-Widget umbauen
 * - predefinedCrump wird nicht mehr nötig sein, da sich alles anhand der Rubrik-ID ableiten lässt.
 */
(function($){
	
	// Template für Breadcrump
	var tmpl_rubrikcrump = $(
		'<span class="rubrikcrump-element" style="display:inline-block; padding-top:5px; padding-bottom:5px;">' +
			'<label for=""></label>' +
			'<input type="hidden" name="" value="" />' +
			'<span class="rubrikcrump"></span>' +
		'</span>'
	);
	
	function RubrikTree(id){
		this.id = id;
	}
	
	
	// Vorbereitung für Rubrikbaum
	RubrikTree.prototype.initializeTree = function(s){
		if($.fn.treeview == undefined)
			throw new Error('Dieses Plugin ist abhängig vom jQuery-Treeview-Plugin.');
		s = $.extend(s || {});
		if($.rubriktree == undefined)
			$.rubriktree = {};
		$.rubriktree[this.id] = $.extend({}, $.rubriktree[this.id], {
			nichtEingrenzen: s.nichtEingrenzen || 'alle',
			treeElement: s.treeElement || '#rubriktree',
			chooseText: s.chooseText || null,
			labelText: s.labelText || 'Rubrik:',
			rubrikcrump: s.rubrikcrump || tmpl_rubrikcrump,
			predefinedCrump: s.predefinedCrump || null,
			separator: s.separator || ' &raquo; ',
			rubrikId: s.rubrikId || 'alle',
			rubriktreeDialogId: s.rubriktreeDialogId || 'treeDialog',
			treeRubriknrId : s.treeRubriknrId || 'treeRubriknr',
			treeRubrikId : s.treeRubrikId || 'rubrikId',
			rubrikcrumpId: s.rubrikcrumpId || 'rubrikcrump',
			beforeOpen: s.beforeOpen || null,
			afterOpen: s.afterOpen || null,
			beforeClose: s.beforeClose || null,
			afterClose: s.afterClose || null,
			kurse: s.kurse || 'ja',
			alleThemenSelector: s.alleThemenSelector || '#allThemes',
			removeIconUrl: s.removeIconUrl || Environment.wwwUrl + '/fileadmin/img/trash_empty.png',
			enableTrashCan: s.enableTrashCan != undefined ? s.enableTrashCan : true,
			level: s.level || null
		});
	};
	
	// öffnen des Rubrikbaums
	RubrikTree.prototype.openTreeHandler = function(){
		var self = this;
		return function(event){
			event.preventDefault();
			var $target = $(event.currentTarget);
			var $rubrik_id = $target.extractClassInt('id');
			var $active = $($.rubriktree[self.id].treeElement).find('span.active');
			$active.each(function(i){
				var id = $(this).extractClassInt('id');
				if (id != $rubrik_id) $(this).removeClass('active');
			});
	
			if ($rubrik_id !== $.rubriktree[self.id].nichtEingrenzen){
				$($.rubriktree[self.id].treeElement).find('span.id-'+$rubrik_id).addClass('active');
			}
			$('.active').parents('ul').show();
			$('#'+$.rubriktree[self.id].rubriktreeDialogId).dialog('open');
		}
	}
	
	// aktiven Zweig im Baum hervorheben
	RubrikTree.prototype.highlightBranchHandler = function(){
		var self = this;
		return function(event){
			var $target = $(event.currentTarget);
			$target.closest($.rubriktree[self.id].treeElement).find('.active').removeClass('active');
			$target.addClass('active');
			var id = $target.extractClassInt('id');
			$('#'+$.rubriktree[self.id].treeRubriknrId).val(id);
			if($.rubriktree[self.id].level != null){
				if($(this).parents('ul').size() > $.rubriktree[self.id].level)
					$($('#'+$.rubriktree[self.id].rubriktreeDialogId).nextAll('.ui-dialog-buttonpane').find('button')[0]).show();
				else
					$($('#'+$.rubriktree[self.id].rubriktreeDialogId).nextAll('.ui-dialog-buttonpane').find('button')[0]).hide();
			};
		};
	};
	
	// Link, um im TreeDialog alle Themen auszuwählen
	RubrikTree.prototype.chooseAllRubriken = function(){
		var self = this;
		return function(event){
			event.preventDefault();
			$('#'+$.rubriktree[self.id].treeRubriknrId).val($.rubriktree[self.id].nichtEingrenzen);
			$target.closest($.rubriktree[self.id].treeElement).find('.active').removeClass('active');
			$($.rubriktree[self.id].treeElement + ' > li ul').hide();
			$($.rubriktree[self.id].treeElement).find('div.collapsable-hitarea')
				.removeClass('collapsable-hitarea')
				.addClass('expandable-hitarea');
		};
	};
	
	RubrikTree.prototype.insertCrumpHandler = function(){
		var self = this;
		return function(data){
			if(data.htmlContent == undefined) { return; }
			// Bereite Eintragen in die Crump vor
			var $hyperlink = $('<a></a>');
			var $rubrikcrump = $('#'+$.rubriktree[self.id].rubrikcrumpId);
			$hyperlink.css('font-weight', 'bold').attr('href','#');
			$rubrikcrump.html('');
			
			// Werden keine Eingrenzungen gemacht, wird das entsprechend kommuniziert :D
			if(data.htmlContent == $.rubriktree[self.id].nichtEingrenzen){
				var a = $hyperlink.clone();
				a.css('margin-left', '10px').attr('id',$.rubriktree[self.id].nichtEingrenzen)
					.text($.rubriktree[self.id].chooseText)
					.bind('click', self.openTreeHandler()).appendTo($rubrikcrump);
			}
			
			// Ansonsten wird die Breadcrump aufgebaut
			else{
				var deleteIconCss = {
					width: '16px',
					height: '16px',
					'background-image': 'url(' + $.rubriktree[self.id].removeIconUrl + ')',
					marginLeft: '5px',
					verticalAlign: 'top',
					display: 'inline-block'
				};
				if($.rubriktree[self.id].enableTrashCan == true){
					var deleteIcon = $hyperlink.clone().css(deleteIconCss);
					deleteIcon.attr({
						'href': '#',
						'title': 'aufheben'
					});
					deleteIcon.bind('click', function(event){
						event.preventDefault();
						$.rubriktree[self.id].rubrikcrump.find('#'+$.rubriktree[self.id].treeRubrikId).val($.rubriktree[self.id].nichtEingrenzen);
						if(typeof $.rubriktree[self.id].afterClose == 'function'){
							$.rubriktree[self.id].afterClose();
						}else{
							$rubrikcrump.html('');
							$hyperlink.clone().css('margin-left', '10px')
								.attr('id',$.rubriktree[self.id].nichtEingrenzen)
								.text($.rubriktree[self.id].chooseText)
								.bind('click', self.openTreeHandler()).appendTo($rubrikcrump);
						}
					});
					deleteIcon.appendTo($rubrikcrump);
				}
				var firstItem = true;
				$.each(data.htmlContent, function(i,n){
					var a = $hyperlink.clone();
					a.addClass('id-' + n.id).text(n.name);
					if(firstItem){
						firstItem = false;
						a.css('margin-left', '10px');
					} else {
						$rubrikcrump.append($.rubriktree[self.id].separator);
					}
					a.bind('click', self.openTreeHandler()).appendTo($rubrikcrump);
				});
			}
		};
	}
	
	$.fn.extend($.fn, {
		
		/**
		 * @param Object Einstellungen
		 */
		rubriktree: function(s){
			var htmlId = this[0].id;
			tree = new RubrikTree(htmlId);
			
			this.css({
				 fontWeight: 'bold',
				 marginLeft: '10px'
			}).bind('click', tree.openTreeHandler());
			
			if(s.chooseText == undefined){
				s.chooseText = $(this).text();
			}
			
			tree.initializeTree(s);
			$($.rubriktree[htmlId].treeElement).treeview({collapsed: true,unique: true});
			
			$.rubriktree[htmlId].rubrikcrump = tmpl_rubrikcrump.clone();
			$.rubriktree[htmlId].rubrikcrump.find('input[type=hidden]').attr('id', $.rubriktree[htmlId].treeRubrikId);
			$.rubriktree[htmlId].rubrikcrump.find('input[type=hidden]').attr('name', $.rubriktree[htmlId].treeRubrikId);
			$.rubriktree[htmlId].rubrikcrump.children('span').attr('id', $.rubriktree[htmlId].rubrikcrumpId);
			$.rubriktree[htmlId].rubrikcrump.find('label').attr('for', $.rubriktree[htmlId].rubrikcrumpId);
			this.after($.rubriktree[htmlId].rubrikcrump);
			this.appendTo($.rubriktree[htmlId].rubrikcrump.find('#'+$.rubriktree[htmlId].rubrikcrumpId));
			$.rubriktree[htmlId].rubrikcrump.find('label').text($.rubriktree[htmlId].labelText);
			$.rubriktree[htmlId].rubrikcrump.find('#'+$.rubriktree[htmlId].treeRubrikId).val($.rubriktree[htmlId].rubrikId);
			if($.rubriktree[htmlId].predefinedCrump != null){
				tree.insertCrumpHandler()({htmlContent:$.rubriktree[htmlId].predefinedCrump});
			}
			
			$(this).bind('click', tree.openTreeHandler());
			$($.rubriktree[htmlId].treeElement + ' span').bind('click', tree.highlightBranchHandler());
			$($.rubriktree[htmlId].alleThemenSelector).bind('click', tree.chooseAllRubriken());
			
			// Dialog für Rubrikenbaum
			$('#'+$.rubriktree[htmlId].rubriktreeDialogId).dialog({
				autoOpen: false,
				bgiframe: true,
				modal: true,
				width:600,
				overlay: {
					backgroundColor: '#000',
					opacity: 0.5
				},
				open: function(){
					if($.rubriktree[htmlId].beforeOpen != null)
						$.rubriktree[htmlId].beforeOpen(this);
					if($.rubriktree[htmlId].level != null){
						$($(this).nextAll('.ui-dialog-buttonpane').find('button')[0]).hide();
//						$($('#'+$.rubriktree[htmlId].rubriktreeDialogId).nextAll('.ui-dialog-buttonpane').find('button')[0]).show();
					};
					if($.browser.msie && $.browser.version < 7)
						$(':not(.ui-dialog) select').css('visibility', 'hidden');
					if($.rubriktree[htmlId].afterOpen != null)
						$.rubriktree[htmlId].afterOpen(this);
				},
				close: function(){
					if($.rubriktree[htmlId].beforeClose != null)
						$.rubriktree[htmlId].beforeClose(this);
					if($.browser.msie && $.browser.version < 7)
						$(':not(.ui-dialog) select').css('visibility', 'visible');
					if($.rubriktree[htmlId].afterClose != null)
						$.rubriktree[htmlId].afterClose(this);
				},
				buttons: {
					'OK': function() {
						tree.id = htmlId;
						var rubrikNr = $('#'+$.rubriktree[htmlId].treeRubriknrId).val();
						if(rubrikNr != ""){
							$.rubriktree[htmlId].rubrikcrump.find('#'+$.rubriktree[htmlId].treeRubrikId).val(rubrikNr);
							if ($.rubriktree[htmlId].kurse == 'ja') {
								$.ajax({
									dataType: 'json',
									url: Environment.kurseUrl + ($.rubriktree[htmlId].kurse=='ja' ? '/Kurssuche' : '/Referenten') + '/rubriktree',
									data: { rubrikId: $('#'+$.rubriktree[htmlId].treeRubriknrId).val(), kurse: 'ja' },
									success: tree.insertCrumpHandler()
								});
							} 
							else {
								$.ajax({
									dataType: 'json',
									url: Environment.kurseUrl + ($.rubriktree[htmlId].kurse=='ja' ? '/Kurssuche' : '/Referenten') + '/rubriktree',
									data: { rubrikId: $('#'+$.rubriktree[htmlId].treeRubriknrId).val(), kurse: 'nein' },
									success: tree.insertCrumpHandler()
								});
							}
						}else{
							$.rubriktree[htmlId].rubrikcrump.find('#'+$.rubriktree[htmlId].treeRubrikId).val($.rubriktree[htmlId].nichtEingrenzen);
						}
						$(this).dialog('close');
					},
					'abbrechen': function(){
						$(this).dialog('close');
					}
				}
			});
		}
	});
	
})(jQuery);

function aktivierePlzFelder($feldPlz, $templateSelectCity, $templateOptionCity, $templateSelectCityNoPlz, debug)
{
	if(debug == undefined) debug = false;
	var count = 0;
	function vaildatePLZ(event)
	{
		$feld = $(event.target);
		
		if ($feld.val().match(/[^0-9]/g))
		{
			posstart = $feld.caret().start-1;
			$feld.val($feld.val().replace(/[^0-9]/g,''));
			$feld.caret({start:  posstart,end: posstart});
		}
	};
	
	function stelleErgebnisPlzPruefungDar(result)
	{
		var $elm = $templateSelectCityNoPlz.clone();
		if (result.fehler){
		 	$('.citySelect').empty().append($elm);
			$('.citySelectMessage').text(result.fehler);
			return ;
		}
		switch(result.OrtListe.length){
			case 0:
			 	$('.citySelect').empty().append($elm);
			break;
			case 1:
		 	 	$select = $templateSelectCity.clone();
		 	 	$.each(result.OrtListe, function(index,ort)
		 	 	{
					$option = $templateOptionCity.clone();
					$option.text(ort.name);
					$option.attr('value', ort.loc_id);
					$option.attr('selected', true);
					$select.append($option);
				});
		 	 	$('.citySelect').append($select);
			break;
			default:
		 	 	$select = $templateSelectCity.clone();
		 	 	$.each(result.OrtListe, function(index,ort)
		 	 	{
					$option = $templateOptionCity.clone();
					$option.text(ort.name);
					$option.attr('value', ort.loc_id);
					$select.append($option);
				});
		 	 	$('.citySelect').append($select);
			break;
		}
	}
	
	function checkPLZ(event)
	{
 	 	$('.citySelect').empty();
	 	$feld = $feldPlz;
 	 	$feldPlz.ajaxrequest('load',{data: {plz:$feld.val()}});
	}
	
	function verzoegerCheckPLZ()
	{
		$feldPlz
			.timer('stop')
			.timer('start');
	}
	
	$feldPlz.timer
	({
		callback : checkPLZ, delay: 500, numTicks: 1 
	});
	
	$feldPlz.ajaxrequest
	({
		ajaxOptions : 
		{
			url : Environment.kurseUrl+'/Plz/pruefePlz', 
			success : stelleErgebnisPlzPruefungDar
		}
	});
	
	$feldPlz.bind('change keyup paste',vaildatePLZ);
	$feldPlz.bind('keyup',verzoegerCheckPLZ);
}


function Statistik()
{
	var self = this;
	
	var $statistik;
	var $statistikZusammenfassung;
	
	init();
	
	function init()
	{
		$statistik = $('#statistik');
		$statistikZusammenfassung = $('#statistikZusammenfassung');

		$statistik.bind('mouseover mouseout', statistikMouseHandler);

	}
	
	function statistikMouseHandler(event)
	{
		var $target = $(event.target);
		var $bar = $target.closest('.bar');
		if ($bar.length == 1)
		{
			var $bars = $bar.parents('.diagram:first').find('.bar');
			var index = $bars.length - 1 - $bars.index($bar);

			var $tds = $statistikZusammenfassung.find('tbody > tr:eq(' + index + ') > td');
			if (event.type == 'mouseover')
			{
				$tds.addClass('highlight');
			}
			else
			{
				$tds.removeClass('highlight');
			}
		}
	}
	
}


