Ajax.Responders.register({
	onCreate: function() {
        if ($('fe_waitImage') == undefined) {
            // beim ersten Request das Image erzeugen
            Element.insert(document.body, {top:'<div id="fe_waitImage" style="display:none"><img src="FEResources?resource=images/indicator_flower.gif" alt="wait"/></div>'});
        }
		$('fe_waitImage').show();
	},
	onComplete: function() {
		if (Ajax.activeRequestCount == 0) {
			// erst beim letzten fertigen Request, ausblenden
			$('fe_waitImage').hide();
		}
	}
});

function noop() {}

function fe_registerPingForm(formId, interval) {
	if (interval == undefined) {
		interval = 30;
	}

	new PeriodicalExecuter(function(pe) {
		new Ajax.Request("Submission", {
			method:'get',
			parameters: {action:"ping:"+formId},
			onSuccess:
				function(transport, json) {
					json = getJson(transport,  json);
					if (json != undefined && json != null && json["exception"] != null) {
						pe.stop(); // form existiert nicht mehr
						alert(json["exception"]);
					}
				},
			onFailure: function(transport) {
				alert(transport.status + ' - ' + transport.statusText);
			},
			onExeption: function(ex) {
				alert(ex);
			}
		});
	}, interval);
}


//function showTooltip(event, divId) {
function showMessageTooltip(event, elem) {
//	var divId = findComponentId(elem.id) + ":message_box";
	var boxDiv = findElement(elem, ":message_box");
	showTooltip(event, boxDiv);

//	var targetLeft = (Event.pointerX(event) + 8);
//	boxDiv.style.left = targetLeft + 'px';
//	boxDiv.style.top = (Event.pointerY(event) + 8) + 'px';
//
//	// muss aktiviert werden, um das Ergebnis mit page abfragen zu können
//	boxDiv.style.visibility = 'hidden';
//	boxDiv.style.display = 'block';
//
//	// Workaround für falsche Positionierung in zentrierten Layouts:
//	// Ziel mit dem Ergebnis vergleichen und korrigieren
//	var resultLeft = Position.page(boxDiv)[0];
//	if (resultLeft != targetLeft) {
//		boxDiv.style.left = eval(targetLeft - (resultLeft - targetLeft)) + 'px';
//	}
//	boxDiv.style.visibility = 'visible';
}

function hideMessageTooltip(elem) {
//	var divId = findComponentId(elem.id) + ":message_box";
//	hideTooltip(event, divId);
	var boxDiv = findElement(elem, ":message_box");
	boxDiv.style.display = 'none';
}

// Tooltips mit bekannter Div-Id
function showTooltip(event, divId) {
//	$(divId).style.width = 'auto';
	divId = $(divId);
	var targetLeft = (Event.pointerX(event) + 8);
	divId.style.left = targetLeft + 'px';
	divId.style.top = (Event.pointerY(event) + 8) + 'px';

	// muss aktiviert werden, um das Ergebnis mit page abfragen zu können
	divId.style.visibility = 'hidden';
	divId.style.display = 'block';

	// Workaround für falsche Positionierung in zentrierten Layouts:
	// Ziel mit dem Ergebnis vergleichen und korrigieren
	var resultLeft = Position.page(divId)[0];
	if (resultLeft != targetLeft) {
		divId.style.left = eval(targetLeft - (resultLeft - targetLeft)) + 'px';
	}
	divId.style.visibility = 'visible';
}

function hideTooltip(divId) {
	$(divId).style.display = 'none';
}

function openPopup(event, divId) {
	divId = $(divId);
	var targetLeft = (Event.pointerX(event) - 8);
	divId.style.left = targetLeft + 'px';
	divId.style.top = (Event.pointerY(event) - 8) + 'px';

	// muss aktiviert werden, um das Ergebnis mit page abfragen zu können
	divId.style.visibility = 'hidden';
	divId.style.display = 'block';

	// Workaround für falsche Positionierung in zentrierten Layouts:
	// Ziel mit dem Ergebnis vergleichen und korrigieren
	var resultLeft = Position.page(divId)[0];
	if (resultLeft != targetLeft) {
		divId.style.left = eval(targetLeft - (resultLeft - targetLeft)) + 'px';
	}
	divId.style.visibility = 'visible';
}

function showPopup(divId) {
	$(divId).style.display = 'block';

}

/* Schließt das Popup nur, wenn sich der Pointer bei diesem Event, außerhalb des Elements befindet */
function checkPopup(event, elem) {
	var pointerX = Event.pointerX(event);
	var pointerY = Event.pointerY(event);
	var elemOffset = Element.cumulativeOffset(elem);
	var elemX = elemOffset.left;
	var elemY = elemOffset.top;
	if (pointerX < elemX || pointerY < elemY
			|| pointerX >= elemX + Element.getWidth(elem)
			|| pointerY >= elemY + Element.getHeight(elem)) {
		elem.style.display = 'none';
	}
}

function hidePopup(divId) {
	$(divId).style.display = 'none';
}

function showButtons(elem) {
	var buttonsDiv = elem.getElementsByTagName("div")[1]; // erstes Div enthält die Buttons
	buttonsDiv.style.display = 'block';
}

function hideButtons(elem) {
	var buttonsDiv = elem.getElementsByTagName("div")[1]; // erstes Div enthält die Buttons
	buttonsDiv.style.display = 'none';
}

// Submit-Methoden

// Hilfsmethode, um aus einem anderen Fenster einen Wert zu verändern
function setElementValue(elementName, value) {
	if ($(elementName)) {
		$(elementName).value = value;
	}
	else {
		alert("found no element " + elementName);
	}
}
function getElementValue(elementName) {
	return $(elementName).value;
}

