/**
 *	Formulář se záložkami
 *
 *	JavaScript script
 *
 *	Popis:
 *	Vloží do dokumentu formulář s libovolným počtem záložek a částí společnou
 *	pro všechny záložky (obvykle spodní část obsahující tlačítka OK a
 *	Zrušit). Každá záložka formuláře i společná část je samostatný blokový
 *	element, který může uživatel naplnit libovolným HTML kódem. Každá záložka
 *	má titulek, pořadí záložek lze interaktivně měnit tažením za titulek.
 *
 * 	@author: 	Jiří Pagáč
 *	@email: 	xpagac02@stud.fit.vutbr.cz
 *	@date:		Mon 16 Feb 2009 09:26:09 PM CET
 *
 *	last modification:
 *      @date:  Wed 11 Mar 2009 08:55:40 PM CET
 *
 *      CHANGES:
 *      - odladění pro zmetka
 *      - pročištění kódu
 *
 *      TODO:
 *      - předělat skrývání a zobrazování záložek v režimu s přesouváním,
 *          je to neefektivní
 *      - dodělat podporu pro volbu tlačítka, které se má použít k přesouvání
 *          (IE nedetekuje levé tlačítko se stejným indexem!!!)
 *      - vizuální přesouvání
 *      - obejít se bez přepisu document
 *      - obejít se bez takového množství modifikací prototypů
 */

