/**
 * (C) by Web-Commerce 2009
 */
/**
 * Erzeugt ein neues Flyout.
 * @param String id Die Id des Elements im HTML.
 * @param Object parentElement Die Referenz auf das Elternelement.
 * @param String dir Die Richtung, in der das Flyout angehaengt werden soll ("down" oder "right")
 * @author WOKI/31.03.2009
 */
function Flyout(id, parentElement, dir){
	this.id = id;
	this.elem = null;
	this.top = 0;
	this.left = 0;
	this.width = 0;
	this.height = 0;
	this.parent = parentElement;
	this.dir = dir;
	this.mouseIsOver = false;
	
	/**
	 * Weist dem Flyout das Element zu.
	 * @param String id Die Id des Elements, das das Flyout verkoerpert.
	 */
	this.setElement = function(id){
		var elem = document.getElementById(id);
		
		if(elem != null && this.parent != null){
			this.elem = elem;
			var dimFlyout = getDimension(elem);
			var posParent = getPosition(this.parent);
			var dimParent = getDimension(this.parent);
			
			//this.width = dimFlyout.width;
			this.width = $("#"+id).outerWidth();
			this.height = dimFlyout.height;
			
			if(this.dir == "right"){
				this.top = posParent.top;
				this.left = posParent.left+dimParent.width;
			} else if(this.dir == "down"){
				this.top = posParent.top + dimParent.height;
				if((posParent.left+this.width)>=document.documentElement.clientWidth) {
					this.left = (posParent.left+dimParent.width-this.width);
				} else {
					this.left = posParent.left;
				}
			}
			var ea = new EventAdder();
			ea.addEvent(this.elem, 'mouseover', this.mouseOver);
			ea.addEvent(this.elem, 'mouseout', this.mouseOut);
			ea.addChildNodesListener(this.elem, this.mouseOut, 'mouseout');
			registerGlobal(this);
		}
	};
	
	/**
	 * Zeigt das Flyout an.
	 */
	this.show = function(){
		if(this.parent != null && this.elem != null){
			this.elem.style.top = this.top+"px";;
			this.elem.style.left = this.left+"px";;
			this.elem.style.position = "absolute";
			this.elem.style.display = "block";
			this.elem.style.visibility = "visible";
			this.mouseIsOver = true;
		}
	};
	
	/**
	 * Schliesst das Flyout.
	 */
	this.close = function(){
		if(this.elem != null){
			this.elem.style.display = "none";
			this.elem.style.visibility = "hidden";
		}
	};
	
	/**
	 * Wird aufgerufen, wenn sich die Maus ueber dem Flyout befindet.
	 * @param Object e Das Event-Objekt.
	 */
	this.mouseOver = function(e){
		if(!e){
			e=window.event;
		}
		el = e.target ? e.target : e.srcElement;
		flyout = getRegisteredFlyout(el.id);
		if(flyout != null){
			if(el.id == flyout.id){
				flyout.mouseIsOver = true;
			}
		}
	};
	
	/**
	 * Wird aufgerufen, wenn die Maus das Element verlaesst.
	 * @param Object Das Event-Objekt.
	 */
	this.mouseOut = function(e){
		if(!e){
			e=window.event;
		}
		elRel = e.relatedTarget ? e.relatedTarget : e.toElement;
		el = e.target ? e.target : e.srcElement;
		var p = elRel;
		var relFlyoutId = "";
		while(p){
			flyout = getRegisteredFlyout(p.id);
			if(flyout != null){
				flyout.mouseIsOver = true;
				relFlyoutId = flyout.id;
				break;
			}
			p = p.parentNode;
		}
		
		if(relFlyoutId == ""){
			markClearing = true;		
		}
		
		p = el;
		while(p){
			flyout = getRegisteredFlyout(p.id);
			if(flyout != null){
				if(flyout.id == relFlyoutId){
					return;
				}
				flyout.mouseIsOver = false;
				return;
			}
			p = p.parentNode;
		}
	};
	
	/**
	 * Ermittelt die Ausmasse des gesetzten Elements.
	 * @return Ein Objekt mit den Feldern width und height.
	 */
	function getDimension(elem){
		if(elem != null){
			var ret = new Object();
			ret.width = elem.offsetWidth;
			ret.height = elem.offsetHeight;
			return ret;
		}
		return null;
	}
	
	/**
	 * Ermittelt die Anzahl der Pixel des Elements
	 * vom oberen Bildschirmrand.
	 * @return Die Anzahl der Pixel.
	 */
	function getPosition(elem){
		if(elem != null){
			var ret = new Object();
			ret.top = getTop(elem);
			ret.left = getLeft(elem);
			return ret;
		}
		return null;
	}
	
	/**
	 * Liefert die Position vom linken Rand
	 * des Objekts vom Fensters.
	 * @param obj Das Objekt.
	 * @return Die Position vom Linken Rand.
	 */
	function getLeft(obj){
		var lPos = 0;
		if (obj.offsetParent){
			while (obj.offsetParent){
				lPos += obj.offsetLeft
				obj = obj.offsetParent;
			}
		} else if (obj.x) {
			lPos += obj.x;
		}
		return lPos;
	}
	
	/**
	 * Liefert die Position vom oberen Rand
	 * des Objekts vom Fensters.
	 * @param obj Das Objekt.
	 * @return Die Position vom oberen Rand.
	 */	
	function getTop(obj){
		var oPos = 0;
		if (obj.offsetParent){
			//oPos = obj.offsetTop;
			while (obj.offsetParent){
				oPos += obj.offsetTop;
				obj = obj.offsetParent;
			}
		} else if (obj.y){
			oPos += obj.y;
		}
		return oPos;
	} 
	
	// Bei der Initialisierung das Element gleich zuweisen.
	this.setElement(id);
}