//function setValueDelayed(elementName, value) {
//	window.setTimeout("setValue(elementName, value)", 1000);
//}

// setzt den Wert eines html-Feldes und fuehrt ggf. das onchange-event aus
// Statt Id des Feldes kann auch ein beliebiges Child-Element des Renderers übergeben werden.
// Dann wird über die Parents nach der Id des Renderers gesucht
function changeValue(fieldId, value) {
	var inputField = $(fieldId);

	inputField.value = value;
	doOnChange(inputField);
}

function doOnChange(inputField) {
	if (typeof inputField.onchange == "function") {
		inputField.onchange();
	}
}

function deleteValue(elem, suffix) {
	elem = findComponentId(elem);
	if (suffix) {
		if (!suffix.startsWith(":")) {
			suffix = ":" + suffix;
		}
		elem = $(elem + suffix);
	}
	changeValue(elem, '');
}

/* Sendet den Wert für eine einzelne Komponente direkt */
function sendValue(elem, value, fieldSuffix) {
	if (value != undefined) {
		// Sonderbehandlung fuer String momentan nicth erforderlich, da alles über this laeuft
//		var  componentId = elem;
//		if (!Object.isString(elem)) {
//			componentId = findComponentId(elem);
//		}
		var componentId = elem.id;
		// erst direkt mit der Id des Elements versuchen, da sie ein Feld einer MultiField-Komponente sein kann
		if (!componentId) {
			// dann die Id in uebergeordneten Elementen suchen 
			componentId = findComponentId(elem);
		}
		if (fieldSuffix) {
			if (!fieldSuffix.startsWith(":")) {
				fieldSuffix = ":" + fieldSuffix;
			}
			if (!componentId.endsWith(fieldSuffix)) {
				componentId += fieldSuffix;
			}
		}
		var pair = { };
		pair[componentId] = value;
		ajaxDoSubmit(Object.toQueryString(pair));
	}
}

/* Sendet die Werte */
function ajaxDoSubmit(values, componentId) {
	new Ajax.Request('Submission', {
		method:'post',
		parameters: values,
		onSuccess: // handleResponse,
			function(transport, json) {
				json = getJson(transport,  json);
				if (isValidResponse(json)) {
					handleResponse(json, false);
				}
			},
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
		},
		onExeption: function(ex) {
			alert(ex);
		},
		onComplete: function(transport) {
			if (200 != transport.status) {
				alert("Could not send data for component\n" + componentId + "\nThis may result in inconsistent form data.");
			}
		}
	});
	if (componentId) {
		var popupDiv = $(componentId + ":renderer_popup");
		if (popupDiv && !Element.hasClassName(popupDiv, "fe_onchange_keep_open")) {
			hidePopup(popupDiv);
		}
	}
}

/* Sendet die Werte einzelner Felder */
function ajaxSubmit(field, field2) {
	var serialized = Form.Element.serialize(field, field2);
	if (serialized == '') {
		// Z.B. bei Listboxen wird bei komplett leerer Auswahl ein leerer Submit-String erzeugt.
		// Um im Submit den Namen der Komponente senden zu können,
		// ist eine Sonderbehandlung über einen speziellen Feldnamen notwendig.
		sendValue(field, 'true', ':empty');
	}
	else {
		var componentId = findComponentId(field);
		ajaxDoSubmit(serialized, componentId);
	}
}

var submitRunning = false;

function ajaxSubmitForm(form, submissionName, nextpage, validation) {
	if (uploadQueue.length - finishedUploadCount > 0) {
		alert("upload running, plaese wait");
		return;
	}
	if (submitRunning) {
		return;
	}
	submitRunning = true;

	form['submit_by'].value = submissionName;
	new Ajax.Request(validation == undefined || validation?form.action:"SaveForm", {
		method:'post',
		parameters: Form.serialize(form),
		onSuccess: function(transport, json) {
			json = getJson(transport,  json);
			if (isValidResponse(json) && handleResponse(json, true, form['form_name'].value)) {
				var saveException = json["save_exception"];
				var gotoNextPage = true;
				if (saveException != null) {
					alert(saveException);
					gotoNextPage = json["keep_page"] != "true";
				}
				if (gotoNextPage) {
					document.location.href = getTarget(json, nextpage);
				}
			}
			submitRunning = false;
		},
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
			submitRunning = false;
		},
		onExeption: function(ex) {
			alert(ex);
			submitRunning = false;
		}
	});
}

function getJson(transport, json) {
	if (json) {
		return json;
	}
	else if (transport.responseText && transport.responseText.startsWith('{')) {
		// bei prototype 1.6 hat das sonst zu einem Fehler geführt, wenn der x-json Header leer war,
		// und im transport kein json steckte, sondern html-Code
		return eval('(' + transport.responseText + ')');
	}
	else {
		return null;
	}
}

function isValidResponse(json) {
	if (json == undefined || json == null) {
		return false;
	}
//	json = getJson(transport, json);
	var exception = json["exception"];
	if (exception != null) {
		alert(exception);
		return false;
	}
	return true;
}

