﻿/*

	v1.0.0, 01.09.2009, dogan

	©2009 Tanyel Dogan, alle Rechte vorbehalten

	animiertes crosbrowser Tabmenu, nach unten
*/


//========== DropTab ==========
if(! Cms4d.Menu)Cms4d.Menu = {};
Cms4d.Menu.DropTab = {
	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_droptab') != -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.DropTab.ParseControls()', this.parsedelay);
		this.parsedelay += 20;
	}//Ende ParseControls

	,CreateControl:function(pnode)
	{
		var mycontrol = new Cms4d.Menu.DropTab.Cms4dDropTabControl(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.DropTab.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.DropTab;
		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.DropTab.document_onmousedown);
		}
		else if(window.addEventListener != undefined)
		{
			//Gecko und andere
			document.addEventListener('mousedown', Cms4d.Menu.DropTab.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.DropTab.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.Cms4dDropTabParseEnable != true)
		{
			this.parsetimerid = setTimeout('Cms4d.Menu.DropTab.TestParseEnable()', 100);
		}
		else
		{
			this.parsetimerid = setTimeout('Cms4d.Menu.DropTab.StartInit()', 0);
		}
	}
	,parsetimerid:setTimeout('Cms4d.Menu.DropTab.TestParseEnable()', 0)
};

//Konstruktor für eine Tabgruppe. pnode stellt den obersten Container-DIV der Gruppe dar.
Cms4d.Menu.DropTab.Cms4dDropTabControl = function(pnode)
{
	//Alle Controls eines document erhalten eine fortlaufende ctrlid.
	this.ctrlid = 'Cms4dDropTabControl_' + 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 DropTab-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.DropTab.Cms4dDropTabControl.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.DropTab.Cms4dDropTabControl.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 DropTab-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.DropTab.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;
	}

	//---

	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 = 0;
	this.ybottom = 0;
	this.tab_body_height = 0;
	//Die absoluten Grenzen innerhalb derer sich der tab min/max im übergeordneten DropTab_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;

		//Resultierende Gesamthöhe des tab_body incl. der margin des untergeordneten tab_content
		this.tab_body_height = this.tab_body.offsetHeight;

		//Alle Tabs im übergeordneten Control zurücksetzen.
		this.pctrl.ResetTabGroup();

		//WICHTIG:
		//Um den overflow eines tab_cont zuverlässig beschneiden zu können wird dem tab_body noch ein
		//Beschneidungs-DIV übergeordnet. Dies ist notwendig, da ja bei einer immer feststehenden Höhe
		//der unter dem Control liegende normale Seiteninhalt nicht für die Maus zugänglich ist. Dies
		//gilt ausser in IE und Opera in den meisten anderen Browsern. Um dies zu verhindern wird
		//der tab nur dan sichtbar, wenn er wirklich einrollen soll.
		//Im Ruhezustand hat tab_clip ein Höhe von 0px und overflow:hidden.
		//Dadurch wird der overflow auch im NS7 und IE6 korrekt nach unten hin abgeschnitten.
		//Für das Einrollen des tab_body muss die Höhe des Beschcneidungscontainers aber genau so hoch werden, wie sein Inhalt.
		//Statt aber aufwändig den clip_tab mit zu animieren wird er dirkt auf die max. Höhe gesetzt.
		//Dies reicht aus, da das Einrollen des tab_body zügig von statten gehen soll und in dieser
		//Zeit niemand auf darunterliegende Seitenelemente klicken muss.
		//Es wird also nachwievor nur der tab_body animiert.
		this.tab_clip.style.height = this.tab_body_height + 'px';

		//Die Untergrenze spielt beim DropTab nicht wirklich eine Rolle, da nach unten hin immer der volle Platz
		//ausgenutzt werden kann, den ein durch seine eigene Höhe Tab braucht
		this.ymin = this.tab_body_height * -1;
		this.ymax = 0;

		this.ytop = this.tab_body_height * -1;
		this.ybottom = 0;

		//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);

		//Die Tabs bewegen sich hier in positiver Richtung. Also wird der Tab zum Start auf die obere Begrenzung gesetzt.
		this.y = this.ytop;

		//Diesen Tab starten
		this.moveable = true;
//alert(this);
		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 im Gegensatz zum TabFlip in POSITIVER Richtung.
		//Desalb ist die Bereichsüberschreitung in positiver Richtung.
		if(newy > this.ybottom)newy = this.ybottom;

		this.y = newy;
//document.title = this.dy + ' ' + this.ystep + ' ' + this.y;
		//In Gegensatz zum FlipTab wird nur der tab_body bewegt.
		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.ybottom)
		{
			//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;

		//Steht der tab an der unteren Begrenzungsposition in Ruheposition, so wird er selbst zurückgesetzt.
		//Im anderen Fall wird die eigene Bewegung in Gang gesetzt.
		if(mc.y == mc.ybottom || mc.moveable == true)
		{
			mc.pctrl.ResetTabGroup();
		}
		else
		{
			//Setzt die Bewegung in Gang
			mc.StartMove(1);
		}
	}

	this.ResetTab = function()
	{
		//BEACHTE: beim DropTab bleiben die Buttons immer an ihrer natürlichen Position stehen
		//Es wird nur der tab_body animiert.
		clearTimeout(this.timerid);
		this.moveable = false;
		/*Der Beschneidungscontainer kann tatsächlich gültig auf 0px Höhe gesetzt werden, so dass
		er den overflow alle seiner Inhalte komplett abschneidet.*/
		this.tab_clip.style.height = '0px';
		this.tab_body.style.top = '-4000px';
		this.y = -4000;
	}

	//---

	//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 funktinoiert in allen Browsern, lässt aber nur einen Handler pro Element zu.
		this.tab_but.onclick = this.tab_but_onclick;

		//Beschneidungscontainer
		//In CSS ist er zwar in der width festgelegt, nicht aber in der height, so dass er beim Einblenden per CSS:hover
		//nicht wirksam wird. Hier wird der overflow jetzt auf hidden gesetzt. Die height wird vor jedem Einblenden bei StartMove eingestellt. s.o.
		with(this.tab_clip.style)
		{
			height = '0px';
			overflow = 'hidden';
		}

		//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.ResetTab();
	}

	//---

	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 += '\ty: ' + this.y + '\n';
		t += '\tfps: ' + this.fps + '\n';
		t += '\tflipspeed_ms: ' + this.flipspeed_ms + '\n';
		t += '\tystep: ' + this.ystep + '\n';
		return(t);
	}
}
//==========