/**
 * Wird verwendet, um das Flyout im globalen Kontext zu registrieren.
 * Wird benoetigt, um Listener-Funktionen den Zugriff auf das Flyout
 * zu ermoeglichen.
 * @param Object f Die Referenz auf das Flyout.
 */
function registerGlobal(f){
	if(f != null){
		if(window.flyouts == null){
			window.flyouts = new Array();
		}
		window.flyouts[f.id] = f;
	}
}

/**
 * Liefert ein global registriertes Flyout.
 * @param String id Die Id des Flyouts.
 * @return Die Referenz auf das Flyout.
 */
function getRegisteredFlyout(id){
	var ret = null;
	if(id){
		ret = window.flyouts[id];
	}
	return ret;
}


/**
 * Modelliert ein Flyoutmenue.
 */
function FlyoutMenu(){
	// Alle geoffnete Flyouts.
	this.flyoutStack = new FlyoutStack();
	
	/**
	 * Oeffnet ein Flyout.
	 * @param String id Die Id des Elements.
	 * @param Object parentElement Die Referenz auf das Elternelement.
	 * @param String dir Die Richtung, in der das Element angehaengt werden soll.
	 */
	this.open = function(id, parentElement, dir){
		var elem = document.getElementById(id);
		var parentFlyout = this.getParentFlyout(parentElement);
		if(elem != null){
			var f = this.flyoutStack.getFlyout(id);
			if(f == null){
				if(parentFlyout != null){
					this.closeUntilMe(parentFlyout.id);
				} else {
					this.closeAll();					
				}
				f = new Flyout(id, parentElement, dir);
				this.flyoutStack.pushFlyout(f);
				f.show();
			} else {
				f.mouseIsOver = true;
			}
		} else if(parentElement != null) {
			if(parentFlyout != null){
				this.closeUntilMe(parentFlyout.id);
			} else {
				this.closeAll();
			}
		}
		
		if(parentElement != null && parentFlyout == null){
			var ea = new EventAdder();
			ea.addEvent(parentElement, 'mouseout', this.parentMouseOut);
			ea.addChildNodesListener(parentElement, this.parentMouseOut, 'mouseout');
		}
	};
	
	/**
	 * Liefert zu einem Element das direkte Elternflyout.
	 * @param Object Elem das Element.
	 * @return Object Das direkte Elternflyout. Null, falls keines vorhanden.
	 */
	this.getParentFlyout = function(elem){
		var ret = null;
		var e = elem;
		var f = null;
		while(e != null){
			f = this.flyoutStack.getFlyout(e.id);
			if(f != null){
				ret = f;
				break;				
			}
			e = e.parentNode;
		}
		return ret;		
	};
	
	/**
	 * Schliesst alle geoffneten Flyouts bis zu einem bestimmten
	 * Flyout.
	 * @param Object me Die Referenz auf das Flyout, bis zu dem geschlossen werden soll.
	 */
	this.closeUntilMe = function(me){
		var f = this.flyoutStack.popFlyout();
		while(f != null){
			if(me != null){
				if(me == f.id){
					this.flyoutStack.pushFlyout(f);
					return;
				}
			}
			f.close();
			f = this.flyoutStack.popFlyout();
		}
	};
	
	/**
	 * Schliesst alle geoffneten Flyouts.
	 */
	this.closeAll = function(){
		this.closeUntilMe(null);
	};
	
	/**
	 * Liefert true, wenn ein bestimmtes Flyout geoeffnet ist.
	 * @param String id Die Id des Flyouts.
	 * @return Boolean true, wenn dies geoeffnet ist.
	 */
	this.isOpened = function(id){
		var f = this.flyoutStack.getFlyout(id);
		return (f != null);
	};
	
	/**
	 * Liefert true, wenn die Maus sich ueber
	 * einem bestimmten Flyout befindet.
	 * @param Object f Die Referenz auf das Flyout.
	 * @return Boolean true, wenn das Flyout geoeffnet ist.
	 */
	this.mouseIsOverFlyout = function(f){
		if(f != null){
			return (f.mouseIsOver == true);
		}
		return false;
	}
	
	/**
	 * Fuehrt die Garbage-Collection durch, d.h.
	 * alle nicht mehr benoetigte Flyouts werden geschlossen.
	 * @param Boolean clearAll Wenn true, wird alles bereinigt.
	 */
	this.garbageCollection = function(clearAll){
		var f = this.flyoutStack.getLastFlyoutWithCriterion(this.mouseIsOverFlyout);
		if(f != null && !clearAll){
			this.closeUntilMe(f.id);
		} else {
			this.closeAll();
		}
	}
	
	/**
	 * Wird aufgerufen, wenn die Maus ein Elternelement verlaesst.
	 */
	this.parentMouseOut = function(e){
		if(!e){
			e=window.event;
		}
		elRel = e.relatedTarget ? e.relatedTarget : e.toElement;
		el = e.target ? e.target : e.srcElement;
		var p = elRel;
		while(p){
			flyout = getRegisteredFlyout(p.id);
			if(flyout != null){
				// Man bewegt sich noch auf einem Flyout
				return;
			}
			p = p.parentNode;
		}
		markClearing = true;
	}
}