function handleResponse(result, submitAll, formName) {
	try {
//		var result = getJson(transport, json);
		var keys = Object.keys(result);
		var errorOccured = false;
		for (var i = 0; i < keys.length; i++) {
			var cName = keys[i]; // Name der Komponente
			if (!cName.startsWith("fe:")) {
				// nur Komponentennamen berücksichtigen
				continue;
			}
			var fieldInfo = result[cName];
			if (!submitAll) {
				updateParentInvalidStates(cName, false);
			}
			if (handleValidationMessage(cName, fieldInfo)) {
//				ajaxAlert("errorOccured! " + key + ": " + fieldInfo);
				errorOccured = true;
			}
		}

		// beim Formular-Submit und wenn sonst kein Fehler auftrat ggf. Bestätigungs-Dialog anzeigen
		if (submitAll && !errorOccured) {
			var confirmMsg = result["confirm_message"];
			if (confirmMsg != null) {
				var confirmed = confirm(confirmMsg);
				errorOccured = !confirmed;
				if (confirmed && result["write_connector"] == "true") {
					// Wenn mit write_connector geantwortet wird,
					// per Ajax write-Request senden
					new Ajax.Request("Submission", {
						asynchronous:false,
						method:'post',
						// aus dem Namen der Komponente wird beim Request nur die Id des Formulars ausgewertet
						parameters: {action:"saveSubmission",form_name:formName},
						onSuccess: function(transport, json) {
							json = getJson(transport, json);
							// eventuelle Exceptions per json an result anhängen
							result["save_exception"] = json["save_exception"];
							result["keep_page"] = json["keep_page"];
						},
						onFailure: function(transport) { alert(transport.status + ' - ' + transport.statusText); },
						onExeption: function(ex) { alert(ex); }
					});
				}
			}
		}

		if (!submitAll || errorOccured) {
			// bei Submits einzelner Felder oder wenn das Formular wegen Fehlern
			// nicht verlassen werden kann, die Renderer aktualisieren
			for (i = 0; i < keys.length; i++) {
				cName = keys[i]; // Name der Komponente
				if (cName.startsWith("fe:")) {
					handleRenderRequest(cName, result[cName]);
				}
			}
		}

		return !errorOccured;
	}
	catch (ex) {
		alert(ex);
	}
	return false;
}

function handleRenderRequest(cName, fieldInfo) {
	var containerDiv = $(cName + ':container');
	var render = fieldInfo['render'];
	if (render) {
		// gesamte Komponente neu rendern
		renderComponent(render, containerDiv);
	}

	var doUpdateFields = fieldInfo['updateFields'];
	if (doUpdateFields) {
		var updateIds = Object.keys(doUpdateFields);
		for (var i = 0; i < updateIds.length; i++) {
			var updateId = updateIds[i];
			var fieldName = cName;
//			if (updateId > 0) {
				fieldName = fieldName + ':' + updateId;
//			}

			var updateData = doUpdateFields[updateId];
			if ('' == updateData) {
				updateAction(fieldName, "renderFields");
				//renderFields(cName, updateId, $(fieldName + ':renderer'))
			}
			else if (updateData.startsWith("v:")) {
				setElementValue(fieldName, updateData.substring(2));
			}
			else if (updateData.startsWith("r:")) {
				// xhtml-Inhalt direkt austauschen
				var content = updateData.substring(2);
				$(fieldName + ':renderer').update(content);
			}
			else if (updateData.startsWith("e:")) {
				// TODO JS per eval ausführen
			}
			// weitere Methoden...
		}
	}

	handleClasses(containerDiv, fieldInfo);
}

var allowedHideEffects = ["BlindUp", "SlideUp", "Fade", "Puff"];
var allowedShowEffects = ["BlindDown", "SlideDown", "Appear"];

// Ändert ausgehend von den Information die per JSON geliefert wurden die Klasse eines Elements
function handleClasses(element, fieldInfo) {
	var effectName = fieldInfo['effect'];
	var addClass = fieldInfo['addclass'];
	if (addClass) {
		if (Object.isString(addClass)) {
			if (addClass == 'fe_cattr_hidden' && effectName != undefined) {
				if (allowedHideEffects.indexOf(effectName) < 0) {
					effectName = allowedHideEffects[0];
				}
				Effect[effectName](element, {duration:0.7});
//				Effect.BlindUp(element, {duration:0.7});

//					afterFinish:function(){Element.addClassName(element, addClass)}});
				// Klasse braucht nicht mehr hinzugefügt werden, da durch scriptaculous ausgeblendet
				// _hidden könnte aber eigentlich auch immer hinzugefügt werden, um es anderweitig per css auszuwerten
			}
			else {
				// dann wird es aber sofort ausgeblendet, da der Effekt asynchron gestartet wird
				Element.addClassName(element, addClass);
			}
		}
		else {
			// als Hash {className:ElementId}
			var classes = Object.keys(addClass);
			for (var i = 0; i < classes.length; i++) {
				var className = classes[i];
				// rekursiver Aufruf, wegen der erweiterten Behandlung für bestimmt Classes
				handleClasses($(addClass[className]), {'addclass':className});
			}
		}
	}
	var removeClass = fieldInfo['removeclass'];
	if (removeClass) {
		if (Object.isString(removeClass)) {
			Element.removeClassName(element, removeClass);
			if (removeClass == 'fe_cattr_hidden' && effectName != undefined) {
				if (allowedShowEffects.indexOf(effectName) < 0) {
					effectName = allowedShowEffects[0];
				}
				Effect[effectName](element, {duration:0.7,
					beforeStart:function(){Element.removeClassName(element, removeClass);}});
//				Effect.BlindDown(element, {duration:0.7,
//					beforeStart:function(){Element.removeClassName(element, removeClass);}});
			}
			else {
				Element.removeClassName(element, removeClass);
			}
		}
		else {
			// als Hash
			classes = Object.keys(addClass);
			for (i = 0; i < classes.length; i++) {
				className = classes[i];
				// rekursiver Aufruf, wegen der erweiterten Behandlung für bestimmt Classes
				handleClasses($(addClass[className]), {'removeclass':className});
//				Element.removeClassName($(addClass[className]), className);
			}
		}
	}
}

