/** 
* DHTML Drop down menu
*
* This is a simple drop down menu whose layout is managed via CSS. It 
*  is also accessible. You can tab through the menu. 
* 
* Menu allows limited customization in the way it shows submenus and 
* marking steps. 
*
* Compatible:
* I have tested the menu under IE6+, Firefox and Opera. Works also on 
* IE 5.5 but there are some problems with CSS (padding and width) 
* 
* Please let me know if it works on other browsers. 
*
* @author Ivars Veksins (ivars [at] gmail [dot] com)
*
* IF YOU EVER USE THIS CODE PLEASE LEAVE THE HEADER AS 
* IT IS. IT WOULD BE NICE IF YOU WOULD LET ME KNOW 
* YOU ARE USING THIS DROP DOWN MENU BY E-MAILING ME.
* 
* ENJOY!
*/


/** 
* Create a super class where we are going to extend 
* our drop down menu functions. 
*/
function DHTML_Popup() {
}

// implement garbage collector 
// garbage collector will call super functions dispose function 
// asking to release all allocated memory 
IMPLEMENT_GARBAGE(DHTML_Popup)

// settings configured by the user 
DHTML_Popup.DELAY = 500;
DHTML_Popup.ID = "navigation";
DHTML_Popup.FLAGS = DHTML_Popup.NORMAL;

// do not touch these settings
// by changing these you can break the functionality 
DHTML_Popup.TIMER = 0;

// status a menu can have
DHTML_Popup.HIDE = 1;
DHTML_Popup.SHOW = 2;
DHTML_Popup.HALT = 4;
DHTML_Popup.MARK = 8;


// variables where we save DOM references 
// in HTML page. 
DHTML_Popup.MENUS = null;
DHTML_Popup.ITEMS = Array();

/** 
* Garbage collector implementation. Release all allocated memory. 
* As I understand global variables and DOM references have to deleted. 
* 
* IE leaks as hell, therefore do whatever you can. 
*/
DHTML_Popup.dispose = function() {
    // release all global properties
    DHTML_Popup.DELAY = null;
    DHTML_Popup.ID = null;
    DHTML_Popup.HIDE = null;
    DHTML_Popup.SHOW = null;
    DHTML_Popup.HALT = null;

    // set effect flags to null
    DHTML_Popup.NORMAL = null;
    DHTML_Popup.SCROLL_IN = null;
    DHTML_Popup.SCROLL_OUT = null;
    DHTML_Popup.SCROLL_INOUT = null;
    DHTML_Popup.FADE_IN = null;
    DHTML_Popup.FADE_OUT = null;
    DHTML_Popup.FADE_INOUT = null;

    // clear main timer 
    if (DHTML_Popup.TIMER)
        clearTimeout(DHTML_Popup.TIMER);

    DHTML_Popup.MENUS = null;
    var items = DHTML_Popup.ITEMS;
    for (var i = 0; i < items.length; i++) {
        var item = items[i];

        // delete all references 
        item.$holding_menus = null;
        item.$owner = null;

        item = null;
    }

    DHTML_Popup.ITEMS = null;
}

