﻿/*

	v1.0.0, 01.09.2009, dogan

	©2009 Tanyel Dogan, alle Rechte vorbehalten

	animiertes crosbrowser Tabmenu, nach oben
*/


//========== FlipTab ==========
if(! Cms4d.Menu)Cms4d.Menu = {};
Cms4d.Menu.FlipTab = {
	ctrlcounter:0
	,Controls:new Object()
	,parsetimerid:null
	//Das delay ist anfangs sehr kurz und wird dann bei jedem Durchlauf erhöht.
	,parsedelay:20
	,parsecounter:0
	,parsemax:20
	,ContainerDivs:new Array()
	,ParseControls:function()
	{
		clearTimeout(this.parsetimerid);
		//Es wird nur bis zu einer max. Anzahl an Durchläufen automatisch geparst. Danach sollten
		//alle Elemente im DOM geladen sein.
		this.parsecounter++;
		if(this.parsecounter > this.parsemax)return;

		//---

		var newdivs = new Array();
		var alldivs = document.getElementsByTagName('div');
		//Es werden diejenigen DIVs als Startpunkte herausgefiltert, die die entsprechende CSS-Klasse beinhalten
		for(var i = 0; i < alldivs.length; i++)
		{
			var mydiv = alldivs[i];
			var class_name = mydiv.className;
			if(class_name.indexOf('cms4d_fliptab') != -1)
			{
				//Ist dieser DIV nicht schon in der Liste, dann hinzufügen.
				var isdupl = false;
				for(var x = 0; x < this.ContainerDivs.length; x++)
				{
					var testdiv = this.ContainerDivs[x];
					if(mydiv === testdiv)
					{
						isdupl = true;
						break;
					}
				}
				if(isdupl != true)
				{
					//Zur Liste aller hinzufügen
					this.ContainerDivs.push(mydiv);
					//zur Hilfsliste der neu zu erzeugenden Controls hinzufügen
					newdivs.push(mydiv);
				}
			}
		}
//document.title = 'Parse ' + this.parsecounter + '|' + this.parsemax + ' : ' + this.parsetimerid + ': ' + newdivs.length;

		//Zu allen neu gefundenen ein Control erzeugen
		for(var i = 0; i < newdivs.length; i++)
		{
			var mydiv = newdivs[i];
			this.CreateControl(mydiv);
		}

		newdivs = null;
		alldivs = null;

		//Grenzen oben und unten
		

		//Neuen Prüfdurchlauf anstossen
		//Das delay wird bei jedem Durchlauf erhöht, so dass am Anfang die Prüfung sehr schnell erfolgt, dann immer langsamer
		//bis das Maximum erreicht ist.
		this.parsetimerid = setTimeout('Cms4d.Menu.FlipTab.ParseControls()', this.parsedelay);
		this.parsedelay += 20;
	}//Ende ParseControls

	,CreateControl:function(pnode)
	{
		var mycontrol = new Cms4d.Menu.FlipTab.Cms4dFlipTabControl(pnode);
		//Jedes neue Control wird als Eigenschaft des Controls-Object hinzugefügt.
		//Dadurch kann über die ctrlid direkt auf den Puffer zugegriffen werden.
		//In zeitgesteuerten Aufrufen sieht das dann z.B. so aus:
		//	setTimeout('Cms4d.Menu.FlipTab.Controls.' + this.ctrlid + '.tu_was()'.....
		//ACHTUNG: es wird davon ausgegangen, dass zur Laufzeit keine bestehenden
		//Controls gelöscht werden !
		this.Controls[mycontrol.ctrlid] = mycontrol;
		//Der Einfachheit halber wird auch eine Variable im Window gesetzt.
		window[mycontrol.ctrlid] = mycontrol;
//alert(mycontrol);
	}

	,document_onmousedown:function(evt)
	{
		var obj = Cms4d.Menu.FlipTab;
		obj.ResetAllControls();
	}
	,ResetAllControls:function()
	{
		//BEACHTE: die contrls liegen als Eigenschatfen eines Objects vor, nicht als Einträge eines Arrays.
		for(var i in this.Controls)
		{
			var mycontrol = this.Controls[i];
			mycontrol.ResetTabGroup();
		}
	}

	,StartInit:function()
	{
		//Wird ausserhalb eines Controls auf das document geklickt, so sollen sich alle Tabs in allen Controls schliessen
		if(window.attachEvent != undefined)
		{
			//IE
			document.attachEvent('onmousedown', Cms4d.Menu.FlipTab.document_onmousedown);
		}
		else if(window.addEventListener != undefined)
		{
			//Gecko und andere
			document.addEventListener('mousedown', Cms4d.Menu.FlipTab.document_onmousedown, false);
		}


		//Selbstständig beginnen nach Controls im Dokument zu suchen
		this.ParseControls()
	}

	//Stopt das mousedown auf dem Control Container, so dass es sich nicht automatisch
	//durch mousedown-Handler im übergeordneten Document sofort wieder schliesst.
	//Wird neben das Control ins document geklick, so ist dort i.d.R. ein Event-Handler
	//definiert, der hier die Hide-Methode aufruft.
	,cancel_bubble:function(evt)
	{
		if(window.event)
		{
			window.event.cancelBubble = true;
			window.event.returnValue = false;
		}
		else if(evt != null)
		{
			//NS, FF
			evt.stopPropagation();
			evt.preventDefault();
		}
	}

	//Initialisierung starten
		//,parsetimerid:setTimeout('Cms4d.Menu.FlipTab.StartInit()', 0)
	//BEACHTE: in unterschiedlichen Browsern kann es zu Timerproblemen kommen, wenn dieses Script noch nicht komplett geladen ist,
	//oder andererseits der HTML-Quellcode mit den zu suchenden DIVs noch nicht geladen wurde.
	//Um sicherzustellen, dass der HTML-Quellcode komplett geladen ist, wird am Ende des Bodys
	//ein flag als Eigenschaft es window gesetzt. Hier wird in einer Testschleife solange dieses Flag
	//geprüft, bis es true ist. Erst dann wird von hier aus der Parsevorgang gestartet.
	,TestParseEnable:function()
	{
		clearTimeout(this.parsetimerid);
		if(window.Cms4dFlipTabParseEnable != true)
		{
			this.parsetimerid = setTimeout('Cms4d.Menu.FlipTab.TestParseEnable()', 100);
		}
		else
		{
			this.parsetimerid = setTimeout('Cms4d.Menu.FlipTab.StartInit()', 0);
		}
	}
	,parsetimerid:setTimeout('Cms4d.Menu.FlipTab.TestParseEnable()', 0)
};