function handleValidationMessage(key, fieldInfo) {
	clearMessage(key, key + ':container', 'fe_invalid_value');

/*
	var contentDiv = $(key + ':content');
	if (contentDiv && contentDiv.hasClassName('fe_c_content')) {
		// nur in richtigen Komponenten die einzelenen Renderer bereinigen (nicht für Gruppen und Repeats)
		var rendererDivs = contentDiv.immediateDescendants();
		for (var i = 0; i < rendererDivs.length; i++) {
			var renderer = rendererDivs[i];
			clearMessage(key, renderer);
		}
	}
*/

	var invalidChilds = fieldInfo.invalidChilds;
	if (invalidChilds != null) {
		if (invalidChilds == 'true') {
			Element.addClassName(key + ':container', 'fe_invalid_childs');
		}
		else {
			Element.removeClassName(key + ':container', 'fe_invalid_childs');			
		}
	}

	var validation = fieldInfo.validation;
	var conversion = fieldInfo.conversion;
	if (validation && validation == 'error') {
		setMessage(key, fieldInfo.message, key + ':container', 'fe_invalid_value');
//		updateParentInvalidStates(key, true);
		return true;
	}
	else if (conversion) {
		if (conversion == 'error') {
			setMessage(key, fieldInfo.message, fieldInfo.element, 'fe_invalid_input');
//		updateParentInvalidStates(key, true);
			return true;
		}
		else {
			// alle Renderer in dieser Komponente zurücksetzen
			var contentDiv = $(key + ':content');
			if (contentDiv && contentDiv.hasClassName('fe_c_content')) {
				// nur in richtigen Komponenten die einzelenen Renderer bereinigen (nicht für Gruppen und Repeats)
				var rendererDivs = contentDiv.immediateDescendants();
				for (var i = 0; i < rendererDivs.length; i++) {
					var renderer = rendererDivs[i];
					if (renderer.id) {
						// die Spacer überspringen
						clearMessage(key, renderer.id, 'fe_invalid_input');
					}
				}
			}
			return false;
		}
	}
	else {
		return false;
	}
}

function updateParentInvalidStates(key, invalid) {
	var lastDot = key.lastIndexOf(".");
	key = key.substring(0, lastDot);
	if (key.lastIndexOf(".") > -1) {
		if (invalid) {
			Element.addClassName(key + ':container', 'fe_invalid_childs');
		}
		else {
			Element.removeClassName(key + ':container', 'fe_invalid_childs');
		}
		updateParentInvalidStates(key, invalid);
	}
}

var tooltips = new Array();

function setMessage(componentId, message, highlightDiv, className) {
	var messageBoxId = componentId + ':message_box';
	var messageField = $(messageBoxId);
	var messageLabel = $(componentId + ':message_label');
	if (!messageField) {
		if (message == '') {
			// ohne div und ohne Message-Text bleibt alles wie bisher
			return;
		}
		messageField = createMessageBox(componentId);
	}

	if (messageField) {
		hideMessageTooltip(messageBoxId);
		messageField.update(message.replace(/\n/g, "<br />"));
	}
	if (message != '') {
		if (messageLabel) {
			messageLabel.style.display='block';
		}
		else {
			// Tooltips direkt über den betreffenden Feldern anzeigen,
			// statt einem separaten Bild oder Text
			Element.addClassName(highlightDiv, "fe_error_tooltip");
			var tooltip = {
				mOver: function(event){showMessageTooltip(event, this.boxId);},
				mOut: function(event){hideMessageTooltip(this.boxId);}
			};
			tooltip.boxId = messageBoxId;
			tooltip.highlightId = highlightDiv;
			tooltip.fOver = tooltip.mOver.bindAsEventListener(tooltip);
			tooltip.fOut = tooltip.mOut.bindAsEventListener(tooltip);
			tooltips[componentId] = tooltip;
			$(highlightDiv).observe('mouseover', tooltip.fOver);
			$(highlightDiv).observe('mouseout', tooltip.fOut);
		}
		Element.addClassName(highlightDiv, className);
	}
	else {
		if (messageLabel) {
			messageLabel.style.display='none';
		}
		else {
			Element.removeClassName(highlightDiv, "fe_error_tooltip");
			var tooltip = tooltips[componentId];
			tooltips[componentId] = null;
			if (tooltip != null) {
				// die highlightId aus dem gecachten Objekt verwenden, da die übergebene
				// bei einem allgemeinen entfernen der Tooltips nicht stimmen muss
				$(tooltip.highlightId).stopObserving('mouseover', tooltip.fOver);
				$(tooltip.highlightId).stopObserving('mouseout', tooltip.fOut);
			}
		}
		Element.removeClassName(highlightDiv, className);
	}
}

function createMessageBox(componentId) {
	// toolTip-Element dynmaisch anlegen
	if (componentId == "c_unknown") {
		return null;
	}
	var messageDiv = new Element('div', {'id':componentId + ':message_box'});
	messageDiv.addClassName('fe_message');
	Element.insert($(componentId + ':container'), messageDiv);
	return messageDiv;
}

function clearMessage(fieldId, elementId, className) {
	setMessage(fieldId, '', elementId, className);
}

// File-Upload

var uploadQueue = [];
var finishedUploadCount = 0;

function queueUpload(field) {
	var componentId = field.name;
	$(componentId + '_progress').style.display = 'block';
	$(componentId + '_progress_text').innerHTML = 'waiting: 0%';

	// Im IE und Firefox sind scheinbar nur 2 Requests pro Seite gleichzeitig möglich.
	// Deshalb wird eine Warteschlange für die Submits verwaltet.
	uploadQueue[uploadQueue.length] = field;
	if (uploadQueue.length - finishedUploadCount == 1) {
		// sofort hochladen
		runProgessBar(field.name);
		field.form.submit();
	}
	field.disabled = true;
	// anderenfalls wird es nur an die Liste gehängt
}