/**
* Function called onload event to lookup for all the unordered lists 
* and apply all the nessecery events and start the main timer which will show and hide 
* menus. 
*/
DHTML_Popup.install = function() {

    // get main container which holds all menus 
    // (DIV element user specified) 
    var container = DHTML_Popup.get_container();

    if (container == null)
        return false;

    // get all menus container contains 
    var menus = container.getElementsByTagName("UL");

    // go through all menus and install events 
    for (var i = 0; i < menus.length; i++) {
        // current menu 
        var menu = menus[i];

        // status what is happening with the menu 
        // default state is to hide 
        menu.$status = DHTML_Popup.HIDE;
        menu.$visible = false;

        // events which will change menu state to show or hide
        event_install(menu, "onmouseover", DHTML_Popup.mouse_over_menu);
        event_install(menu, "onmouseout", DHTML_Popup.mouse_out_menu);

        // check wether the menu is root menu 
        if (i == 0)
            menu.$root = true;

        var items = menu.childNodes;
        var last_valid_item = null;

        // go through all items in the menu and set their 
        // events and any properties
        for (var n = 0; n < items.length; n++) {
            var item = items[n];

            if (!item.innerHTML)
                continue;

            last_valid_item = item;

            // set reference to owner menu 
            item.$owner = menu;

            // install events to show menu only if the item has any menus 
            if (DHTML_Popup.item_has_menu(item)) {

                // install events which check if the sub menu has to be showed or not 
                event_install(item, "onmouseover", DHTML_Popup.mouse_over_item);
                event_install(item, "onmouseout", DHTML_Popup.mouse_out_item);

                // manage keyboard with the same actions 
                // only anchors receive focus and blur properly
                var anchor = item.getElementsByTagName("A");

                if (anchor.length > 0) {
                    // set reference to item 
                    event_install(anchor[0], "onfocus", DHTML_Popup.item_focused);
                    event_install(anchor[0], "onclick", DHTML_Popup.item_clicked);
                }

                // save references to items
                DHTML_Popup.ITEMS.push(item);
            }

        }

        // when the last item is blured the holding menu has to be set to hidden 
        // last valid item handles the onblur event
        if (last_valid_item) {
            var anchor = last_valid_item.getElementsByTagName("A");

            if (anchor.length > 0)
                event_install(anchor[0], "onblur", DHTML_Popup.item_blured);
        }


    }


    // save reference for later use
    DHTML_Popup.MENUS = menus;

    // start worker "thread" 
    DHTML_Popup.start_worker();

    // delete container reference 
    container = null;
    return true;
}

event_install(window, "onload", DHTML_Popup.install);



/** 
* Keyboard accessible event which is trigered when a anchor receives 
* a focus. On focus we are deciding which menus to show or to hide
*/
DHTML_Popup.item_focused = function() {
    var item = this.parentNode;

    // fast tabbing causes to open to many menus 
    // check if is menu set to visible and not visible yet 
    var menus = DHTML_Popup.MENUS;

    for (var i = 0; i < menus.length; i++) {
        var menu = menus[i];

        if (menu.$root) continue;

        if (item.$owner.$root) {
            menu.$status = DHTML_Popup.HIDE;
            DHTML_Popup.menu_hide(menu);
        } else {
            // hide menu if set to hide and not hidden 
            if (menu.$status & DHTML_Popup.HIDE && menu.$visible)
                DHTML_Popup.menu_hide(menu);

            // hide menus which are set to show but not showed 
            if (menu.$status & DHTML_Popup.SHOW && !menu.$visible)
                menu.$status = DHTML_Popup.HIDE;
        }
    }

    // activate new item 
    var holding = item.$holding_menus[0];

    if (holding)
        holding.$status = DHTML_Popup.HALT | DHTML_Popup.SHOW | DHTML_Popup.MARK;

}

/** 
* Focusing and bluring breaks the functionality if using mouse 
* and user clicks a lot. 
* A little hack which checks whether it is a click or a tab. 
*/
DHTML_Popup.item_clicked = function() {
    var owner = this.parentNode.$owner;
    if (!owner)
        return;

    owner.$clicked = true;
}

/** 
* Keyboard accessible event triggered when an anchor 
* looses its focus. 
*/
DHTML_Popup.item_blured = function() {
    var owner = this.parentNode.$owner;

    if (!owner)
        return;

    if (owner.$clicked) {
        owner.$clicked = false;
        return;
    }

    // root menu doesn't have to be hidden 
    if (owner.$root)
        return;

    var holding = this.parentNode.$holding_menus;

    owner.style.zIndex = '1';

    if (holding && holding[0].$status & DHTML_Popup.SHOW) {
        // tell to hide all
        holding[0].$hide = owner;

    } else {
        if (owner.$hide)
            owner.$hide.$status = DHTML_Popup.HIDE;

        owner.$status = DHTML_Popup.HALT | DHTML_Popup.HIDE;
    }

    // delete reference
    owner = null;
}