/**
 * Dient zum Verwalten von Flyouts auf einem Stack.
 */
function FlyoutStack(){
	// Das Array, ueber das der Stack realsiert wird.
	this.flyouts = new Array();
	
	/**
	 * Legt ein Flyout auf den Stack.
	 * @param Object f Das Flyout, das auf den Stack gelegt wird.
	 */
	this.pushFlyout = function(f){
		this.flyouts.push(f);
	}
	
	/**
	 * Holt das oberste Element vom Stack.
	 * @return Das oberste Element, null sonst.
	 */
	this.popFlyout = function(){
		var ret = null;
		if(this.flyouts != null){
			ret = this.flyouts.pop();
		}
		return ret;
	};
	
	/**
	 * Liefert ein bestimmtes Flyout vom Stack.
	 * @param String id Die Id des Flyouts.
	 * @return Object Das Flyout, sofern auf dem Stack.
	 */
	this.getFlyout = function(id){
		if(this.flyouts != null){
			for(var i = 0; i < this.flyouts.length;i++){
				if(this.flyouts[i].id == id){
					return this.flyouts[i];
				}
			}
		}
		return null;
	};
	
	/**
	 * Liefert das letzte Flyout mit einem bestimmten Kriterium.
	 * @param Function criterion Die Funktion, die das Flyout erhaelt
	 * 							 und true liefert, wenn das Kriterium erfüllt ist.
	 * @return Object Das oberste Flyout auf dem Stack mit dem Kriterium.
	 */
	this.getLastFlyoutWithCriterion = function(criterion){
		if(criterion != null){
			for(var i = (this.flyouts.length-1); i >= 0; i--){
				if(criterion(this.flyouts[i])){
					return this.flyouts[i];
				}
			}
		}
		return null;
	}
}

