Ver código fonte

New implementation of the "Actions" Javascript context menu. Simpler, easier to customize and more robust.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@408 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 15 anos atrás
pai
commit
3fb03675ba

+ 3 - 3
application/displayblock.class.inc.php

@@ -954,14 +954,14 @@ class MenuBlock extends DisplayBlock
 				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); }
 			}
 		}
-		$sHtml .= "<div class=\"jd_menu_itop\"><ul class=\"jd_menu jd_menu_itop\">\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
+		$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
 		foreach ($aActions as $aAction)
 		{
 			$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
-			$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n<li>\n";
+			$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n";
 		}
 		$sHtml .= "</ul>\n</li>\n</ul></div>\n";
-		$oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n");
+		$oPage->add_ready_script("$(\"div.itop_popup>ul\").popupmenu();\n");
 		return $sHtml;
 	}	
 }

+ 2 - 1
application/itopwebpage.class.inc.php

@@ -31,7 +31,8 @@ class iTopWebPage extends NiceWebPage
 		$this->add_linked_script("../js/jquery.treeview.js");
 		$this->add_linked_script("../js/jquery.autocomplete.js");
 		$this->add_linked_script("../js/jquery.bgiframe.js");
-		$this->add_linked_script("../js/jquery.jdMenu.js");
+		$this->add_linked_script("../js/jquery.positionBy.js");
+		$this->add_linked_script("../js/jquery.popupmenu.js");
 		$this->add_linked_script("../js/date.js");
 		$this->add_linked_script("../js/jquery.date.picker.js");
 		$this->add_linked_script("../js/jquery.tablesorter.min.js");

+ 51 - 100
css/light-grey.css

@@ -354,131 +354,82 @@ div.iTopLogo span {
     padding:5px 0px 16px 20px;
 }
 