/** 
* Gets a container which holds all unordered lists 
* @return DOM reference
*/
DHTML_Popup.get_container = function() {
    var container = document.getElementById(DHTML_Popup.ID);

    if (container == null)
        ctad_error("DHTML_Popup container can not be found.");

    return container;
}


/** 
* function which goes through all menus in container and checks 
* whether they have to be hidden or displayed. 
* 
* This function is executed in a delay specified 
* DHTML_Popup.DELAY
*/
DHTML_Popup.start_worker = function() {
    var menus = DHTML_Popup.MENUS;

    if (!menus)
        return;

    // loop through all elements 
    for (var i = 0; i < menus.length; i++) {

        var menu = menus[i];

        // root menu never should be hidden 
        if (menu.$root)
            continue;


        // check wether holding menu item has to be marked 
        // or unmarked 
        if (menu.$status & DHTML_Popup.MARK) {
            DHTML_Popup.mark(menu.parentNode);
        } else {
            DHTML_Popup.unmark(menu.parentNode);
        }

        // check menu needs to be hidden or showed 
        if (menu.$status & DHTML_Popup.HIDE) {
            DHTML_Popup.menu_hide(menu);
        } else if (menu.$status & DHTML_Popup.SHOW) {
            DHTML_Popup.menu_show(menu);
        }
    }

    DHTML_Popup.TIMER = setTimeout('DHTML_Popup.start_worker()', DHTML_Popup.DELAY);
}

/** 
* Sets menu to invisible mode. 
* @argument menu which needs to be hidden 
*/
DHTML_Popup.menu_hide = function(menu) {

    if (!menu.$visible)
        return;

    DHTML_Popup.custom_hide(menu);
    menu.$visible = false;
}


/** 
* Sets menu to visible mode. 
* @argument menu to be set to visible 
*/
DHTML_Popup.menu_show = function(menu) {
    if (menu.$visible)
        return;

    // ie doesn't hide menus when not visible 
    // therefore we have to hide them ourselfs 
    // (BUG)
    var menus = menu.getElementsByTagName("UL");
    for (var i = 0; i < menus.length; i++) {
        var m = menus[i];

        m.style.display = 'block';
        m.style.display = 'none';
    }

    DHTML_Popup.custom_show(menu);
    menu.$visible = true;
};


/** 
* When mouse is over an item set its holding menu status 
* to show. The main worker is going to show it. 
*/
DHTML_Popup.mouse_over_item = function() {
    // get menu to show 
    var menu = this.$holding_menus[0];

    // set menu status to show 
    if (menu.$status & DHTML_Popup.HIDE)
        menu.$status = (menu.$status & ~DHTML_Popup.HIDE) | DHTML_Popup.SHOW | DHTML_Popup.MARK;

    // change z order
    menu.style.zIndex = '12';

    DHTML_Popup.restart_worker();


}

/** 
* When mouse is outside the item set its holding menu status 
* to be hidden. Again, the main worker hides it. 
*/
DHTML_Popup.mouse_out_item = function() {
    // get menu to hide 
    var menu = this.$holding_menus[0];

    if (menu.$status & DHTML_Popup.SHOW)
        menu.$status = (menu.$status & ~(DHTML_Popup.SHOW | DHTML_Popup.MARK)) | DHTML_Popup.HIDE;

    // change z order 
    menu.style.zIndex = '1';

    DHTML_Popup.restart_worker();
}


/**  
* Event handler when mouse is over menu 
* Sets status to halt to true. Forces menu to be dislpayed (locks it)
*/
DHTML_Popup.mouse_over_menu = function() {
    if (this.$status & DHTML_Popup.HALT)
        return;

    this.$status |= DHTML_Popup.HALT;
    DHTML_Popup.restart_worker();
}


/**  
* Removes the lock from menu. Menu can be hidden by setting
* HIDE flag. 
*/
DHTML_Popup.mouse_out_menu = function() {
    if (this.$status & DHTML_Popup.HALT)
        this.$status &= ~DHTML_Popup.HALT;

    DHTML_Popup.restart_worker();
}