function runProgessBar(fieldName) {
	// nur einen Request für alle Uploads laufen lassen und eine Liste aller
	// Progressbars zurückgeben.
	new PeriodicalExecuter(function(pe) {
		new Ajax.Request("Submission", {
			method:'post',
			// aus dem Namen der Komponente wird beim Request nur die Id des Formulars ausgewertet
			parameters: {action:"uploadState",uploadId:fieldName},
			onSuccess: function(transport, json) {
				json = getJson(transport, json);

				if (!isValidResponse(json)) {
					// Executer abbrechen?
					finishedUploadCount++;
					if (uploadQueue.length <= finishedUploadCount) {
						pe.stop(); // das war der letzte
					}
					return;
				}

				var keys = Object.keys(json);
				var errorOccured = false;
				for (var i = 0; i < keys.length; i++) {
					// der Key ist der vollständige Name der Komponente (also die Element-Id)
					var key = keys[i];
					var fieldInfo = json[key];
					var renderer = fieldInfo["renderer"];
					var progressPercent = fieldInfo["percent"];
					if (progressPercent != null) {
						$(renderer + ':file_progress_text').innerHTML = fieldInfo["message"] + " " + progressPercent + '%';
						$(renderer + ':file_progress_content').style.width = parseInt(progressPercent * 2.5) + 'px';
					}
					else {
						// ohne Prozent-Angabe im JSON-Container ist der Upload beendet
						$(renderer + ':file_progress').style.display = 'none';
						$(renderer + ':file_progress_content').style.width = '0';
						finishedUploadCount++;
						if (fieldInfo["error"] != null) {
							// nach einem Fehler immer komplett neu rendern,
							// da Firefox3 auf MacOS die Komponente nach einem gescheiterten Upload einer app-Datei blockiert
							updateAction(renderer, "renderFields");
							alert("Error: " + fieldInfo["error"]);
						}
						else {
							var previewUpdate = fieldInfo["preview"];
							if (previewUpdate != null) {
								// direktes Update derzeit nur noch für die Anzeige von Fehlern
								$(key + ":file_preview").update();
							}
							handleResponse(json, false);
						}


						if (uploadQueue.length = finishedUploadCount) {
							// das war der letzte
							pe.stop();
						}
						else {
							// den nächsten submitten
							var field = uploadQueue[finishedUploadCount];
							field.disabled = false; // kurz freischalten, um submitten zu können
							field.form.submit();
							field.disabled = true;
						}
					}
				}
			},
			onFailure: function(transport) {
				alert(transport.status + ' - ' + transport.statusText);
			},
			onExeption: function(ex) {
				alert(ex);
			}
		});
	}, 1);
}

// TreeSelectRenderer

function foldNode(liElement, node) {
	liElement = liElement.parentNode;
	updateFragment(liElement, node);
	if (Element.hasClassName(liElement, "fe_node_open")) {
		Element.removeClassName(liElement, "fe_node_open");
		Element.addClassName(liElement, "fe_node_closed");
		// Entfernen des ul-Elements würde nicht ausreichen, da das Schließen
		// per Request dem TreeState auf dem Server mitgeteilt werden muss.
	}
	else {
		Element.removeClassName(liElement, "fe_node_closed");
		Element.addClassName(liElement, "fe_node_open");
	//	var componentId = getNextParentElementId(element);
	}
}

function setNode(element, value) {
	var allowDeselect = true;
	if (!Object.isElement(element)) {
		// Es wird ein Event uebergeben, wenn die Ctrl-Taste beim Deselektieren gedrueckt sein muss
		allowDeselect = element.ctrlKey;
		element = Event.element(element);
	}
	// else Ein Element wird uebergeben, wenn ein selektiertes Element beim erneuten Klicken immer deselektiert wird
//	var componentId = null;
	var newInPath = new Array();
	var deselected = false;
	while (true) {
		element = element.parentNode;
		if (element.tagName == "LI") {
			// alle LI-Elemente im neuen Pfad markieren und merken
//			alert("has selected class = " + Element.hasClassName(element, "fe_node_selected"));
			// Unterscheidung zwischen node_selected und node_in_path
			if (newInPath.length == 0 && allowDeselect &&  Element.hasClassName(element, "fe_node_selected")) {
				// wenn ctrl gedrückt wurde und das Element schon selektiert war,
				// wird es deselektiert und der Wert gelöscht
				value = "";
				deselected = true;
			}
			else if (!deselected) {
				if (newInPath.length == 0) {
					// das erste Element wird als selected markiert, wenn nicht bereits ein anderes deselektiert wurde
					Element.addClassName(element, "fe_node_selected");
				}
				else {
					// falls der alte selektierte Knoten im Pfad des neuen liegt, die Klasse immer im neuen Pfad entfernen
					Element.removeClassName(element, "fe_node_selected");
				}
				newInPath.push(element);
				Element.addClassName(element, "fe_node_in_path");
			}
		}
		else {
			// die zwischenzeitlichen ul-Elemente haben keine Id - erst der Renderer oder das popup-Div
			if (Element.readAttribute(element, 'id')) {
				break;
			}
		}
	}
//	if (componentId.endsWith(":renderer")) {
//		componentId = componentId.substring(0, componentId.indexOf(":renderer"));
//	}
	var componentId = findComponentId(element);
	// Wert im Input-Feld änmdern und damit senden
	changeValue(componentId, value);

	var oldInPath = Element.select(element, '.fe_node_in_path');
	oldInPath.each(function(item) {
		// alle Markierungen der Elemente des alten Pfades entfernen, außer die auch im neuen sind 
		if (newInPath.indexOf(item) < 0) {
			Element.removeClassName(item, "fe_node_in_path");
		}
	});
	var oldSelected = Element.select(element, '.fe_node_selected');
	oldSelected.each(function(item) {
		if (newInPath.indexOf(item) < 0) {
			Element.removeClassName(item, "fe_node_selected");
		}
	});
}