-/* jdMenu popup menus */
-ul.jd_menu, 
-ul.jd_menu_vertical {
-	margin: 0px;
-	padding: 0px;
-	list-style-type: none;
-}
-ul.jd_menu ul,
-ul.jd_menu_vertical ul {
-	display: none;
-}
-ul.jd_menu li {
-	float: left;
-}
-
-/* -- Sub-Menus -- */
-ul.jd_menu ul,
-ul.jd_menu_vertical ul {
-	position: absolute;
-	display: none;
-	list-style-type: none;
-	margin: 0px;
-	padding: 0px;
-	z-index: 10000;
-}
-ul.jd_menu ul li,
-ul.jd_menu_vertical ul li {
-	float: none;
-	margin: 0px;
+/* popup menus */
+div.itop_popup {
+	margin: 0;
+	padding: 0;
+	float:right;
 }
 
-/* jdMenu popup menus styling */
-div.jd_menu_itop {
+div.itop_popup > ul {
 	height:19px;
-	float:right;
-	display:inline;
+	display:block;
 	width:70px; /* Nasty work-around for IE... en attendant mieux */
 	padding-left: 5px;
-	background: url(../images/actions_left.png) no-repeat left;
+	background: url(../images/actions_left.png) no-repeat top left;
+	cursor: pointer;
 }
 
-ul.jd_menu_itop {
+div.itop_popup > ul > li {
+	float: left;
+	list-style: none;
+	font-size: 11px;
+	font-family: Tahoma,sans-serif;
 	height: 19px;
-	padding-right:16px;
-	background: url(../images/actions_right.png) no-repeat right;
+	padding-right: 16px;
+	padding-left: 4px;
+	background: url(../images/actions_right.png) no-repeat top right transparent;
 	font-weight: bold;
+	color: #fff;
+	vertical-align: middle;
 }
-
-ul.jd_menu_vertical {
-	width: 200px;
-	height: auto;
-	clear: both;
-	background: url(gradient-vertical.png) repeat-x;
-	background-color: #A5AFB8;
-}
-
-
-ul.jd_menu_itop a, 
-ul.jd_menu_itop a:active,
-ul.jd_menu_itop a:link,
-ul.jd_menu_itop a:visited {
+	
+.itop_popup li a {
+	display: block;
+	padding: 5px 12px;
 	text-decoration: none;
-	color: #FFF;
-}
-ul.jd_menu_itop ul li a,
-ul.jd_menu_itop ul li a:active,
-ul.jd_menu_itop ul li a:link,
-ul.jd_menu_itop ul li a:visited {
+	noborder: 1px solid white;
+	width: 70px;
 	color: #000;
-}
-ul.jd_menu_itop li {
-	font-family: Tahoma, sans-serif;
-	font-size: 11px;
-	padding: 2px 6px 4px 6px;
-	cursor: pointer;
+	font-weight: bold;
 	white-space: nowrap;
-	color: #FFF;
-}
-ul.jd_menu_itop li.jd_menu_active_menubar,
-ul.jd_menu_itop li.jd_menu_hover_menubar {
-	color: #FFF;
+	background: #fff;
 }
 
-ul.jd_menu_vertical li.jd_menu_active_menubar,
-ul.jd_menu_vertical li.jd_menu_hover_menubar {
-	padding-left: 6px;
-	padding-top: 1px;
-	border-top: 1px solid #70777D;
-	border-left: 0px;
-	border-right: 0px;
+.itop_popup li a:hover {
+	background: #1A4473;
 }
 
-ul.jd_menu_itop ul {
-	background: #d81515;
-	border: 1px solid #70777D;
+.itop_popup ul > li > ul
+{	
+	border: 1px solid black;
+	background: #fff;
 }
-ul.jd_menu_itop ul li {
-	padding: 3px 10px 3px 4px;
-	background: #FFF;
-	border: none;
-	color: #000;
+	
+.itop_popup li > ul
+{	margin: 0;
+	padding: 0;
+	position: absolute;
+	display: none;
+	border-top: 1px solid white;
 }
-ul.jd_menu_itop ul li.jd_menu_active,
-ul.jd_menu_itop ul li.jd_menu_hover {
-	background: #d81515;
-	padding-top: 2px;
-	border-top: 1px solid #ABB5BC;
-	padding-bottom: 2px;
-	border-bottom: 1px solid #929AA1;
-	color: #FFF;
+
+.itop_popup li ul li {
+	float: none;
+	display: inline;
 }
 
-ul.jd_menu_itop li ul li a {
-	padding: 0;
-	background: #fff;
-	padding-left:0;
+.itop_popup li ul li a {
+	width: auto;
 }
 
-ul.jd_menu_itop ul li.jd_menu_active a.jd_menu_active,
-ul.jd_menu_itop ul li.jd_menu_hover a.jd_menu_hover {
-	color: #FFF;
-	background: #d81515;
-	padding-left:0;
+.itop_popup li ul li a:hover {
+	background: #D81515;
+	color: #fff;
+	font-weight: bold;
 }
 
+/************************************/
 .wizHeader {
 	background: #83b217;
 	padding: 15px;

+ 146 - 410
js/jquery.jdMenu.js

@@ -1,5 +1,5 @@
 /*
- * jdMenu 1.3.beta2 (2007-03-06)
+ * jdMenu 1.4.1 (2008-03-31)
  *
  * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
  * Dual licensed under the MIT (MIT-LICENSE.txt)
@@ -7,429 +7,165 @@
  *
  * http://jdsharp.us/
  *
- * Built upon jQuery 1.1.1 (http://jquery.com)
- * This also requires the jQuery dimensions plugin
+ * Built upon jQuery 1.2.1 (http://jquery.com)
+ * This also requires the jQuery dimensions >= 1.2 plugin
  */
-(function($){
-	// This will store an element list of all our menu objects
-	var jdMenu = [];
-	
-	// Public methods
-	$.fn.jdMenu = function(inSettings) {
-		var settings = $.extend({}, arguments.callee.defaults, inSettings);
-		return this.each(function() {
-			jdMenu.push(this);
-			$(this).addClass('jd_menu_flag_root');
-			this.$settings = $.extend({}, settings, {isVerticalMenu: $(this).is('.jd_menu_vertical')});
-			addEvents(this);
-		});
-	};
-	$.fn.jdMenuShow = function() {
-		return this.each(function() {
-			showMenuLI.apply(this);
-		});
-	};
-	$.fn.jdMenuHide = function() {
-		return this.each(function() {
-			hideMenuUL.apply(this);
-		});
-	};
 
-	// Private methods and logic
-	$(window)
-		// Bind a click event to hide all visible menus when the document is clicked
-		.bind('click', function(){
-			$(jdMenu).find('ul:visible').jdMenuHide();
-		})
-		// Cleanup after ourself by nulling the $settings object
-		.bind('unload', function() {
-			$(jdMenu).each(function() {
-				this.$settings = null;
-			});
-		});
+// This initializes the menu
+$(function() {
+	$('ul.jd_menu').jdMenu();
+});
 
-	// These are our default settings for this plugin
-	$.fn.jdMenu.defaults = {
-		activateDelay: 750,
-		showDelay: 150,
-		hideDelay: 550,
-		onShow: null,
-		onHideCheck: null,
-		onHide: null,
-		onAnimate: null,
-		onClick: null,
-		offsetX: 4,
-		offsetY: 2,
-		iframe: $.browser.msie
-	};
-	
-	// Our special parentsUntil method to get all parents up to and including the matched element
-	$.fn.parentsUntil = function(match) {
-		var a = [];
-		$(this[0]).parents().each(function() {
-			a.push(this);
-			return !$(this).is(match);
-		});
-		return this.pushStack(a, arguments);
-	};
-
-	// Returns our settings object for this menu
-	function getSettings(el) {
-		return $(el).parents('ul.jd_menu_flag_root')[0].$settings;
-	}
-
-	// Unbind any events and then rebind them
+(function($){
 	function addEvents(ul) {
-		removeEvents(ul);
+		var settings = $.data( $(ul).parents().andSelf().filter('ul.jd_menu')[0], 'jdMenuSettings' );
 		$('> li', ul)
-			.hover(hoverOverLI, hoverOutLI)
-			.bind('click', itemClick)
-			.find('> a.accessible')
-				.bind('click', accessibleClick);
-	};
-	
-	// Remove all events for this menu
-	function removeEvents(ul) {
-		$('> li', ul)
-			.unbind('mouseover').unbind('mouseout')
-			.unbind('click')
-			.find('> a.accessible')
-				.unbind('click');
-	};
-	
-	function hoverOverLI() {
-		var cls = 'jd_menu_hover' + ($(this).parent().is('.jd_menu_flag_root') ? '_menubar' : '');
-		$(this).addClass(cls).find('> a').addClass(cls);
-		
-		if (this.$timer) {
-			clearTimeout(this.$timer);
-		}
-
-		// Do we have a sub menu?
-		if ($('> ul', this).size() > 0) {
-			var settings = getSettings(this);
-			
-			// Which delay to use, the longer activate one or the shorter show delay if a menu is already visible
-			var delay = ($(this).parents('ul.jd_menu_flag_root').find('ul:visible').size() == 0) 
-							? settings.activateDelay : settings.showDelay;
-			var t = this;
-			this.$timer = setTimeout(function() {
-				showMenuLI.apply(t);
-			}, delay);
-		}
-	};
-	
-	function hoverOutLI() {
-		// Remove both classes so we do not have to test which one we are
-		$(this)	.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
+			.bind('mouseenter.jdmenu mouseleave.jdmenu', function(evt) {
+				$(this).toggleClass('jdm_hover');
+				var ul = $('> ul', this);
+				if ( ul.length == 1 ) {
+					clearTimeout( this.$jdTimer );
+					var enter = ( evt.type == 'mouseenter' );
+					var fn = ( enter ? showMenu : hideMenu );
+					this.$jdTimer = setTimeout(function() {
+						fn( ul[0], settings.onAnimate, settings.isVertical );
+					}, enter ? settings.showDelay : settings.hideDelay );
+				}
+			})
+			.bind('click.jdmenu', function(evt) {
+				var ul = $('> ul', this);
+				if ( ul.length == 1 && 
+					( settings.disableLinks == true || $(this).hasClass('accessible') ) ) {
+					showMenu( ul, settings.onAnimate, settings.isVertical );
+					return false;
+				}
+				
+				// The user clicked the li and we need to trigger a click for the a
+				if ( evt.target == this ) {
+					var link = $('> a', evt.target).not('.accessible');
+					if ( link.length > 0 ) {
+						var a = link[0];
+						if ( !a.onclick ) {
+							window.open( a.href, a.target || '_self' );
+						} else {
+							$(a).trigger('click');
+						}
+					}
+				}
+				if ( settings.disableLinks || 
+					( !settings.disableLinks && !$(this).parent().hasClass('jd_menu') ) ) {
+					$(this).parent().jdMenuHide();
+					evt.stopPropagation();
+				}
+			})
 			.find('> a')
-				.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar');
-		
-		if (this.$timer) {
-			clearTimeout(this.$timer);
-		}
-
-		// TODO: Possible bug with our test for visibility in that parent menus are hidden child menus are not
+				.bind('focus.jdmenu blur.jdmenu', function(evt) {
+					var p = $(this).parents('li:eq(0)');
+					if ( evt.type == 'focus' ) {
+						p.addClass('jdm_hover');
+					} else { 
+						p.removeClass('jdm_hover');
+					}
+				})
+				.filter('.accessible')
+					.bind('click.jdmenu', function(evt) {
+						evt.preventDefault();
+					});
+	}
 
-		// If we have a visible menu, hide it
-		if ($(this).is(':visible') && $('> ul', this).size() > 0) {
-			var settings = getSettings(this);
-			var ul = $('> ul', this)[0];
-			this.$timer = setTimeout(function() {
-				hideMenuUL.apply(ul);
-			}, settings.hideDelay);
+	function showMenu(ul, animate, vertical) {
+		var ul = $(ul);
+		if ( ul.is(':visible') ) {
+			return;
+		}
+		ul.bgiframe();
+		var li = ul.parent();
+		ul	.trigger('jdMenuShow')
+			.positionBy({ 	target: 	li[0], 
+							targetPos: 	( vertical === true || !li.parent().hasClass('jd_menu') ? 1 : 3 ), 
+							elementPos: 0,
+							hideAfterPosition: true
+							});
+		if ( !ul.hasClass('jdm_events') ) {
+			ul.addClass('jdm_events');
+			addEvents(ul);
+		}
+		li	.addClass('jdm_active')
+			// Hide any adjacent menus
+			.siblings('li').find('> ul:eq(0):visible')
+				.each(function(){
+					hideMenu( this ); 
+				});
+		if ( animate === undefined ) {
+			ul.show();
+		} else {
+			animate.apply( ul[0], [true] );
 		}
-	};
+	}
 	
-	// "this" is a reference to the LI element that contains 
-	// the UL that will be shown
-	function showMenuLI() {
-		var ul = $('> ul', this).get(0);
-		// We are already visible, just return
-		if ($(ul).is(':visible')) {
-			return false;
-		}
-
-		// Clear our timer if it exists
-		if (this.$timer) {
-			clearTimeout(this.$timer);
-		}
-
-		// Get our settings object
-		var settings = getSettings(this);
-
-		// Call our callback
-		if (settings.onShow != null && settings.onShow.apply(this) == false) {
-			return false;
-		}
-
-		// Add hover classes, needed for accessible functionality
-		var isRoot = $(this).parent().is('.jd_menu_flag_root');
-		var c = 'jd_menu_active' + (isRoot ? '_menubar' : '');
-		$(this).addClass(c).find('> a').addClass(c);
-
-		if (!isRoot) {
-			// Add the active class to the parent list item which maybe our menubar
-			var c = 'jd_menu_active' + ($(this).parent().parent().parent().is('.jd_menu_flag_root') ? '_menubar' : '');
-			$(this).parent().parent().addClass(c).find('> a').addClass(c);
+	function hideMenu(ul, animate) {
+		var ul = $(ul);
+		$('.bgiframe', ul).remove();
+		ul	.filter(':not(.jd_menu)')
+			.find('> li > ul:eq(0):visible')
+				.each(function() {
+					hideMenu( this );
+				})
+			.end();
+		if ( animate === undefined ) {
+			ul.hide()
+		} else {
+			animate.apply( ul[0], [false] );
 		}
 
-		// Hide any existing menues at the same level
-		$(this).parent().find('> li > ul:visible').not(ul).each(function() {
-			hideMenuUL.apply(this);
+		ul	.trigger('jdMenuHide')
+			.parents('li:eq(0)')
+				.removeClass('jdm_active jdm_hover')
+			.end()
+				.find('> li')
+				.removeClass('jdm_active jdm_hover');
+	}
+	
+	// Public methods
+	$.fn.jdMenu = function(settings) {
+		// Future settings: activateDelay
+		var settings = $.extend({	// Time in ms before menu shows
+									showDelay: 		200,
+									// Time in ms before menu hides
+									hideDelay: 		500,
+									// Should items that contain submenus not 
+									// respond to clicks
+									disableLinks:	true
+									// This callback allows for you to animate menus
+									//onAnimate:	null
+									}, settings);
+		if ( !$.isFunction( settings.onAnimate ) ) {
+			settings.onAnimate = undefined;
+		}
+		return this.filter('ul.jd_menu').each(function() {
+			$.data(	this, 
+					'jdMenuSettings', 
+					$.extend({ isVertical: $(this).hasClass('jd_menu_vertical') }, settings) 
+					);
+			addEvents(this);
 		});
-
-		addEvents(ul);
-
-		// Our range object is used in calculating menu positions
-		var Range = function(x1, x2, y1, y2) {
-			this.x1	= x1;
-			this.x2 = x2;
-			this.y1 = y1;
-			this.y2 = y2;
-		}
-		Range.prototype.contains = function(range) {
-			return 	(this.x1 <= range.x1 && range.x2 <= this.x2) 
-					&& 
-					(this.y1 <= range.y1 && range.y2 <= this.y2);
-		}
-		Range.prototype.transform = function(x, y) {
-			return new Range(this.x1 + x, this.x2 + x, this.y1 + y, this.y2 + y);
-		}
-		Range.prototype.nudgeX = function(range) {
-			if (this.x1 < range.x1) {
-				return new Range(range.x1, range.x1 + (this.x2 - this.x1), this.y1, this.y2);
-			} else if (this.x2 > range.x2) {
-				return new Range(range.x2 - (this.x2 - this.x1), range.x2, this.y1, this.y2);
-			}
-			return this;
-		}
-		Range.prototype.nudgeY = function(range) {
-			if (this.y1 < range.y1) {
-				return new Range(this.x1, this.x2, range.y1, range.y1 + (this.y2 - this.y1));
-			} else if (this.y2 > range.y2) {
-				return new Range(this.x1, this.x2, range.y2 - (this.y2 - this.y1), range.y2);
-			}
-			return this;
-		}
-
-		// window width & scroll offset
-		var sx = $(window).scrollLeft()
-		var sy = $(window).scrollTop();
-		var ww = $(window).innerWidth();
-		var wh = $(window).innerHeight();
-
-		var viewport = new Range(	sx, sx + ww, 
-									sy, sy + wh);
-
-		// "Show" our menu so we can calculate its width, set left and top so that it does not accidentally
-		// go offscreen and trigger browser scroll bars
-		$(ul).css({visibility: 'hidden', left: 0, top: 0}).show();
-
-		var menuWidth		= $(ul).outerWidth();
-		var menuHeight		= $(ul).outerHeight();
-
-		// Get the LI parent UL outerwidth in case borders are applied to it
-		var tp 				= $(this).parent();
-		var thisWidth		= tp.outerWidth();
-		var thisBorderWidth	= parseInt(tp.css('borderLeftWidth')) + parseInt(tp.css('borderRightWidth'));
-		//var thisBorderTop 	= parseInt(tp.css('borderTopWidth'));
-		var thisHeight		= $(this).outerHeight();
-		var thisOffset 		= $(this).offset({border: false});
-
-		$(ul).hide().css({visibility: ''});
-
-		// We define a list of valid positions for our menu and then test against them to find one that works best
-		var position = [];
-	// Bottom Horizontal
-		// Menu is directly below and left edges aligned to parent item
-		position[0] = new Range(thisOffset.left, thisOffset.left + menuWidth, 
-								thisOffset.top + thisHeight, thisOffset.top + thisHeight + menuHeight);
-		// Menu is directly below and right edges aligned to parent item
-		position[1] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
-								position[0].y1, position[0].y2);
-		// Menu is "nudged" horizontally below parent item
-		position[2] = position[0].nudgeX(viewport);
-
-	// Right vertical
-		// Menu is directly right and top edge aligned to parent item
-		position[3] = new Range(thisOffset.left + thisWidth - thisBorderWidth, thisOffset.left + thisWidth - thisBorderWidth + menuWidth,
-								thisOffset.top, thisOffset.top + menuHeight);
-		// Menu is directly right and bottom edges aligned with parent item
-		position[4] = new Range(position[3].x1, position[3].x2, 
-								position[0].y1 - menuHeight, position[0].y1);
-		// Menu is "nudged" vertically to right of parent item
-		position[5] = position[3].nudgeY(viewport);
-
-	// Top Horizontal
-		// Menu is directly top and left edges aligned to parent item
-		position[6] = new Range(thisOffset.left, thisOffset.left + menuWidth, 
-								thisOffset.top - menuHeight, thisOffset.top);
-		// Menu is directly top and right edges aligned to parent item
-		position[7] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
-								position[6].y1, position[6].y2);
-		// Menu is "nudged" horizontally to the top of parent item
-		position[8] = position[6].nudgeX(viewport);
+	};
 	
-	// Left vertical
-		// Menu is directly left and top edges aligned to parent item
-		position[9] = new Range(thisOffset.left - menuWidth, thisOffset.left, 
-								position[3].y1, position[3].y2);
-		// Menu is directly left and bottom edges aligned to parent item
-		position[10]= new Range(position[9].x1, position[9].x2, 
-								position[4].y1 + thisHeight - menuHeight, position[4].y1 + thisHeight);
-		// Menu is "nudged" vertically to left of parent item
-		position[11]= position[10].nudgeY(viewport);
-
-		// This defines the order in which we test our positions
-		var order = [];
-		if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) {
-			order = [0, 1, 2, 6, 7, 8, 5, 11];
-		} else {
-			order = [3, 4, 5, 9, 10, 11, 0, 1, 2, 6, 7, 8];
-		}
-
-		// Set our default position (first position) if no others can be found
-		var pos = order[0];
-		for (var i = 0, j = order.length; i < j; i++) {
-			// If this position for our menu is within the viewport of the browser, use this position
-			if (viewport.contains(position[order[i]])) {
-				pos = order[i];
-				break;
-			}
-		}
-		var menuPosition = position[pos];
-
-		// Find if we are absolutely positioned or have an absolutely positioned parent
-		$(this).add($(this).parents()).each(function() {
-			if ($(this).css('position') == 'absolute') {
-				var abs = $(this).offset();
-				// Transform our coordinates to be relative to the absolute parent
-				menuPosition = menuPosition.transform(-abs.left, -abs.top);
-				return false;
-			}
+	$.fn.jdMenuUnbind = function() {
+		$('ul.jdm_events', this)
+			.unbind('.jdmenu')
+			.find('> a').unbind('.jdmenu');
+	};
+	$.fn.jdMenuHide = function() {
+		return this.filter('ul').each(function(){ 
+			hideMenu( this );
 		});
+	};
 
-		switch (pos) {
-			case 3:
-				menuPosition.y1 += settings.offsetY;
-			case 4:
-				menuPosition.x1 -= settings.offsetX;
-				break;
-			
-			case 9:
-				menuPosition.y1 += settings.offsetY;
-			case 10:
-				menuPosition.x1 += settings.offsetX;
-				break;
-		}
-
-		if (settings.iframe) {
-			$(ul).bgiframe();
-		}
-
-		if (settings.onAnimate) {
-			$(ul).css({left: menuPosition.x1, top: menuPosition.y1});
-			// The onAnimate method is expected to "show" the element it is passed
-			settings.onAnimate.apply(ul, [true]);
-		} else {
-			$(ul).css({left: menuPosition.x1, top: menuPosition.y1}).show();
-		}
-
-		return true;
-	}
-
-	// "this" is a reference to a UL menu to be hidden
-	function hideMenuUL(recurse) {
-		if (!$(this).is(':visible')) {
-			return false;
-		}
-
-		var settings = getSettings(this);
-
-		// Test if this menu should get hidden
-		if (settings.onHideCheck != null && settings.onHideCheck.apply(this) == false) {
-			return false;
-		}
-		
-		// Hide all of our child menus first
-		$('> li ul:visible', this).each(function() {
-			hideMenuUL.apply(this, [false]);
+	// Private methods and logic
+	$(window)
+		// Bind a click event to hide all visible menus when the document is clicked
+		.bind('click.jdmenu', function(){
+			$('ul.jd_menu ul:visible').jdMenuHide();
 		});
-
-		// If we are the root, do not hide ourself
-		if ($(this).is('.jd_menu_flag_root')) {
-			alert('We are root');
-			return false;
-		}
-
-		var elms = $('> li', this).add($(this).parent());
-		elms.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
-			.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar')
-			.find('> a')
-				.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
-				.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar');
-
-		removeEvents(this);
-		$(this).each(function() {
-			if (settings.onAnimate != null) {
-				settings.onAnimate.apply(this, [false]);
-			} else {
-				$(this).hide();
-			}
-		}).find('> .bgiframe').remove();
-		// Our callback for after our menu is hidden
-		if (settings.onHide != null) {
-			settings.onHide.apply(this);
-		}
-
-		// Recursively hide our parent menus
-		if (recurse == true) {
-			$(this).parentsUntil('ul.jd_menu_flag_root')
-					.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
-				.not('.jd_menu_flag_root').filter('ul')
-					.each(function() {
-						hideMenuUL.apply(this, [false]);
-					});
-		}
-
-		return true;
-	}
-
-	// Prevent the default (usually following a link)
-	function accessibleClick(e) {
-		if ($(this).is('.accessible')) {
-			// Stop the browser from the default link action allowing the 
-			// click event to propagate to propagate to our LI (itemClick function)
-			e.preventDefault();
-		}
-	}
-
-	// Trigger a menu click
-	function itemClick(e) {
-		e.stopPropagation();
-
-		var settings = getSettings(this);
-		if (settings.onClick != null && settings.onClick.apply(this) == false) {
-			return false;
-		}
-
-		if ($('> ul', this).size() > 0) {
-			showMenuLI.apply(this);
-		} else {
-			if (e.target == this) {
-				var link = $('> a', e.target).not('.accessible');
-				if (link.size() > 0) {
-					var a = link.get(0);
-					if (!a.onclick) {
-						window.open(a.href, a.target || '_self');
-					} else {
-						$(a).click();
-					}
-				}
-			}
-			
-			hideMenuUL.apply($(this).parent(), [true]);
-		}
-	}
 })(jQuery);

+ 62 - 0
js/jquery.popupmenu.js

@@ -0,0 +1,62 @@
+/*
+ * Simple popup menu 1.0 (2010-05-15)
+ *
+ * Copyright (c) 2010 Combodo SARL (www.combodo.com)
+ * Licenced under the GPL licence.
+ *
+ * http://www.combodo.com/
+ *
+ * Built upon jQuery jQuery 1.2.3a (http://jquery.com)
+ * Requires the jQuery positionBy plugin by Jonathan Sharp (http://jdsharp.us)
+ */
+
+jQuery.fn.popupmenu = function ()
+{
+	var popupmenu = null;
+
+	return this.each(function() 
+	{
+		$(this).bind('mouseenter.popup_menu click.popup_menu', function (evt)
+		{
+			console.log(evt.type);
+			var previous_popup = popupmenu;
+			var bMenuClosed = false;
+			popupmenu = $(this).find('ul');
+			if ( previous_popup != null)
+			{
+				if ( ((evt.type == 'click') && ((previous_popup[0] == popupmenu[0])) || // Comparing the jQuery objects
+					(evt.type == 'mouseenter') && (previous_popup[0] != popupmenu[0])) )
+				// The user clicked again in the menu or moved over another menu let's close it
+				previous_popup.css('display', 'none');
+				bMenuClosed = true;
+				
+			}
+			if ( (previous_popup == null) || (previous_popup[0] != popupmenu[0])) // Comparing the jQuery objects
+			{
+				// We really clicked in a different menu, let's open it
+				popupmenu.bgiframe();
+				popupmenu.positionBy({ target: $(this), 
+										targetPos: 	2, 
+										elementPos: 0,
+										hideAfterPosition: true
+										});
+				popupmenu.css('display', 'block');
+			}
+			if (bMenuClosed)
+			{
+				popupmenu = null;
+			}
+			evt.stopPropagation();
+		});
+
+		$(document).bind('click.popup_menu', function(evt)
+		{
+			if (popupmenu)
+			{
+				// The user clicked in the document's body, let's close the menu
+				popupmenu.css('display', 'none');
+				popupmenu = null;
+			}
+		});
+	});
+};

+ 306 - 0
js/jquery.positionBy.js

@@ -0,0 +1,306 @@
+/*
+ * positionBy 1.0.7 (2008-01-29)
+ *
+ * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://jdsharp.us/
+ *
+ * Built upon jQuery 1.2.2 (http://jquery.com)
+ * This also requires the jQuery dimensions plugin
+ */
+(function($){
+	/**
+	 * This function centers an absolutely positioned element
+	 */
+	/*
+	$.fn.positionCenter = function(offsetLeft, offsetTop) {
+		var offsetLeft 	= offsetLeft || 1;
+		var offsetTop 	= offsetTop || 1;
+
+		var ww = $(window).width();
+		var wh = $(window).height();
+		var sl = $(window).scrollLeft();
+		var st = $(window).scrollTop();
+
+		return this.each(function() {
+			var $t = $(this);
+			
+			// If we are not visible we have to display our element (with a negative position offscreen)
+
+			var left = Math.round( ( ww - $t.outerWidth() ) / 2 );
+			if ( left < 0 ) {
+				left = 0;
+			} else {
+				left *= offsetLeft;
+			}
+			left += sl;
+			var top  = Math.round( ( wh - $t.outerHeight() ) / 2 );
+			if ( top < 0 ) {
+				top = 0;
+			} else {
+				top *= offsetTop;
+			}
+			top += st;
+
+			$(this).parents().each(function() {
+				var $this = $(this);
+				if ( $this.css('position') != 'static' ) {
+					var o = $this.offset();
+					left += -o.left;
+					top	 += -o.top;
+					return false;
+				}
+			});
+
+			$t.css({left: left, top: top});
+		});
+	};
+	*/
+	
+	// Our range object is used in calculating positions
+	var Range = function(x1, y1, x2, y2) {
+		this.x1	= x1;	this.x2 = x2;
+		this.y1 = y1;	this.y2 = y2;
+	};
+	Range.prototype.contains = function(range) {
+		return 	(this.x1 <= range.x1 && range.x2 <= this.x2) 
+				&& 
+				(this.y1 <= range.y1 && range.y2 <= this.y2);
+	};
+	Range.prototype.transform = function(x, y) {
+		return new Range(this.x1 + x, this.y1 + y, this.x2 + x, this.y2 + y);
+	};
+
+	$.fn.positionBy = function(args) {
+		var date1 = new Date();
+		if ( this.length == 0 ) {
+			return this;
+		}
+		
+		var args = $.extend({	// The target element to position us relative to
+								target:		null,
+								// The target's corner, possible values 0-3
+								targetPos:	null,
+								// The element's corner, possible values 0-3
+								elementPos:	null,
+								
+								// A raw x,y coordinate
+								x:			null,
+								y:			null,
+
+								// Pass in an array of positions that are valid 0-15
+								positions:	null,
+
+								// Add the final position class to the element (eg. positionBy0 through positionBy3, positionBy15)
+								addClass: 	false,
+								
+								// Force our element to be at the location we specified (don't try to auto position it)
+								force: 		false,
+								
+								// The element that we will make sure our element doesn't go outside of
+								container: 	window,
+
+								// Should the element be hidden after positioning?
+								hideAfterPosition: false
+							}, args);
+
+		if ( args.x != null ) {
+			var tLeft	= args.x;
+			var tTop	= args.y;
+			var tWidth	= 0;
+			var tHeight	= 0;
+			
+		// Position in relation to an element
+		} else {
+			var $target	= $( $( args.target )[0] );
+			var tWidth	= $target.outerWidth();
+			var tHeight	= $target.outerHeight();
+			var tOffset	= $target.offset();
+			var tLeft	= tOffset.left;
+			var tTop	= tOffset.top;
+		}
+
+		// Our target right, bottom coord
+		var tRight	= tLeft + tWidth;
+		var tBottom	= tTop + tHeight;
+
+		return this.each(function() {
+			var $element = $( this );
+
+			// Position our element in the top left so we can grab its width without triggering scrollbars
+			if ( !$element.is(':visible') ) {
+				$element.css({	left:  		-3000, 
+								top: 		-3000
+								})
+								.show();
+			}
+
+			var eWidth	= $element.outerWidth();
+			var eHeight	= $element.outerHeight();
+	
+			// Holds x1,y1,x2,y2 coordinates for a position in relation to our target element
+			var position = [];
+			// Holds a list of alternate positions to try if this one is not in the browser viewport
+			var next	 = [];
+	
+			// Our Positions via ASCII ART
+			/*
+   	      	 8   9       10   11
+			   +------------+
+			 7 | 15      12 | 0
+			   |            |
+			 6 | 14      13 | 1
+			   +------------+ 
+			 5   4        3   2
+	
+			 */
+
+			position[0]	= new Range(tRight, 			tTop, 				tRight + eWidth, 	tTop + eHeight);
+			next[0]		= [1,7,4];
+		
+			position[1]	= new Range(tRight, 			tBottom - eHeight, 	tRight + eWidth, 	tBottom);
+			next[1]		= [0,6,4];
+		
+			position[2] = new Range(tRight, 			tBottom,			tRight + eWidth, 	tBottom + eHeight);
+			next[2]		= [1,3,10];
+		
+			position[3] = new Range(tRight - eWidth, 	tBottom,			tRight, 			tBottom + eHeight);
+			next[3]		= [1,6,10];
+			
+			position[4] = new Range(tLeft, 				tBottom,			tLeft + eWidth, 	tBottom + eHeight);
+			next[4]		= [1,6,9];
+		
+			position[5] = new Range(tLeft - eWidth, 	tBottom, 			tLeft, 				tBottom + eHeight);
+			next[5]		= [6,4,9];
+		
+			position[6] = new Range(tLeft - eWidth, 	tBottom - eHeight,	tLeft, 				tBottom);
+			next[6]		= [7,1,4];
+			
+			position[7] = new Range(tLeft - eWidth, 	tTop,				tLeft, 				tTop + eHeight);
+			next[7]		= [6,0,4];
+			
+			position[8] = new Range(tLeft - eWidth, 	tTop - eHeight,		tLeft, 				tTop);
+			next[8]		= [7,9,4];
+			
+			position[9] = new Range(tLeft, 				tTop - eHeight,		tLeft + eWidth, 	tTop);
+			next[9]		= [0,7,4];
+			
+			position[10]= new Range(tRight - eWidth, 	tTop - eHeight,		tRight, 			tTop);
+			next[10]	= [0,7,3];
+		
+			position[11]= new Range(tRight, 			tTop - eHeight, 	tRight + eWidth, 	tTop);
+			next[11]	= [0,10,3];
+			
+			position[12]= new Range(tRight - eWidth, 	tTop,				tRight, 			tTop + eHeight);
+			next[12]	= [13,7,10];
+			
+			position[13]= new Range(tRight - eWidth, 	tBottom - eHeight,	tRight, 			tBottom);
+			next[13]	= [12,6,3];
+			
+			position[14]= new Range(tLeft, 				tBottom - eHeight,	tLeft + eWidth, 	tBottom);
+			next[14]	= [15,1,4];
+			
+			position[15]= new Range(tLeft, 				tTop,				tLeft + eWidth, 	tTop + eHeight);
+			next[15]	= [14,0,9];
+	
+			if ( args.positions !== null ) {
+				var pos = args.positions[0];
+			} else if ( args.targetPos != null && args.elementPos != null ) {
+				var pos = [];
+				pos[0] = [];
+				pos[0][0] = 15;
+				pos[0][1] = 7;
+				pos[0][2] = 8;
+				pos[0][3] = 9;
+				pos[1] = [];
+				pos[1][0] = 0;
+				pos[1][1] = 12;
+				pos[1][2] = 10;
+				pos[1][3] = 11;
+				pos[2] = [];
+				pos[2][0] = 2;
+				pos[2][1] = 3;
+				pos[2][2] = 13;
+				pos[2][3] = 1;
+				pos[3] = [];
+				pos[3][0] = 4;
+				pos[3][1] = 5;
+				pos[3][2] = 6;
+				pos[3][3] = 14;
+
+				var pos = pos[args.targetPos][args.elementPos];
+			}
+			var ePos = position[pos];
+			var fPos = pos;
+
+			if ( !args.force ) {
+				// TODO: Do the args.container
+				// window width & scroll offset
+				$window = $( window );
+				var sx = $window.scrollLeft();
+				var sy = $window.scrollTop();
+				
+				// TODO: Look at innerWidth & innerHeight
+				var container = new Range( sx, sy, sx + $window.width(), sy + $window.height() );
+	
+				// If we are outside of our viewport, see if we are outside vertically or horizontally and push onto the stack
+				var stack;
+				if ( args.positions ) {
+					stack = args.positions;
+				} else {
+					stack = [pos];
+				}
+				var test = [];		// Keeps track of our positions we already tried
+				
+				while ( stack.length > 0 ) {
+					var p = stack.shift();
+					if ( test[p] ) {
+						continue;
+					}
+					test[p] = true;
+	
+					// If our current position is not within the viewport (eg. window) 
+					// add the next suggested position
+					if ( !container.contains(position[p]) ) {
+						if ( args.positions === null ) {
+							stack = jQuery.merge( stack, next[p] );
+						}
+					} else {
+						ePos = position[p];
+						break;
+					}
+				}
+			}
+
+			// + TODO: Determine if we are going to use absolute left, top, bottom, right 
+			// positions relative to our target
+		
+			// Take into account any absolute or fixed positioning
+			// to 'normalize' our coordinates
+			$element.parents().each(function() {
+				var $this = $(this);
+				if ( $this.css('position') != 'static' ) {
+					var abs = $this.offset();
+					ePos = ePos.transform( -abs.left, -abs.top );
+					return false;
+				}
+			});
+		
+			// Finally position our element
+			var css = { left: ePos.x1, top: ePos.y1 };
+			if ( args.hideAfterPosition ) {
+				css['display'] = 'none';
+			}
+			$element.css( css );
+
+			if ( args.addClass ) {
+				$element.removeClass( 'positionBy0 positionBy1 positionBy2 positionBy3 positionBy4 positionBy5 '
+									+ 'positionBy6 positionBy7 positionBy8 positionBy9 positionBy10 positionBy11 '
+									+ 'positionBy12 positionBy13 positionBy14 positionBy15')
+						.addClass('positionBy' + p);
+			}
+		});
+	};
+})(jQuery);