/** 
* Stops main working "thread" and 
* starts from begining 
*/
DHTML_Popup.restart_worker = function() {
    clearTimeout(DHTML_Popup.TIMER);
    DHTML_Popup.TIMER = setTimeout('DHTML_Popup.start_worker()', DHTML_Popup.DELAY);
}

/** 
* Checks wether an item has another level 
* menu inside. 
* 
* @argument LI DOM reference 
* @return status 
*/
DHTML_Popup.item_has_menu = function(item) {
    var menus = item.getElementsByTagName("UL");

    if (menus == null || menus.length == 0)
        return false;

    // sets reference with menus inside the item 
    item.$holding_menus = menus;

    return true;
}


/** 
* Overridable function for marking active steps
* @argument item to mark 
*/
DHTML_Popup.mark = function(item) {
}

/** 
* Overridable function which resets the item to normal state. 
* @argument item to unmark 
*/
DHTML_Popup.unmark = function(item) {
}


/**
* Function which performs custom show on menu. 
* This function can also be overrided 
*/
DHTML_Popup.custom_show = function(menu) {
    menu.style.display = 'block';
}

/**
* Function which performs a custom hide on menu 
* This function can also be overrided 
*/
DHTML_Popup.custom_hide = function(menu) {
    menu.style.display = 'none';
}

// --- EFFECTS --- // 


// scrolling effect 
function Scrolling() {
}

IMPLEMENT_GARBAGE(Scrolling);

// there can be multiple menus to be hidden but only one has to be shown at 
// time. Therefore show menu reference is a direct reference, but menus to hide 
// array 
Scrolling.$hidden = [];
Scrolling.$active_out = null;

Scrolling.$show = null;

// scrolling step 
Scrolling.$step_size = 10;


Scrolling.dispose = function() {
    Scrolling.$hidden = null;
    Scrolling.$active_out = null;
    Scrolling.$show = null;
    Scrolling.$step_size = null;
}

// scrolls from 1 to the original height of menu 
Scrolling.do_scrolling_in = function() {
    var menu = Scrolling.$show;
    if (!menu)
        return;

    if (menu.clientHeight < menu.$original_height) {
        menu.style.height = (menu.clientHeight + Scrolling.$step_size) + 'px';
        setTimeout('Scrolling.do_scrolling_in()', 10);

    } else {
        // finished scrolling 

        menu.style.height = 'auto';
        menu.style.overflow = 'visible';

        Scrolling.$show = null;
    }
}

// scroll froms original height to 1 
Scrolling.do_scrolling_out = function() {
    var menu = Scrolling.$active_out;

    if (!menu)
        return;

    menu.$height -= Scrolling.$step_size;

    if (menu.$height > 0) {
        menu.style.height = menu.$height + 'px';
        setTimeout('Scrolling.do_scrolling_out()', 10);
    } else {

        menu.style.display = 'none';

        menu.style.overflow = 'visible';
        menu.style.height = menu.$original_height + 'px';

        // check for next element 
        Scrolling.$active_out = Scrolling.$hidden.pop();

        if (Scrolling.$active_out)
            Scrolling.do_scrolling_out();
    }
}

// indicates that a menu has to be scrolled in 
// prepares menu and starts scrolling 
Scrolling.scroll_in = function(menu) {
    // have to set to be displayed as block 
    // otherwise the height is incorrect
    menu.style.display = 'block';

    // save the original height 
    if (!menu.$original_height)
        menu.$original_height = menu.clientHeight;

    // otherwise anchors are as they are 
    menu.style.overflow = 'hidden';
    menu.style.height = '1px';

    // global reference 
    Scrolling.$show = menu;

    // start scrolling 
    Scrolling.do_scrolling_in();
}

// indicates that the menu has to be hidden 
// and asks to start scrolling out 
Scrolling.scroll_out = function(menu) {
    menu.style.overflow = 'hidden';

    if (!menu.$original_height)
        menu.$original_height = menu.clientHeight;

    menu.$height = menu.$original_height;

    Scrolling.$hidden.push(menu);

    if (!Scrolling.$active_out) {
        Scrolling.$active_out = Scrolling.$hidden.pop();
        Scrolling.do_scrolling_out();
    }
}
