/* transMenu - v0.1.5 (2007-07-07) * Copyright (c) 2007 Roman Weich * http://p.sohei.org * */ (function($) { var defaults = { onClick: function(){ $(this).find('>a').each(function(){ if ( this.href ) { window.location = this.href; } }); }, arrow_char: '►', selected_char: '✓', subDelay: 300, direction: 'down', mainDelay: 10 }; var transMenuSettings; $.fn.transMenu = function(options) { var shown = false; var liOffset = 2; transMenuSettings = $.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 $(div).find('ul li').unbind('mouseover', liHoverIn).unbind('mouseout', liHoverOut).unbind('click', transMenuSettings.onClick); $(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 (! $('div').parent().is('.hover')) { return; } //assign events to all div>ul>li-elements $(div).find('ul li').mouseover(liHoverIn).mouseout(liHoverOut).click(transMenuSettings.onClick); //positioning if (! $(div).parent().is('.main')) { $(div).css('left', $(div).parent().parent().width() - liOffset); } if (transMenuSettings.direction == 'up') { $(div).css('top', ($(div).height() * -1) + $(div).parent().parent().height()); } div.isVisible = true; //we use this over :visible to speed up traversing $(div).show(); 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) { $(this).addClass('hover').siblings('li.hover').removeClass('hover'); if ( shown ) { hoverIn(this, transMenuSettings.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, transMenuSettings.subDelay); }; var hoverIn = function(li, delay) { //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'); //clear show-div timer if ( div && div.timer && !div.isVisible ) { 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'); var innerDiv = $(li).children('div'); innerDiv = innerDiv.length ? innerDiv[0] : null; //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 ) { //return only if the target is no direct child of this if ( !isChild(this, e.target) ) { 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 mainHoverOut = function(e) { //no need to test e.target==this, as no child has the same event bound 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'); //menuitem has no submenu, so dont remove the hover if the mouse goes outside the menu } else if ( !div && relTarget ) { p = $(e.target).parents('UL.trans_menu'); if ( p.contains(relTarget)) { $(this).removeClass('hover'); } } else if ( relTarget ) { //remove hover only when moving to anywhere inside the trans_menu p = $(e.target).parents('UL.trans_menu'); if ( !div.isVisible && (p.contains(relTarget)) ) { $(this).removeClass('hover'); } } }; var mainClick = function() { var div = getOneChild(this, 'DIV'); //clicked on an open main-menu-item if ( div && div.isVisible ) { clean(); $(this).addClass('hover'); } else { hoverIn(this, transMenuSettings.mainDelay); shown = true; $('ul.trans_menu li').addClass('active'); $(document).bind('mousedown', checkMouse); } }; var checkMouse = function(e) { //is the mouse inside a trans_menu? if yes, is it an open (the current) one? var vis = false; $(e.target).parents('UL.trans_menu').find('div').each( function(){ if ( this.isVisible ) { vis = true; } }); if ( !vis ) { clean(); } }; var clean = function() { //remove timeout and hide the divs $('ul.trans_menu div.outerbox').each(function(){ if ( this.timer ) { clearTimeout(this.timer); this.timer = null; } if ( this.isVisible ) { $(this).hide(); this.isVisible = false; } }); $('ul.trans_menu li').removeClass('hover'); //remove events $('ul.trans_menu>li li').unbind('mouseover', liHoverIn).unbind('mouseout', liHoverOut).unbind('click', transMenuSettings.onClick); $(document).unbind('mousedown', checkMouse); shown = false; $('ul.trans_menu li').removeClass('active'); }; 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 isChild = function(elem, childElem) { var n = elem.firstChild; for ( ; n; n = n.nextSibling ) { if ( n == childElem ) { return true; } } return false; }; return this.each(function() { //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); }; } if (! $(this).is('.trans_menu')) { $(this).addClass('trans_menu'); } //add shadows $('ul', this).shadowBox(); //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 = $(this).children('li'); for ( var j = 0; j < liElems.length; j++ ) { if ( getOneChild(getOneChild(getOneChild(liElems[j], 'DIV'), 'UL'), 'LI') ) { $(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 ( transMenuSettings.arrow_char ) { var arrow_markup = $("" + transMenuSettings.arrow_char + ''); // Mozilla float/position hack if ($.browser.mozilla) { arrow_markup.css('margin-top', '-13px'); } $('div.inner div.outerbox', this).before(arrow_markup); } //the floating list elements are destroying the layout..so make it nice again.. $(this).wrap('
').after(''); }); }; $.fn.transMenu.setDefaults = function(o) { $.extend(defaults, o); }; $.fn.shadowBox = function() { return this.each(function() { var outer = $('').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(''); }); }; $.fn.selectMenuItem = function() { if (this.find('span.selected').length == 0) { this.prepend($("" + transMenuSettings.selected_char + "")); } return this; }; $.fn.deselectMenuItem = function() { return this.find('span.selected').remove(); }; $.fn.menuItemIsSelected = function() { return (this.find('span.selected').length > 0); }; $.fn.deselectMenuSiblings = function() { this.parent().find('span.selected').remove(); this.selectMenuItem(); return this; }; })(jQuery);