//Konstruktor für eine Tabgruppe. pnode stellt den obersten Container-DIV der Gruppe dar.
Cms4d.Menu.FlipTab.Cms4dFlipTabControl = function(pnode)
{
	//Alle Controls eines document erhalten eine fortlaufende ctrlid.
	this.ctrlid = 'Cms4dFlipTabControl_' + Cms4d.UniqueId();

	//Alle Tabs eines Containes sind miteinander gekoppelt. Es kann immer nur
	//ein Tab zu gleichen Zeit sichtbar sein.
	this.tabs = new Array();

	this.container_elm = pnode;

	//Elemente sammeln
	var elms = Cms4d.GetElements(this.container_elm, 5);

	//Das oberste UL-Element, dass tab_list darstellt. Dieses wird
	//über CSS absolut so weit nach unten positioniert, dass die Register
	//mit ihrer Unterkante exakt auf der Unterkante von fliptab-Container aufsitzen.
	//Diese Position * -1 wird als ymin für die einzelnen tabs ausgewertet (s.u. StartMove)
	this.tab_list_elm = null;
	var ulist = elms.GetTagsByClassName('ul', 'tab_list');
	if(ulist.length > 0)this.tab_list_elm = ulist[0];

	//Tabs sammeln
	var tab_li_list = elms.GetTagsByClassName('li', 'tab_li');
	for(var i = 0; i < tab_li_list.length; i++)
	{
		var tab_li = tab_li_list[i];
		var mytab = new Cms4d.Menu.FlipTab.Cms4dFlipTabControl.prototype.TabControl(this, tab_li);
		//BEACHTE: ein neues Control für einen Tab wird nur dann zu den Listen hinzugefügt,
		//wenn das Parsen aller erforderlichen Elemente o.k. war (s.u. Konstruktor)
		if(mytab.ctrlok == true)
		{
			this.tabs.push(mytab);
			window[mytab.ctrlid] = mytab;
		}
	}

	this.ResetTabGroup = function(call_tab)
	{
		//Alle Tabs und body an Ursprungsposition setzen.
		//Da sich sowohl der Reiter, als auch der Body an ihrern jeweils übergeordneten Elementen
		//orientieren (diese stellen mit position:relative die Layoutanker dar) müssen beide Positionen nur auf 0 zurückgesetzt werden.
		//Die Offsets bei der Animation sind also auch für beide Elemente identisch.
		for(var i = 0; i < this.tabs.length; i++)
		{
			var mytab = this.tabs[i];
			mytab.ResetTab();
		}
	}

	this.toString = function()
	{
		var t = '';
		t += 'ctrlid: ' + this.ctrlid + '\n';
		t += 'tabs:\n';
		for(var i = 0; i < this.tabs.length; i++)
		{
			var mytab = this.tabs[i];
			t += mytab;
		}
		return(t);
	}
}