/**
 * Fuegt Events Elementen hinzu.
 */
function EventAdder(){
	/**
	 * Fuegt einem Element ein Action-Listener hinzu.
	 * @param Object obj Die Refernz auf das Objekt.
	 * @param String type Der Typ des Events.
	 * @param Function fn Die Listener-Funktion.
	 */
	this.addEvent = function( obj, type, fn ){
	   if (obj.addEventListener) {
	      obj.addEventListener( type, fn, false );
	   } else if (obj.attachEvent) {
	      obj["e"+type+fn] = fn;
	      obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
	      obj.attachEvent( "on"+type, obj[type+fn] );
	   }
	};
	
	/**
	 * Entfernt einen Listener von einem Element.
	 * @param Object obj Die Refernz auf das Objekt.
	 * @param String type Der Typ des Events.
	 * @param Function fn Die Listener-Funktion.
	 */
	this.removeEvent = function( obj, type, fn ){
	   if (obj.removeEventListener) {
	      obj.removeEventListener( type, fn, false );
	   } else if (obj.detachEvent) {
	      obj.detachEvent( "on"+type, obj[type+fn] );
	      obj[type+fn] = null;
	      obj["e"+type+fn] = null;
	   }
	};
	
	/**
	 * Fuegt allen Kindelementen eines Elements einen Listener hinzu.
	 * @param Object elem Die Refernz auf das Element.
	 * @param Function listener Die Referenz auf die Listener-Funktion.
	 * @param String type Der Typ des Listeners.
	 */
	this.addChildNodesListener = function(elem, listener, type){
		if(elem != null && elem.childNodes){
			for(var i = 0; i < elem.childNodes.length; i++){
				this.addEvent(elem.childNodes[i], type, listener);
				this.addChildNodesListener(elem.childNodes[i], listener, type);
			}
		}
	};
}

/**
 * Wird verwendet, um das Flyout anzuzeigen.
 * @param String parentId Die Id des Eleternelements.
 * @param String flyoutId Die Id des Flyouts.
 * @param String dir Die Richtung, in der das Flyout angzeigt werden soll.
 */
function showFlyout(parentId, flyoutId, dir){
	parentElement = document.getElementById(parentId);
	if(parentElement != null){
		markClearing = false;
		flyoutMenu.open(flyoutId, parentElement, dir);
	}
}

/**
 * Wird zyklisch aufgerufen, um die Garbage-Collection durchzufuehren.
 */
function garbageCollection(){
	flyoutMenu.garbageCollection(markClearing);
	markClearing = false;
}

function closeAllByClick(){
	var ea = new EventAdder();
	var func = function(e) {
					flyoutMenu.closeAll();
				};
	ea.addEvent(document, 'click', func);
	
}

var markClearing = false;
// Das eigentliche FlyoutMenue.
var flyoutMenu = new FlyoutMenu();
// Initialisierung der Garbage-Collection
var gcCollection = window.setInterval("garbageCollection()", 3000);
closeAllByClick();