function msetNode(element, value) {
	// TODO für Mehrfachauswahl im Baum Angeklickten Wert hinzufügen oder entfernen
	// und an den Server senden
	// TODO bei Mehrfachauswahl nicht die Pfade der selektierten Elemente markieren
	var toggleMode = true;
	if (!Object.isElement(element)) {
		// Es wird ein Event uebergeben, wenn die Ctrl-Taste beim Deselektieren gedrueckt sein muss
		toggleMode = element.ctrlKey;
		element = Event.element(element);
	}
	// else Ein Element wird uebergeben, wenn immer der toggleMode gilt
	element = getNextParentElementByName(element, "LI");
	var fieldId = findComponentId(element) + ":";
	if (toggleMode) {
		fieldId += "toggle";
		changeValue(fieldId, value);
		if (Element.hasClassName(element, "fe_node_selected")) {
			Element.removeClassName(element, "fe_node_selected");
		}
		else {
			Element.addClassName(element, "fe_node_selected");
		}
	}
	else {
		var rootElement = $(fieldId + "renderer");
		fieldId += "set";
		var oldSelected = Element.select(rootElement, '.fe_node_selected');
		if ((!Element.hasClassName(element, "fe_node_selected") || oldSelected.length > 1)) {
			// wenn ein nicht selektiertes angeklickt wird, oder mehr als nur eins selektiet war
			// dann nur das angeklickte selektieren
			changeValue(fieldId, value);
			// alle anderen entfernen
			oldSelected.each(function(item) {
				Element.removeClassName(item, "fe_node_selected");
			});
			Element.addClassName(element, "fe_node_selected");
		}
	}
	// hinterher wieder leeren, damit es nicht nochmal beim Submit gesendet wird
	setElementValue(fieldId, "");
}

// Sucht nach dem naechsten uebergeordneten Element mit dem angegebenen Namen.
// Achtung: der Name muss upperCase uebergeben werden
function getNextParentElementByName(element, name) {
	element = element.parentNode;
	while (element && name != element.tagName) {
		element = element.parentNode;
	}
	return element;
}

function getNextParentElementId(element) {
	var componentId = null;
	// naechstes Component-Element mit einer id suchen
	while (!componentId) {
		element = element.parentNode;
		componentId = Element.readAttribute(element, 'id');
	}
	return componentId;
}


function findElement(elem, idSuffix) {
	if (Object.isString(elem)) {
		elem = $(elem);
	}
	if (!Object.isElement(elem)) {
		return null;
	}
	if (elem.id != null && elem.id.endsWith(idSuffix)) {
		// bereits das richtige Element
		return elem;
	}

	var elemId = getNextParentElementId(elem);
	var index = elemId.lastIndexOf(':');
	elemId = elemId.substring(0, index) + idSuffix;
	return $(elemId);
}

/* Sucht im aktuellen und den übergeordneten Elementen nach der Id und entfernt deren :Suffix */
function findComponentId(elem) {
	if (Object.isString(elem)) {
		elem = $(elem);
	}
	if (!Object.isElement(elem)) {
		return null;
	}
	var elemId = elem.id;
	if (!elemId) {
		elemId = getNextParentElementId(elem);
	}

	var index = elemId.lastIndexOf(':');
	if ('.0123456789'.indexOf(elemId.charAt(index + 1)) > -1) {
		// Das ist bereits die Id ohne Feld-Zusatz (. = Anfang des Namens; 0-9 = RendererId
		return elemId;
	}
	return elemId.substring(0, index);
}

// Optionen

function removeOption(elem, value) {
	var fieldId = findComponentId(elem) + ":remove";
	changeValue(fieldId, value);
	// TODO nur bei Erfolgsbestätigung vom Server! Aber wie?
	Element.remove(getNextParentElementByName(elem, "LI"));
	// hidden-Feld hinterher wieder leeren, damit es nicht nochmal beim Submit gesendet wird
	setElementValue(fieldId, "");
}

function addOption(elem, value) {
	var fieldId = findComponentId(elem) + ":add";
	changeValue(fieldId, value);
	// TODO nur bei Erfolgsbestätigung vom Server! Aber wie?
	Element.remove(elem);
	// hidden-Feld hinterher wieder leeren, damit es nicht nochmal beim Submit gesendet wird
	setElementValue(fieldId, "");
}


// Repeats und Gruppen

// aktuell selektierte Repeat-Zeilen (RepeatId:zeile)
var selectedRows = {};

function getRowElement(repeatId, row) {
	return $(repeatId + '.' + row + ':container');
}

function selectRow(event, elem) {
	var rowId = findComponentId(elem);
	selectRowById(rowId, event && event.ctrlKey);
}

function selectRowById(rowId, allowDeselect) {
	var index = rowId.lastIndexOf('.');
	var row = rowId.substring(index + 1);
	var repeatId = rowId.substring(0, index);
	var oldRow = selectedRows[repeatId];
	selectedRows[repeatId] = row;

	if (row != oldRow) {
		if (oldRow) {
			Element.removeClassName(getRowElement(repeatId, oldRow), "fe_selected_repeat_row");
		}
		Element.addClassName(getRowElement(repeatId, row), "fe_selected_repeat_row");
	}
	else if (allowDeselect) {
		// deselektieren, wenn auch Ctrl.
		Element.removeClassName(getRowElement(repeatId, row), "fe_selected_repeat_row");
		selectedRows[repeatId] = null;
	}
}