//Konstruktor für ein einzelnes Tab, das sich selbst verwaltet
Cms4d.Menu.FlipTab.Cms4dFlipTabControl.prototype.TabControl = function(parent_control, pnode)
{
	this.ctrlid = 'TabControl_' + Cms4d.UniqueId();
	this.pctrl = parent_control;
	this.ctrlok = false;

	//Auf dem ROOT-Node jedes Tabs wird nur noch die Klasse tab_li belassen.
	//Die 2. Klasse tab_hide, die im Layout statisch gesetzt war war dafüt verantwortlich
	//in allen Browsern ausser IE6 beo hover das zugehörige Tab anzuzeigen.
	//Ab jetzt wird die gesamte Kontrolle durch das FlipTab-Control bzw. dieses TabControl hier durchgeführt.
	this.container_elm = pnode;
	//mousedown abblocken, damit das tab nicht sofort geschlossen wird, wenn auf einem seiner
	//eigenen Elemente ein mousedown stattfindet. s.o. document_onmousedown
	this.container_elm.onmousedown = Cms4d.Menu.FlipTab.cancel_bubble;

	//----- alle erforderlichen Elemente suchen --------
	//Sind nicht alle vorhanden, so bleibt das Control ungültig.
	var elms = Cms4d.GetElements(this.container_elm, 4);

	//Ein click auf den Reiter löst später die Aufwärtsanimation von Reiter und zugehörigem body aus.
	this.tab_but = null;
	var divlist = elms.GetTagsByClassName('div', 'tab_but');
	if(divlist.length > 0)this.tab_but = divlist[0];

	this.tab_clip = null;
	divlist = elms.GetTagsByClassName('div', 'tab_clip');
	if(divlist.length > 0)this.tab_clip = divlist[0];

	this.tab_body = null;
	divlist = elms.GetTagsByClassName('div', 'tab_body');
	if(divlist.length > 0)this.tab_body = divlist[0];

	this.tab_content = null;
	divlist = elms.GetTagsByClassName('div', 'tab_content');
	if(divlist.length > 0)this.tab_content = divlist[0];

	if(this.tab_but != null && this.tab_clip != null && this.tab_body != null && this.tab_content != null)
	{
		this.ctrlok = true;
	}

/*
	//Ein click auf den Reiter löst später die Aufwärtsanimation von Reiter und zugehörigem body aus.
	this.tab_but = null;
	var divlist = elms.GetTagsByClassName('div', 'tab_but');
	if(divlist.length > 0)this.tab_but = divlist[0];

	this.tab_body = null;
	var lilist = elms.GetTagsByClassName('li', 'tab_body');
	if(lilist.length > 0)this.tab_body = lilist[0];

	this.tab_content = null;
	var divlist = elms.GetTagsByClassName('div', 'tab_content');
	if(divlist.length > 0)this.tab_content = divlist[0];

	if(this.tab_but != null && this.tab_body != null && this.tab_content != null)
	{
		this.ctrlok = true;
	}
*/

	//---

	this.timerid = null;
	this.timerdelay = 20;
	this.fps = 0;
	this.flipspeed_ms = 250;
	this.dy = 0;
	this.moveable = false;
	this.ystep = 20;
	this.y = 0;
	this.ytop = -400;
	this.ybottom = 0;
	this.tab_body_height = 0;
	//Die absoluten Grenzen innerhalb derer sich der tab min/max im übergeordneten fliptab_container bewegen darf.
	//Wird vor jedem Start neu ausgemessen.s.u.
	this.ymin = 0;
	this.ymax = 0;

	this.StartMove = function(d)
	{
		if(typeof(d) == 'number')this.dy = d;

		//Grenzen ausmessen
		//Dies muss jedesmal geschehen, da bei der Initialisierung es Objektes
		//ggf. bestimmte Element noch nicht Ihre endgültige Ausdehnung haben.
		this.tab_but_height = this.tab_but.offsetHeight;

		//Damit aber die untersten Inhalte nicht durch die darüberliegenden tab_buttons verdeckt werden (diese haben den höchsten
		//zIndex und liegen immer vor allen tab_bodys) wird noch ein paddingBottom in der Höher der Reiter hinzugefügt.
		//Es wird padding statt margin verwendet, da der IE6 wie immer Probleme mit margin hat...
		this.tab_content.style.paddingBottom = (this.tab_but_height) + 'px';

		//Resultierende Gesamthöhe des tab_body incl. der margin des untergeordneten tab_content
		this.tab_body_height = this.tab_body.offsetHeight;

		//Die gesamte tab_list ist über CSS im fliptab-Container absolut nach unten positioniert.
		//Da die Oberkante aller Tabs identisch mit der Oberkante von tab_list ist stellt dessen
		//Y-Position in Pixel die max Höhe darf, in der sich die tabs bewegen dürfen bevor sie oben
		//an die Oberkante des tablist-Containers stossen. Der Wert ist für die relative Bewegung nach oben negativ.
		//Unabhängig von der tatsächlichen Höhe eines tab_body ist seine max Bewegungshöhe (ytop) nie grösser
		//als ymin.
		this.ymin = (this.pctrl.tab_list_elm.offsetTop * -1);
		this.ymax = 0;
		//Der tab darf sich nur soweit in negativer Richtung nach oben bewegen, dass am Ende seine Unterkante
		//direkt auf der Unterkante des fliptab-Containers aufsitzt. Diese Position darf aber nie
		//kleiner sein als ymin. Sonst würde der tab oben aus dem fliptab-Container hinausragen.
// A C H T U N G :
//Im IE8 kommt es zu falschen Messergebnissen wenn bestimmte Schriftgrössen verwendet werden.
//Die Ursache liegt wohl in Nachkommawerten wie z.B. 1.25em, die dann vom IE8 anders gerundet werden als vom IE7 und
//allen anderen. Deshalb wird sicherheitshalber die Obere Grenze um 1px runtergesetzt.
//Andernfalls fürde in solchen Fällen ein Spalt von 1px unterhalb des tab_body zu sehen sein !
//WICHTIG: dieses 1px ist zu berücksichtigen, wenn ggf. in einem Design am unteren Rand des tab_body
//eine Linie erscheinen soll. Dann darf natürlich kein Pixel abgezogen werden.
		this.ytop = ((this.tab_body_height - 1) * -1);
		if(this.ytop < this.ymin)this.ytop = this.ymin;

		//Alle Tabs sollen ungeachtet ihrer unterschiedlichen Höhe immer die gleiche Zeit brauchen,
		//um sich bis zum oberen Anschlag zu bewegen. Deshalb wird je nach Höhe von ytop
		//die Schrittweite der Bewegung variiert. Ein sehr hoher Tab bewegt sich schneller als ein sehr niedriger.
		//Grundlage ist die Anzahl der Timerdurchläufe pro Sekunde. Soll sich ein tab in einer Sekunde
		//bis zum oberen Anschlag bewegen, muss er pro Schleifendurchlauf um eine bestimmte Distanz verschoben werden.
		this.fps = this.flipspeed_ms / this.timerdelay;
		this.ystep = Math.round((this.ytop * -1) / this.fps);
//alert(this);

		//---

		//Alle Tabs im übergeordneten Control zurücksetzen.
		this.pctrl.ResetTabGroup(this);
		//BEACHTE: die tabs bewegen sich in negativer Richtung nach oben. Also wird die Ausgangsposition Y
		//auf ybottom gesetzt. In diesem Fall hier ist dies 0.
		this.y = this.ybottom;
		//Diesen Tab starten
		this.moveable = true;
		this.Move();
	}
	this.Move = function()
	{
		clearTimeout(this.timerid);
		if(this.moveable != true)return;
		var newy = this.y + (this.ystep * this.dy);
		//BEACHTE: die Bewegung erfolg in negativer Richtung. Desalb ist die Bereichsüberschreitung in negativer Richtung.
		if(newy < this.ytop)newy = this.ytop;

		this.y = newy;
		//BEACHTE: sowohl der Parent des Reiter, als auch der des Body verhalten sich durch position:relative
		//als Positionierungsanker (offsetParent) der beiden absolut positinierten Unterlemente.
		//Da die ofsetParents berets den gewünschten Abstand voneinander haben können die
		//beiden sichtbaren Unterelemente um den selben Offset verschoben werden.
		this.tab_but.style.top = this.y + 'px';
		this.tab_body.style.top = this.y + 'px';

		//ggf. neuer Durchlauf, solange y noch grösser ist als die obere Grenze
		if(this.y > this.ytop)
		{
			//Ein Verweis auf jedes Control wurde als neue Eigenschaft im window erzeugt.
			//Dies ist der schnellste Weg, um auf dieses Control von aussen zuzgreifen.
			var func = 'window.' + this.ctrlid + '.Move()';
			this.timerid = setTimeout(func, this.timerdelay);
		}
	}

	//---

	this.tab_but_onclick = function(evt)
	{
		//BEACHTE: diese Funktion wird ggf. anonym aufgerufen.
		//Deshalb wird sicherheitshalber über die Eigenschaft Cms4dControl des auslösenden HTML-Elements
		//auf das Control zugegriffen.
		var e = null;
		if(window.event)
		{
			//IE, Opera, Chrome, Safari
			e = window.event.srcElement;
			window.event.cancelBubble = true;
			window.event.returnValue = false;
		}
		else
		{
			//NS7, Firefox
			e = evt.target;
			evt.preventDefault();
			evt.stopPropagation();
		}
		if(e == null)return;
		var mc = e.Cms4dControl;
		if(mc == null)return;

		//Ist das tab in Ruheposition, dann wird die eigene Bewegung in Gang gesetzt. Ist das
		//tab hingegen auf dem Weg nach oben, oder steht am oberen Anschlag, so wird es zurückgesetzt.

		//Setzt die Bewegung in Gang
		if(mc.y == mc.ybottom)
		{
			mc.StartMove(-1);
		}
		else
		{
			mc.pctrl.ResetTabGroup();
		}
	}

	this.ResetTab = function()
	{
		clearTimeout(this.timerid);
		this.moveable = false;
		this.tab_but.style.top = this.ybottom + 'px';
		this.tab_body.style.top = this.ybottom + 'px';
		this.y = this.ybottom;
		//this.tab_body.style.zIndex = i;
	}

	//---

	//Container formatieren
	if(this.ctrlok == true)
	{
		with(this.container_elm)
		{
			Cms4dControl = this;
			//BEACHTE: da das LI noch weitere wichtige Klassen haben kann wird nur der Schlüsselstring ersetzt.
			var new_classname = className.replace('tab_hide', '');
			className = new_classname;
		}

		this.tab_but_height = this.tab_but.offsetHeight;
		this.tab_but.Cms4dControl = this;
		//BEACHTE: diese Zuweisung funktioniert in allen Browsern, lässt aber nur einen Handler pro Element zu.
		this.tab_but.onclick = this.tab_but_onclick;

		//CSS-Höhe des bodys entfernen, so dass er nur noch so hoch ist wie sein tatsächlicher Inhalt.
		//Der Tab kann später nur so weit ausgefahren werden, wie seine eigene Höhe.
		this.tab_body.Cms4dControl = this;
		with(this.tab_body.style)
		{
			height = 'auto';
		}
	}

	//---

	this.toString = function()
	{
		var t = '';
		t += '\tpctrl.ctrlid: ' + this.pctrl.ctrlid + '\n';
		t += '\tctrlid: ' + this.ctrlid + '\n';
		t += '\tctrlok: ' + this.ctrlok + '\n';
		t += '\ttab_but_height: ' + this.tab_but_height + '\n';
		t += '\ttab_body_height: ' + this.tab_body_height + '\n';
		t += '\ttimerdelay: ' + this.timerdelay + '\n';
		t += '\tymin / ymax: ' + this.ymin + ' / ' + this.ymax + '\n';
		t += '\tytop / ybottom: ' + this.ytop + ' / ' + this.ybottom + '\n';
		t += '\tfps: ' + this.fps + '\n';
		t += '\tflipspeed_ms: ' + this.flipspeed_ms + '\n';
		t += '\tystep: ' + this.ystep + '\n';
		return(t);
	}
}
//==========