/**
 *  Prototyp panelu se záložkami - konstruktor.
 *
 *  @param idContainer  Id blokového elementu, který obsahuje záložky.
 *  @param setupAfterInstantiate Pokud je <code>true</code>, tak současně
 *             s new bude spuštěna instalace, pokud <code>false</code>,
 *             tak se musí potřebné funkce zavolat ručně.
 *
 *             Jsou to tyto dvě funkce:
 *                  searchForTabElements(this.tabElementClass);
 *                  setupTabPanel();
 *
 *  @param defaultSelected  Index záložky, která má být vybrána po zavolání
 *                              funkce setupTabPanel. Validni jsou hodnoty > 0,
 *                              pokud bude při volání setupTabPanel hodnota větší,
 *                              než čítač tabCount, bude vybrána poslední záložka.
 *
 *  @param preserveHs       Pokud je <code>true</code>, tak nadpisy sloužící jako
 *                              obsah oušek budou zachovány. Pokud je
 *                              <code>false</code>, tak budou odstraněny.
 *
 *  @param tabMovable       Pokud je <code>true</code>, tak půjde se záložkama šibovat.
 */
 function tabPanel(idContainer, setupAfterInstantiate, defaultSelected, preserveHs, tabMovable) {

    this.idContainer        = idContainer;          // id kontejneru
    this.tabContainer       = document.getElementById(idContainer); // element nadřazeného kontejneru    
    this.tabElements        = [];                   // pole pro ukládání záložek
    this.tabCSSClass        = this.tabContainer.className;      // výchozí třída pro záložky
    this.tabHeadingClass    = this.tabCSSClass + ' tab-heading';  // výchozí třída pro kontejner oušek
    this.tabElementClass    = this.tabCSSClass + ' tab';  // výchozí třída pro obsah záložky
    this.tabHeadingIdPrefix = this.idContainer + '-tabh-'; // výchozí prefix pro generování id oušek
    this.tabPosition        = 'top';                // umisteni zalozek: top [ bottom ]; !!! EXPERIMENTAL !!!
    this.tabMovable         = true;                 // určuje, zda budou záložky přesouvatelné    
    this.tabDefaultActive   = 0;                    // index záložky, která bude po vytvoření aktivní
    this.selectedTabIndex   = 0;                    // index vybrané záložky    
    this.typeOfHTitles      = 'H3';                 // typ nadpisů, nebo elementů, které budou použity
                                                    // jako nadpis do ouška
    this.preserveHs         = true;                 // pokud je true, tak zachová nadpisy H, které se
                                                    // použijí jako titulky do oušek, pokud je false,
                                                    // tak budou h1 elementy odstraněny z obsahu
    this.defaultTabName     = 'Default';            // výchozí text ouška, uplatní se, pokud element
                                                    // neobsahuje h1, nebo kontejner atribut title
    this.startDrag          = false;                // po vytvoření není ve stavu, kdy se přesouvají záložky - neměnit

    // upraveni vychozich nastaveni
    if(defaultSelected > 0) this.tabDefaultActive = defaultSelected;

    // pokud je false, tak z obsahuj záložek odstraním nadpisy
    if(preserveHs != true) this.preserveHs = false;

    // pokud je false, tak z obsahuj záložek odstraním nadpisy
    if(tabMovable != true) this.tabMovable = false;

    // rock'n'roll
    if(setupAfterInstantiate == true) {
        this.searchForTabElements(this.tabElementClass);
        this.searchForHs();
        this.setupTabPanel();
    }

    this.DEBUG              = null;                 // pro DEBUG výpisy nastavit cokoliv různého od null

    if(this.DEBUG != null) {        
        document.write('Tab Elements count: '+ this.tabElements.length +'<br />');
        document.write('Tab CSS Class:      '+ this.tabCSSClass +'<br />');
        document.write('Tab Heading Class:  '+ this.tabHeadingClass +'<br />');
        document.write('Tab Element Class:  '+ this.tabElementClass +'<br />');
        document.write('Tab HeadingIdPrefix: '+ this.tabHeadingIdPrefix +'<br />');
        document.write('Tab Default Active: '+ this.tabDefaultActive +'<br />');
        document.write('Preserve Hs: '+ this.preserveHs +'<br />');
        document.write('Tab movable: '+ this.tabMovable +'<br />');
    }
 }

 /**
  *     Najde všechny záložky (bezprostřední následníky kontejneru) a z bezprostredne
  *     nasledujicich nadpisu udela titulky zalozek. Dále viz. preserveH1
  *
  *     @param tabClass   Nazev tridy elementu zalozky.
  */
 tabPanel.prototype.searchForTabElements = function(tabClass) {
    elements = this.tabContainer.getElementsByTagName('*');    

    // našel všechny obsažené elementy
    if((elements != null) && !(elements instanceof Array)) {
        for(var el in elements) {            
            // do pole vlozim jen elementy pro tab a jen z prvni urovne,
            // takze vnorene zalozky timto nejsou ovlivneny
            if((elements[el].className == tabClass) && (elements[el].parentNode === this.tabContainer)) {
                elements[el].id = this.idContainer +'-'+ this.tabElements.length;                

                // uložím blok tabu do pole
                this.tabElements.push(elements[el]);
                this.tabCount++;
            }
        }
    } else {
        return false;
    }

    return true;
 }

 /**
  *     Z bezprostredně následujících nadpisů H
  *     udělá titulky záložek. Dále viz. preserveH1
  */
 tabPanel.prototype.searchForHs = function() {
    // Zjištění titulku záložek.
    for(var i=0; i<this.tabElements.length; i++) {
        var hElements = this.tabElements[i].childNodes;

        for(var j=0; j<hElements.length; j++) {
            if((hElements[j].tagName == this.typeOfHTitles) && (hElements[j].parentNode === this.tabElements[i])) {
                
                // pokud jako první synovský element následuje nadpis, bude použit jako titulek
                this.tabElements[i].tabTitle = hElements[j].innerHTML;
                
                // pokud je preserveH1 == false tak odstranim nadpis
                if(this.preserveHs == false) {
                    this.tabElements[i].removeChild(hElements[j]);
                }

                // jakmile najdu, tak zbaběle končím :-/
                break;
            } else {
                if(this.tabElements[i].title != '') {
                    // pokud není definován nadpis, zkusíme získat data z titulku
                    this.tabElements[i].tabTitle = this.tabElements[i].title;
                } else {
                    // pokud se neaplikuje nic z predchozich, ulozim zde defaultni nazev
                    this.tabElements[i].tabTitle = this.defaultTabName;
                }
            }

        }
        // document.write('<br /> tabTitle: '+ i +') '+this.tabElements[i].tabTitle);
    }
 }


 /**
  *     Nainstaluje záložky.
  */ 
 tabPanel.prototype.setupTabPanel = function() {
    /* Příprava elementů pro vytvoření uspořádaného seznamu
     * a tím pádem sémanticky vhodně vytvořeného seznamu. */
    var tabDivItem = document.createElement('div');
    var tabUlItem = document.createElement('ul');
    
    tabDivItem.className = this.tabHeadingClass;    // nastavení CSS třídy pro lištu záložek

    /*
     *  Pokud je povoleno přesouvání záložek, tak provedu potřebná nastavení:
     *    - vypnutí výběru textu nad elementem záložek,
     *      tohle je nutné kvůli tomu, že při přesouvání
     *      záložek by kurzor myši vybíral text.
     *    - přiřazení callback události
     */
    if(this.tabMovable == true) {
        disableSelection(tabDivItem);   // vypnutí označování textu myší pro lištu záložek
        document.objectTabPanelOrigin = null;   // do objektu dokumentu je nutné uložit referenci na objekt záložky
        document.onmouseup = this.mouseUpDocument;  // zaridi shozeni flagu kdekoliv dojde k onmouseup
    }
    

    /*  Jednotlivé záložky přeměním v položky seznamu a
     *  do vytvořeného elementu si vložím atributy:
     *       objectOrigin - reference na tento objekt
     *       tabToShow    - index do pole záložek
     *
     *  Pote nastavim udalost onclick tak, aby zavolala statickou
     *  funkci showPanel, ktera vyuzije vyse zminenych atributu
     *  k zobrazeni, resp. schovani zalozek.
     */
    for(var i=0; i<this.tabElements.length; i++) { 
        tabLiItem = document.createElement('li');
        
        if(this.tabMovable == true) {
            //  záložky se mohou přesouvat.
            tabLiItem.onmousedown = this.mouseDown;
            tabLiItem.onmouseup = this.mouseUp;
            tabLiItem.onmousemove = this.mouseMove;
        } else {
            // přesouvání záložek je vypnuto, proto se bude volat jednoduchá funkce
            tabLiItem.onclick = this.showPanelCallback;
        }

        // Vytvoření titulku záložky.
        tabAItem = document.createElement('a');
        tabAItem.innerHTML = this.tabElements[i].tabTitle;
        tabAItem.setAttribute('name', this.tabElements[i].tabTitle);
        tabLiItem.appendChild(tabAItem);

        // Nastavení nutných vlastností elementu záložky.
        tabLiItem.id = this.tabHeadingIdPrefix + i; // jedinečné id záložky
		tabLiItem.idTab = i;                        // index záložky v poli
        tabLiItem.objectOrigin = this;              // reference na objekt záložek
                                                    // - používá se při práci z callback
                                                    //   funkcí

        // Připojení k rodiči na konec.
        tabUlItem.appendChild(tabLiItem);
    }

    tabDivItem.appendChild(tabUlItem);

    // Podle nastavení zobrazím nahoře nebo dole.
    if(this.tabPosition == 'top') {
        this.tabContainer.insertBefore(tabDivItem, this.tabContainer.firstChild);
    } else {
        this.tabContainer.appendChild(tabDivItem);
    }

    // Nastavím výchozí zobrazení podle nastavení.
    this.showPanel(this.tabDefaultActive);  
 }

 /** 
  *     Zviditelni vybrany blok a ostatni zneviditelni.
  *     Používá se při inicializaci pro prvotní nastavení všech panelů.
  *     Je pomalejší.
  *     @param idTabEl    index záložky v poli záložek
  */
 tabPanel.prototype.showPanel = function(idTabEl) {
    // Nejdříve všechny skryji.
    for(var i=0; i<this.tabElements.length; i++) {
        this.tabElements[i].style.display = 'none';

        var tempLi = document.getElementById(this.tabHeadingIdPrefix + i);
        tempLi.className = '';
        tempLi.getElementsByTagName('a')[0].className = '';
    }

    /* A poté zobrazím pouze zvolenou záložku, je to rychlejší, než pro
     * každou záložku v poli spouštět podmínku, poté se nastaví jiná třída
     * pro každý prvek konceptu sliding doors. */
    this.tabElements[idTabEl].style.display = 'block';  // zobrazím požadovanou záložku
    var tempLi = document.getElementById(this.tabHeadingIdPrefix + idTabEl);
    tempLi.className = 'current';   // třída označuje, že záložka je vybrána
    tempLi.getElementsByTagName('a')[0].className = 'current';  // pro aktivní záložku je
                                                                // také zvláštní třída

    // uložím index vybrané záložky propozdější využití
    this.selectedTabIndex = idTabEl;
}

 /**
  *     Zviditelni blok a ostatni zneviditelni. Staticka funkce.
  *     Pro použití v onclick události, když není aktivní přesun záložek.
  *
  *     this - plati v kontextu objektu, ktery spustil reakci na udalost onclick,
  *         -> v tomto pripade polozka seznamu. Ta obsahuje dva atributy navic,
  *             ktere umozni identifikovat objekt pres zpetnou referenci, ktera
  *             je v te polozce uvedena.
  *
  *     Přes:   this.objectOrigin  - přistupuji k datovým položkám objektu
  *             this.tabToShow     - index do pole zalozek
  */
 tabPanel.prototype.showPanelCallback = function() {   
    // Skryji původní záložku.
    this.objectOrigin.tabElements[this.objectOrigin.selectedTabIndex].style.display = 'none';
    var tempLi = document.getElementById(this.objectOrigin.tabHeadingIdPrefix + this.objectOrigin.selectedTabIndex);
    tempLi.className = '';
    tempLi.getElementsByTagName('a')[0].className = '';

    // A poté zobrazím pouze zvolenou záložku.
    this.objectOrigin.tabElements[this.idTab].style.display = 'block';
    var tempLi = document.getElementById(this.objectOrigin.tabHeadingIdPrefix + this.idTab);
    tempLi.className = 'current';
    tempLi.getElementsByTagName('a')[0].className = 'current';

    // Uložím index vybrané záložky.
    this.objectOrigin.selectedTabIndex = this.idTab;
}

