﻿/* clickMenu - v0.1.6
* Copyright (c) 2007 Roman Weich
* http://p.sohei.org
*
* Changelog: 
* v 0.1.6 - 2007-09-06
*	-fix: having a link in the top-level menu would not open the menu but call the link instead
* v 0.1.5 - 2007-07-07
*	-change/fix: menu opening/clofsing now through simple show() and hide() calls - before fadeIn and fadeOut were used for which extra functions to stop a already running animation were created -> they were 
*			buggy (not working with the interface plugin in jquery1.1.2 and not working with jquery1.1.3 at all) and now removed
*	-change: removed option: fadeTime
*	-change: now using the bgiframe plugin for adding iframes in ie6 when available
* v 0.1.4 - 2007-03-20
*	-fix: the default options were overwritten by the context related options
*	-fix: hiding a submenu all hover- and click-events were unbound, even the ones not defined in this plugin - unbinding should work now
* v 0.1.3 - 2007-03-13
*	-fix: some display problems ie had when no width was set on the submenu, so on ie the width for each submenu will be explicitely set
*	-fix: the fix to the ie-width-problem is a fix to the "ie does not support css min-width stuff" problem too which displayed some submenus too narrow (it looked just not right)
*	-fix: some bugs, when user the was too fast with the mouse
* v 0.1.2 - 2007-03-11
*	-change: made a lot changes in the traversing routines to speed things up (having better memory usage now as well)
*	-change: added $.fn.clickMenu.setDefaults() for setting global defaults
*	-fix: hoverbug when a main menu item had no submenu
*	-fix: some bugs i found while rewriting most of the stuff
* v 0.1.1 - 2007-03-04
*	-change: the width of the submenus is no longer fixed, its set in the plugin now
*	-change: the submenu-arrow is now an img, not the background-img of the list element - that allows better positioning, and background-changes on hover (you have to set the image through the arrowSrc option)
*	-fix: clicking on a clickMenu while another was already open, didn't close the open one
*	-change: clicking on the open main menu item will close it
*	-fix: on an open menu moving the mouse to a main menu item and moving it fastly elsewere hid the whole menu
* v 0.1.0 - 2007-03-03
*/