function deleteRow(elem) {
	var repeatId = findComponentId(elem);
	var row = selectedRows[repeatId];
	selectedRows[repeatId] = null;
	if (!row) {
		return;
	}
	new Ajax.Request("FEResources", {
		method:'get',
		parameters: {action:"deleteRow",'componentId':repeatId,'row':row},
		onSuccess: function(transport, json) {
			json = getJson(transport,  json);
			if (isValidResponse(json)) {
				Element.remove(getRowElement(repeatId, row));
				handleResponse(json);
			}
		},
		onFailure: function(transport) { alert(transport.status + ' - ' + transport.statusText); },
		onExeption: function(ex) { alert(ex); }
	});
}

function insertRow(elem, before) {
	var repeatId = findComponentId(elem);
	var selectedRow = selectedRows[repeatId];
	// wenn after, muss das Servlet beim Einfügen noch eine Zeile weiter nach hinten
	// selectedRow kann nicht einfach um 1 erhöht werden, dass es einen Unterschied
	// zwischen serverseitiger und clientseitiger Zeilen-Id gibt.
	var offset = before ? 0 : 1;
	if (!selectedRow) {
		selectedRow = -1;
	}
	var repeatContent = $(repeatId + ':content');
	var tables = repeatContent.getElementsByTagName('table');
	// in tabellen bis zum tbody vorarbeiten
	if (tables.length > 0) {
		// wurde als Tabelle gerendert
		var table = tables[0];
		repeatContent = table.getElementsByTagName('tbody')[0];
	}

	new Ajax.Request("FEResources", {
		method:'get',
		parameters: {action:"newRow",'componentId':repeatId,'selectedRow':selectedRow,'offset':offset},
		onSuccess: function(transport, json) {
			json = getJson(transport,  json);
			if (!isValidResponse(json)) {
				return;
			}
			var jsonRepeat = json[repeatId];
//			var responseText = transport.responseText;
			var responseText = jsonRepeat["newRow"];
			if (selectedRow >= 0) {
				if (before) {
					Element.insert(getRowElement(repeatId, selectedRow), {before:responseText});
				}
				else {
					Element.insert(getRowElement(repeatId, selectedRow), {after:responseText});
				}
			}
			else {
				if (before) {
					Element.insert(repeatContent, {top:responseText});
				}
				else {
					Element.insert(repeatContent, {bottom:responseText});
				}
			}

			handleResponse(json);
			var focusId = jsonRepeat["focus"];
			self.focus();
			$(focusId).focus();
			selectRowById(jsonRepeat["newRowId"], false);
		},
		onFailure: function(transport) { alert(transport.status + ' - ' + transport.statusText); },
		onExeption: function(ex) { alert(ex); }
	});
}

function moveRow(elem, direction) {
	var repeatId = findComponentId(elem);
	var rowId = selectedRows[repeatId];
	if (!rowId) {
		return;
	}
	new Ajax.Request("FEResources", {
		method:'get',
		parameters: {action:"moveRow",'componentId':repeatId,'row':rowId,'dir':direction},
		onSuccess: function(transport, json) {
			json = getJson(transport,  json);
			if (!isValidResponse(json)) {
				return;
			}
			// Ziel-Zeile aus JSON-Response ermitteln
			var fieldInfo = json[repeatId];
			if (!fieldInfo) {
				return;
			}
			var removeRow = getRowElement(repeatId, rowId);
			var otherRow = $(fieldInfo['otherRow'] + ':container');
			// erstmal entfernen
			Element.remove(removeRow);
			// dann neu einfuegen
			if (direction < 0) {
				Element.insert(otherRow, {before:removeRow});
			}
			else {
				Element.insert(otherRow, {after:removeRow});
			}
			handleResponse(json);
		},
		onFailure: function(transport) { alert(transport.status + ' - ' + transport.statusText); },
		onExeption: function(ex) { alert(ex); }
	});
}

function renderComponent(componentId, replaceDiv) {
//	ajaxAlert("renderCompoent " + componentId + " : " + replaceDiv.id);
	// Hier muss auch der EffectUpdater verwendet werden (statt Updater).
	// Mit dem normalen Updater wird JavaScript im Html-Code nicht ausgeführt,
	// was aber z.B. für die Initialisierung der Slider notwendig ist.
	new Ajax.EffectUpdater(replaceDiv, "FEResources", {
		method:'post',
		parameters: {action:"renderComponent",componentId:componentId},
//		insertion: Insertion.Bottom,
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
		},
		onExeption: function(ex) {
			alert(ex);
		}
	});
}
// TODO kann weg?
function renderFields(componentId, renderId, replaceDiv) {
	new Ajax.EffectUpdater(replaceDiv, "FEResources", {
		method:'post',
		parameters: {action:"renderFields",componentId:componentId}, //,renderId:renderId},
//		insertion: Insertion.Bottom,
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
		},
		onExeption: function(ex) {
			alert(ex);
		}
	});
}

function createPageSlider(sliderElem, start, max) {
//	sliderElem = $(sliderElem);
	var sliderHandle = sliderElem.down('.fe_slider_handle');
    new Control.Slider(sliderHandle, sliderElem, {
		range: $R(1, max),
	   values: $R(1, max),
      sliderValue: start,
      onSlide: function(value) {
	      sliderHandle.update(value);
      },
      onChange: function(value) {
	      sliderHandle.update(value);
	      updateAction(sliderElem, 'gotoPage', value);
      }
    });
}