/**
 *      Událost se volá při stisku tlačítka.
 *
 *      IE má problémy při současném zpracování událostí
 *      onclick a onmousedown,onmouseup.
 */
 tabPanel.prototype.mouseDown = function(ev) {
    // V IE je ev null a události obsahuje window.event.
    ev = ev || window.event;

    this.objectOrigin.startDrag = true;     // budu možná přesouvat.
    this.objectOrigin.elementToMove = this; // budu přesouvat tento element
                                            // (uložím si jeho referenci do
                                            // původního objektu záložek)
    this.objectOrigin.oldposX = ev.pageX;   // pozici myši si uložím pro
                                            // použití v mousemove

    document.objectTabPanelOrigin = this.objectOrigin;  // Do objektu document si uložím
                                                // referenci na původní objekt
                                                // záložek. Je to nutné při kliku mimo
                                                // objekt záložek.

    // Nastavim kurzor pro přesun.
    this.objectOrigin.elementToMove.parentNode.style.cursor = 'move';
 }

/**
 *      Událost se volá při vymáčknutí tlačítka.
 *
 *      IE má problémy při současném zpracování událostí
 *      onclick a onmousedown,onmouseup.
 */
 tabPanel.prototype.mouseUp = function() {
    // provadim, jen pokud jsem ve stavu presouvani, nebo reference existuje
    if((this.objectOrigin.startDrag == null)
        || (this.objectOrigin.startDrag == false)) { return; }
    
    this.objectOrigin.startDrag = false;        // už nepřesouvám
    this.objectOrigin.oldposX = null;           // starou pozici znehodnotím
    this.objectOrigin.elementToMove.parentNode.style.cursor = '';   // nastavím normální kurzor
    
    // Provede pouze když se rodičovské elementy záložek shodují.
    if(this.parentNode === this.objectOrigin.elementToMove.parentNode) {        
        
        /* Přesouvám na libovolné místo (má význam jen v kombinaci s tupým MSIE).
         * Když se neodchytí mousemove událost, tak nedojde k postupnému přesunu záložek,
         * proto je nutné je přesunout explicitně. */        
        var moveVector = this.objectOrigin.elementToMove.offsetLeft - this.offsetLeft;  // směr ve kterém přesouvám záložku

        if(moveVector > 0) {
            // přesouvám doleva
            this.parentNode.insertBefore(this.objectOrigin.elementToMove, this);
        } else {
            // posouvám doprav
            this.parentNode.insertBefore(this.objectOrigin.elementToMove, this.nextSibling); // vloží na konec seznamu
        }

        // Zobrazím panel/skryji ostatní. IE při události občas vrátí v this jiný element.
        if(this.tagName == 'A') {
            this.parentNode.objectOrigin.showPanel(this.objectOrigin.elementToMove.idTab);
        } else {
            this.objectOrigin.showPanel(this.objectOrigin.elementToMove.idTab);
        }
     }
  }