(function ($) {
    var defaults = {
        onClick: function () {
            $(this).find('>a').each(function () {
                if (this.href) {
                    window.location = this.href;
                }
            });
        },
        arrowSrc: '/images_srv/icons/right.png',
        subDelay: 300,
        mainDelay: 10,
        mouseOver: false,
        onComplete: null
    };
    $.fn.clickMenu = function (options) {
        var shown = false;
        var liOffset = (($.browser.msie) ? 4 : 2);
        var settings = $.extend({}, defaults, options);
        var hideDIV = function (div, delay) {
            //a timer running to show the div?
            if (div.timer && !div.isVisible) {
                clearTimeout(div.timer);
            }
            else if (div.timer) {
                return; //hide-timer already running
            }
            if (div.isVisible) {
                div.timer = setTimeout(function () {
                    //remove events
                    $(getAllChilds(getOneChild(div, 'UL'), 'LI')).unbind('mouseover', liHoverIn).unbind('mouseout', liHoverOut).unbind('click', settings.onClick);
                    //hide it
                    $(div).hide();
                    div.isVisible = false;
                    div.timer = null;
                }, delay);
            }
        };

        var showDIV = function (div, delay) {
            if (div.timer) {
                clearTimeout(div.timer);
            }
            if (!div.isVisible) {
                div.timer = setTimeout(function () {
                    //check if the mouse is still over the parent item - if not dont show the submenu
                    if (!checkClass(div.parentNode, 'hover')) {
                        return;
                    }
                    //assign events to all div>ul>li-elements
                    $(getAllChilds(getOneChild(div, 'UL'), 'LI')).mouseover(liHoverIn).mouseout(liHoverOut).click(settings.onClick);
                    //positioning
                    if (!checkClass(div.parentNode, 'main')) {
                        $(div).css('left', div.parentNode.offsetWidth - liOffset);
                    }
                    //show it
                    div.isVisible = true; //we use this over :visible to speed up traversing
                    $(div).show();
                    if ($.browser.msie) //fixing a display-bug in ie6 and adding min-width
                    {
                        var cW = $(getOneChild(div, 'UL')).width();
                        if (cW < 100) {
                            cW = 100;
                        }
                        $(div).css('width', cW);
                    }
                    div.timer = null;
                }, delay);
            }
        };

        //same as hover.handlehover in jquery - just can't use hover() directly - need the ability to unbind only the one hover event
        var testHandleHover = function (e) {
            // Check if mouse(over|out) are still within the same parent element
            var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
            // Traverse up the tree
            while (p && p != this) {
                try {
                    p = p.parentNode;
                }
                catch (e) {
                    p = this;
                }
            }
            // If we actually just moused on to a sub-element, ignore it
            if (p == this) {
                return false;
            }
            return true;
        };

        var mainHoverIn = function (e) {
            //no need to test e.target==this, as no child has the same event binded
            //its possible, that a main menu item still has hover (if it has no submenu) - thus remove it
            var lis = getAllChilds(this.parentNode, 'LI');
            var pattern = new RegExp("(^|\\s)hover(\\s|$)");
            for (var i = 0; i < lis.length; i++) {
                if (pattern.test(lis[i].className)) {
                    $(lis[i]).removeClass('hover');
                }
            }
            $(this).addClass('hover');
            if (shown) {
                hoverIn(this, settings.mainDelay);
            }
        };

        var liHoverIn = function (e) {
            if (!testHandleHover(e)) {
                return false;
            }
            if (e.target != this) {
                //look whether the target is a direct child of this (maybe an image)
                if (!isChild(this, e.target)) {
                    return;
                }
            }
            hoverIn(this, settings.subDelay);
        };

        var hoverIn = function (li, delay) {
            var innerDiv = getOneChild(li, 'DIV');
            //stop running timers from the other menus on the same level - a little faster than $('>*>div', li.parentNode)
            var n = li.parentNode.firstChild;
            for (; n; n = n.nextSibling) {
                if (n.nodeType == 1 && n.nodeName.toUpperCase() == 'LI') {
                    var div = getOneChild(n, 'DIV');
                    if (div && div.timer && !div.isVisible) //clear show-div timer
                    {
                        clearTimeout(div.timer);
                        div.timer = null;
                    }
                }
            }
            //is there a timer running to hide one of the parent divs? stop it
            var pNode = li.parentNode;
            for (; pNode; pNode = pNode.parentNode) {
                if (pNode.nodeType == 1 && pNode.nodeName.toUpperCase() == 'DIV') {
                    if (pNode.timer) {
                        clearTimeout(pNode.timer);
                        pNode.timer = null;
                        $(pNode.parentNode).addClass('hover');
                    }
                }
            }
            //highlight the current element
            $(li).addClass('hover');
            //is the submenu already visible?
            if (innerDiv && innerDiv.isVisible) {
                //hide-timer running?
                if (innerDiv.timer) {
                    clearTimeout(innerDiv.timer);
                    innerDiv.timer = null;
                }
                else {
                    return;
                }
            }
            //hide all open menus on the same level and below and unhighlight the li item (but not the current submenu!)
            $(li.parentNode.getElementsByTagName('DIV')).each(function () {
                if (this != innerDiv && this.isVisible) {
                    hideDIV(this, delay);
                    $(this.parentNode).removeClass('hover');
                }
            });
            //show the submenu, if there is one
            if (innerDiv) {
                showDIV(innerDiv, delay);
            }
        };

        var liHoverOut = function (e) {
            if (!testHandleHover(e)) {
                return false;
            }
            if (e.target != this) {
                if (!isChild(this, e.target)) //return only if the target is no direct child of this
                {
                    return;
                }
            }
            //remove the hover from the submenu item, if the mouse is hovering out of the menu (this is only for the last open (levelwise) (sub-)menu)
            var div = getOneChild(this, 'DIV');
            if (!div) {
                $(this).removeClass('hover');
            }
            else {
                if (!div.isVisible) {
                    $(this).removeClass('hover');
                }
            }
        };

        var mainHover = function () {
            hoverIn(this, settings.mainDelay);
            shown = true;
            $(document).bind("mousedown", checkMouse);
            return false;
        }

        var mainHoverOut = function (e) {
            //no need to test e.target==this, as no child has the same event binded
            //remove hover
            var div = getOneChild(this, 'DIV');
            var relTarget = e.relatedTarget || e.toElement; //this is undefined sometimes (e.g. when the mouse moves out of the window), so dont remove hover then
            var p;
            if (!shown) {
                $(this).removeClass('hover');
            }
            else if (!div && relTarget) //menuitem has no submenu, so dont remove the hover if the mouse goes outside the menu
            {
                p = findParentWithClass(e.target, 'UL', 'clickMenu');
                if (p.contains(relTarget)) {
                    $(this).removeClass('hover');
                }
            }
            else if (relTarget) {
                //remove hover only when moving to anywhere inside the clickmenu
                p = findParentWithClass(e.target, 'UL', 'clickMenu');
                if (!div.isVisible && (p.contains(relTarget))) {
                    $(this).removeClass('hover');
                }
            }
        };

        var mainClick = function () {
            var div = getOneChild(this, 'DIV');
            if (div && div.isVisible) //clicked on an open main-menu-item
            {
                clean();
                $(this).addClass('hover');
            }
            else {
                hoverIn(this, settings.mainDelay);
                shown = true;
                $(document).bind('mousedown', checkMouse);
            }
            return false;
        };

        var checkMouse = function (e) {
            //is the mouse inside a clickmenu? if yes, is it an open (the current) one?
            var vis = false;
            var cm = findParentWithClass(e.target, 'UL', 'clickMenu');
            if (cm) {
                $(cm.getElementsByTagName('DIV')).each(function () {
                    if (this.isVisible) {
                        vis = true;
                    }
                });
            }
            if (!vis) {
                clean();
            }
        };

        var clean = function () {
            //remove timeout and hide the divs
            $('ul.clickMenu div.outerbox').each(function () {
                if (this.timer) {
                    clearTimeout(this.timer);
                    this.timer = null;
                }
                if (this.isVisible) {
                    $(this).hide();
                    this.isVisible = false;
                }
            });
            $('ul.clickMenu li').removeClass('hover');
            //remove events
            $('ul.clickMenu>li li').unbind('mouseover', liHoverIn).unbind('mouseout', liHoverOut).unbind('click', settings.onClick);
            $(document).unbind('mousedown', checkMouse);
            shown = false;
        };

        var getOneChild = function (elem, name) {
            if (!elem) {
                return null;
            }
            var n = elem.firstChild;
            for (; n; n = n.nextSibling) {
                if (n.nodeType == 1 && n.nodeName.toUpperCase() == name) {
                    return n;
                }
            }
            return null;
        };

        var getAllChilds = function (elem, name) {
            if (!elem) {
                return [];
            }
            var r = [];
            var n = elem.firstChild;
            for (; n; n = n.nextSibling) {
                if (n.nodeType == 1 && n.nodeName.toUpperCase() == name) {
                    r[r.length] = n;
                }
            }
            return r;
        };

        var findParentWithClass = function (elem, searchTag, searchClass) {
            var pNode = elem.parentNode;
            var pattern = new RegExp("(^|\\s)" + searchClass + "(\\s|$)");
            for (; pNode; pNode = pNode.parentNode) {
                if (pNode.nodeType == 1 && pNode.nodeName.toUpperCase() == searchTag && pattern.test(pNode.className)) {
                    return pNode;
                }
            }
            return null;
        };

        var checkClass = function (elem, searchClass) {
            var pattern = new RegExp("(^|\\s)" + searchClass + "(\\s|$)");
            if (pattern.test(elem.className)) {
                return true;
            }
            return false;
        };

        var isChild = function (elem, childElem) {
            var n = elem.firstChild;
            for (; n; n = n.nextSibling) {
                if (n == childElem) {
                    return true;
                }
            }
            return false;
        };

        var TotalItems = $(this).length - 1;
        return this.each(function (idx) {
            //add .contains() to mozilla - http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
            if (window.Node && Node.prototype && !Node.prototype.contains) {
                Node.prototype.contains = function (arg) {
                    return !!(this.compareDocumentPosition(arg) & 16);
                };
            }
            //add class
            if (!checkClass(this, 'clickMenu')) {
                $(this).addClass('clickMenu');
            }
            //add shadows
            $('ul', this).shadowBox();
            //ie6? - add iframes
            if ($.browser.msie && (!$.browser.version || parseInt($.browser.version) <= 6)) {
                if ($.fn.bgiframe) {
                    $('div.outerbox', this).bgiframe();
                }
                else {
                    /* thanks to Mark Gibson - http://www.nabble.com/forum/ViewPost.jtp?post=6504414&framed=y */
                    $('div.outerbox', this).append('<iframe style="display:block;position:absolute;top:0;left:0;z-index:-1;filter:mask();' +
									'width:expression(this.parentNode.offsetWidth);height:expression(this.parentNode.offsetHeight)"/>');
                }
            }
            //assign events
            $(this).bind('closemenu', function () { clean(); }); //assign closemenu-event, through wich the menu can be closed from outside the plugin
            //add click event handling, if there are any elements inside the main menu
            var liElems = getAllChilds(this, 'LI');
            for (var j = 0; j < liElems.length; j++) {
                if (getOneChild(getOneChild(getOneChild(liElems[j], 'DIV'), 'UL'), 'LI')) // >div>ul>li
                {
                    if (settings.mouseOver) {
                        $(liElems[j]).mouseover(mainHover);
                    }
                    else {
                        $(liElems[j]).click(mainClick);
                    }
                }
            }
            //add hover event handling and assign classes
            $(liElems).hover(mainHoverIn, mainHoverOut).addClass('main').find('>div').addClass('inner');
            //add the little arrow before each submenu
            if (settings.arrowSrc) {
                //$('div.inner div.outerbox', this).before('<img src="' + settings.arrowSrc + '" class="liArrow" />').parent().addClass("parent");
                $('div.inner div.outerbox', this).parent().addClass("parent");
            }

            //the floating list elements are destroying the layout..so make it nice again..
            $(this).wrap('<div class="cmDiv"></div>').after('<div style="clear: both; visibility: hidden;"></div>');

            AA.Console.Log("Item " + idx + " of " + TotalItems);

            if (idx === TotalItems && settings.onComplete) {
                settings.onComplete();
            }

        });
    };
    $.fn.clickMenu.setDefaults = function (o) {
        $.extend(defaults, o);
    };
})(jQuery);

(function($) {
    $.fn.shadowBox = function() {
        return this.each(function() {
            var outer = $('<div class="outerbox"></div>').get(0);
            if ($(this).css('position') == 'absolute') {
                //if the child(this) is positioned abolute, we have to use relative positioning and shrink the outerbox accordingly to the innerbox
                $(outer).css({ position: 'relative', width: this.offsetWidth, height: this.offsetHeight });
            }
            else {
                //shrink the outerbox
                $(outer).css('position', 'absolute');
            }
            //add the boxes
            $(this).addClass('innerBox').wrap(outer).
					before('<div class="shadowbox1"></div><div class="shadowbox2"></div><div class="shadowbox3"></div>');
        });
    };
})(jQuery);