// ermittelt anhand der rendererId das Div-Element,
// in dem der Renderer seine Elemente erzeugen/aktualisieren darf
function getRenderArea(rendererId) {
//	ajaxAlert("getRenderArea: " + rendererId);
	var div = $(rendererId + ":renderer_popup");
	if (div) {
//		ajaxAlert("found popup: " + div);
		return div;
	}
//	ajaxAlert("found standard renderer " + $(rendererId + ":renderer"));
	return $(rendererId + ":renderer");
}

function updateAction(rendererId, actionName, param) {
	if (!Object.isString(rendererId)) {
		rendererId = findComponentId(rendererId);
	}

	new Ajax.EffectUpdater(getRenderArea(rendererId), "FEResources", {
		method:'post',
		parameters: {action:actionName,componentId:rendererId,parameter:param},
//		insertion: Insertion.Bottom,
		onComplete: function(response, json) {
			// der EffektUpdater übergibt ein JSON-Objekt, wenn im response json-Code enthalten war
			if (isValidResponse(json)) {
				handleResponse(json, false);				
			}
		},
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
		},
		onExeption: function(ex) {
			alert(ex);
		}
	});
}

function updateFragment(elem, fName, componentId) {
	if (Object.isString(elem)) {
		if (!componentId) {
			componentId = elem;
		}
		elem = $(elem);
	}
	else {
		if (!componentId) {
			componentId = getNextParentElementId(elem);
		}
	}
	new Ajax.Updater(elem, "FEResources", {
		// post, da sonst Sonderzeichen falsch kodiert werden
		method:'post',
		parameters: {action:"updateFragment",componentId:componentId,fragmentName:fName},
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
		},
		onExeption: function(ex) {
			alert(ex);
		}
	});
}

function ajaxResetForm(formId) {
	new Ajax.Updater($('fe:' + formId), "FEResources", {
		method:'post',
		parameters: {action:"resetForm",formId:formId},
		onFailure: function(transport) {
			alert(transport.status + ' - ' + transport.statusText);
			return false;
		},
		onExeption: function(ex) {
			alert(ex);
			return false;
		}
	});
	return true;
}

function ajaxCancelForm(formId, nextpage) {
	new Ajax.Request("FEResources", {
		method:'post',
		parameters: {action:"cancelForm",formId:formId},
		onComplete: function(transport, json) {
			json = getJson(transport,  json);
			document.location.href = getTarget(json, nextpage);
		}
	});
}

function getTarget(json, defaultTarget) {
	if (json) {
		var target = json["target"];
		if (target) {
			return target;
		}
	}
	return defaultTarget;
}

function ajaxAlert(message) {
	new Ajax.Request("FEResources", {
		method:'get',
		parameters: {action:"alert",message:message}
	});
}

function openDialog(event, url, componentId, width, height) {
	var left = event.screenX - width / 2;
	var top = event.screenY;
	// verschieben, wenn es aus dem Screen ragt (speziell wegen IE) 
	if (left > screen.width - width - 10) {
		left = screen.width - width - 10;
	}
	if (top > screen.height - height - 60) {
		top = screen.height - height - 60;
	}
	// TODO richtige Position auch für Opera ermitteln, wo es einen Offset durch Menüs und das Bookmark-Fenster gibt
	var winName = "dialog_" + componentId.replace(/[:.]/g, "_");
	var winParam = "resizable=yes,width="+width+",height="+height+",left="+left+",top="+top;
	var newDialog = window.open(url, winName, winParam);
	newDialog.focus();

	new PeriodicalExecuter(function(pe) {
		if (newDialog.closed) {
				if ($(componentId).value != undefined && typeof $(componentId).onchange == "function") {
					$(componentId).onchange();
				}
			  pe.stop();
		  }
	}, 1);
}

function decIncInput(input, event, min, max) {
	if(event.keyCode == 38) {
		addIntValue(input, 1, min, max);
	}
	else if(event.keyCode == 40) {
		addIntValue(input, -1, min, max);
	}
}

function addIntValue(input, add, min, max) {
	var value = input.value;
	if (value.blank()) {
		value = 0;
	}
	else {
		value = parseInt(value);
	}
	value = value + add;
	// optionale Beschränkung auf min und max-Werte
	if ((min != undefined && value < min) || (max != undefined && value > max)) {
		return;
	}

	if (value == 0) {
		input.value = '';
	}
	else {
		input.value = value;
	}
}

/* modifizierte Kopie aus prototype */
Ajax.EffectUpdater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
	 var wasEmpty = Element.empty(container);
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
	    json = getJson(response,  json);
	    if (json == null) {
	    	// Html-Update nur ausführen, wenn keine JSON-Antwort
		 var isEmpty = response.responseText.empty();
			if (wasEmpty != isEmpty) {
				// wechsel zwischen leer und nicht leer animieren
				if (wasEmpty) {
					Effect.BlindUp(container, {duration:0,afterFinish:function() {
						container.update(response.responseText);
						Effect.BlindDown(container, {duration:0.7});
					}});
				}
				else {
					Effect.BlindUp(container, {duration:0.7, afterFinish:function() {
						container.update(response.responseText);
						Effect.BlindDown(container, {duration:0});
					}});
				}
			}
		 else {
				// Inhalt nur austauschen
				container.update(response.responseText);
//				this.updateContent(response.responseText);
			}
	    }

		if (Object.isFunction(onComplete)) onComplete(response, json);
	 }).bind(this);

    $super(url, options);
  }
});