/** 
 *      Callback pro onmouseup událost nad dokumentem.
 *      Pro případ, že tlačítko myši pustím jinde nad dokumentem.
 */
 tabPanel.prototype.mouseUpDocument = function() {
    if(this.objectTabPanelOrigin != null) {
        if(this.objectTabPanelOrigin.startDrag == false) { return; }

        this.objectTabPanelOrigin.startDrag = false;
        this.objectTabPanelOrigin.oldposX = null;           // starou pozici znehodnotím        
        this.objectTabPanelOrigin.elementToMove.parentNode.style.cursor = '';   // nastavím normální kurzor
        this.objectTabPanelOrigin = null;
    }
 }

/**
 *  Callback funkce pro průběžné přesouvání panelů.
 *  
 *  Tuto událost neumí registrovat/nebo používat MSIE!
 *  V rámci zachování (mého) duševního zdraví budou uživatelé ochuzeni
 *  o trochu toho user-friendly!
 */
 tabPanel.prototype.mouseMove = function(ev) {
    ev = ev || window.event;
    
    if((this.objectOrigin.startDrag != null) && (this.objectOrigin.startDrag == true)) {
        // v Internet Exploreru je ev null a události obsahuje window.event.        

        /*  Toto je eliminace "záchvěvů". Během přesouvání myši se přesouvají
         *  elementy v rámci DOM. Pokud je jedna záložka krátká a druhá naopak
         *  dlouhá, tak by se mohlo stát, že se budou neustále přesouvat mezi
         *  sebou, protože při výměně se pod myší objeví delší záložka a dojde
         *  opět k přehození.
         *
         *  Proto k přesunu dojde teprve, když vzdálenost, kterou myš ujede je
         *  větší nebo rovna rozdílu šířky těchto dvou záložek. Tím dojde k
         *  přehození teprve v bezpečné vzdálenosti.
         */
        if(Math.abs(ev.pageX - this.objectOrigin.oldposX) >=
            (Math.abs(this.offsetWidth - this.objectOrigin.elementToMove.offsetWidth))) {

            this.objectOrigin.oldposX = ev.pageX;
            this.objectOrigin.currentElement = this;

            /*
             *  Pokud přesouvám o jednu záložku.
             *
             *  Pokud se přesunuji nad záložkou vlevo od přesouvané,
             *  vložím záložku před aktuální.
             *
             *  Pokud ne, tak vložím před záložku o 2 vpravo.
             *
             *  Pokud je nextSibling == null (tj. bezprostřední následník v DOM,
             *  tak se element vloží na konec seznamu synovských elementů.
             */
            if(this.nextSibling === this.objectOrigin.elementToMove) {
                this.parentNode.insertBefore(this.objectOrigin.elementToMove, this);
            } else {
                this.parentNode.insertBefore(this.objectOrigin.elementToMove, this.nextSibling);
            }
        }
    }
 }


/**
 *  Prevzaty kod pro zakaz oznacovani textu pri drag&drop operaci.
 */

/***********************************************
* Disable Text Selection script- © Dynamic Drive DHTML code library (www.dynamicdrive.com)
* This notice MUST stay intact for legal use
* Visit Dynamic Drive at http://www.dynamicdrive.com/ for full source code
***********************************************/

 function disableSelection(target) {
    if (typeof target.onselectstart!="undefined") //IE route
        target.onselectstart = function() { return false }
    else if (typeof target.style.MozUserSelect != "undefined") //Firefox route
        target.style.MozUserSelect="none"
    else //All other route (ie: Opera)
        target.onmousedown = function() { return false }
    target.style.cursor = "default"
 }