src/cm/media/js/lib/fg-menu/fg.menu.js
changeset 0 40c8f766c9b8
equal deleted inserted replaced
-1:000000000000 0:40c8f766c9b8
       
     1 /*-------------------------------------------------------------------- 
       
     2 Scripts for creating and manipulating custom menus based on standard <ul> markup
       
     3 Version: 3.0, 03.31.2009
       
     4 
       
     5 By: Maggie Costello Wachs (maggie@filamentgroup.com) and Scott Jehl (scott@filamentgroup.com)
       
     6 	http://www.filamentgroup.com
       
     7 	* reference articles: http://www.filamentgroup.com/lab/jquery_ipod_style_drilldown_menu/
       
     8 		
       
     9 Copyright (c) 2009 Filament Group
       
    10 Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
       
    11 --------------------------------------------------------------------*/
       
    12 
       
    13 
       
    14 var allUIMenus = [];
       
    15 
       
    16 $.fn.menu = function(options){
       
    17 	var caller = this;
       
    18 	var options = options;
       
    19 	var m = new Menu(caller, options);	
       
    20 	allUIMenus.push(m);
       
    21 	
       
    22 	$(this)
       
    23 	.mousedown(function(){
       
    24 		if (!m.menuOpen) { m.showLoading(); };
       
    25 	})	
       
    26 	.click(function(){
       
    27 		if (m.menuOpen == false) { m.showMenu(); }
       
    28 		else { m.kill(); };
       
    29 		return false;
       
    30 	});	
       
    31 };
       
    32 
       
    33 function Menu(caller, options){
       
    34 	var menu = this;
       
    35 	var caller = $(caller);
       
    36 	var container = $('<div class="fg-menu-container ui-widget ui-widget-content ui-corner-all">'+options.content+'</div>');
       
    37 	
       
    38 	this.menuOpen = false;
       
    39 	this.menuExists = false;
       
    40 	
       
    41 	var options = jQuery.extend({
       
    42 		content: null,
       
    43 		width: 180, // width of menu container, must be set or passed in to calculate widths of child menus
       
    44 		maxHeight: 180, // max height of menu (if a drilldown: height does not include breadcrumb)
       
    45 		positionOpts: {
       
    46 			posX: 'left', 
       
    47 			posY: 'bottom',
       
    48 			offsetX: 0,
       
    49 			offsetY: 0,
       
    50 			directionH: 'right',
       
    51 			directionV: 'down', 
       
    52 			detectH: true, // do horizontal collision detection  
       
    53 			detectV: true, // do vertical collision detection
       
    54 			linkToFront: false
       
    55 		},
       
    56 		showSpeed: 200, // show/hide speed in milliseconds
       
    57 		callerOnState: 'ui-state-active', // class to change the appearance of the link/button when the menu is showing
       
    58 		loadingState: 'ui-state-loading', // class added to the link/button while the menu is created
       
    59 		linkHover: 'ui-state-hover', // class for menu option hover state
       
    60 		linkHoverSecondary: 'li-hover', // alternate class, may be used for multi-level menus		
       
    61 	// ----- multi-level menu defaults -----
       
    62 		crossSpeed: 200, // cross-fade speed for multi-level menus
       
    63 		crumbDefaultText: 'Choose an option:',
       
    64 		backLink: true, // in the ipod-style menu: instead of breadcrumbs, show only a 'back' link
       
    65 		backLinkText: 'Back',
       
    66 		flyOut: false, // multi-level menus are ipod-style by default; this parameter overrides to make a flyout instead
       
    67 		flyOutOnState: 'ui-state-default',
       
    68 		nextMenuLink: 'ui-icon-triangle-1-e', // class to style the link (specifically, a span within the link) used in the multi-level menu to show the next level
       
    69 		topLinkText: 'All',
       
    70 		nextCrumbLink: 'ui-icon-carat-1-e'	
       
    71 	}, options);
       
    72 	
       
    73 	var killAllMenus = function(){
       
    74 		$.each(allUIMenus, function(i){
       
    75 			if (allUIMenus[i].menuOpen) { allUIMenus[i].kill(); };	
       
    76 		});
       
    77 	};
       
    78 	
       
    79 	this.kill = function(){
       
    80 		caller
       
    81 			.removeClass(options.loadingState)
       
    82 			.removeClass('fg-menu-open')
       
    83 			.removeClass(options.callerOnState);	
       
    84 		container.find('li').removeClass(options.linkHoverSecondary).find('a').removeClass(options.linkHover);		
       
    85 		if (options.flyOutOnState) { container.find('li a').removeClass(options.flyOutOnState); };	
       
    86 		if (options.callerOnState) { 	caller.removeClass(options.callerOnState); };			
       
    87 		if (container.is('.fg-menu-ipod')) { menu.resetDrilldownMenu(); };
       
    88 		if (container.is('.fg-menu-flyout')) { menu.resetFlyoutMenu(); };	
       
    89 		container.parent().hide();	
       
    90 		menu.menuOpen = false;
       
    91 		$(document).unbind('click', killAllMenus);
       
    92 		$(document).unbind('keydown');
       
    93 	};
       
    94 	
       
    95 	this.showLoading = function(){
       
    96 		caller.addClass(options.loadingState);
       
    97 	};
       
    98 
       
    99 	this.showMenu = function(){
       
   100 		killAllMenus();
       
   101 		if (!menu.menuExists) { menu.create() };
       
   102 		caller
       
   103 			.addClass('fg-menu-open')
       
   104 			.addClass(options.callerOnState);
       
   105 		container.parent().show().click(function(){ menu.kill(); return false; });
       
   106 		container.hide().slideDown(options.showSpeed).find('.fg-menu:eq(0)');
       
   107 		menu.menuOpen = true;
       
   108 		caller.removeClass(options.loadingState);
       
   109 		$(document).click(killAllMenus);
       
   110 		
       
   111 		// assign key events
       
   112 		$(document).keydown(function(event){
       
   113 			var e;
       
   114 			if (event.which !="") { e = event.which; }
       
   115 			else if (event.charCode != "") { e = event.charCode; }
       
   116 			else if (event.keyCode != "") { e = event.keyCode; }
       
   117 			
       
   118 			var menuType = ($(event.target).parents('div').is('.fg-menu-flyout')) ? 'flyout' : 'ipod' ;
       
   119 			
       
   120 			switch(e) {
       
   121 				case 37: // left arrow 
       
   122 					if (menuType == 'flyout') {
       
   123 						$(event.target).trigger('mouseout');
       
   124 						if ($('.'+options.flyOutOnState).size() > 0) { $('.'+options.flyOutOnState).trigger('mouseover'); };
       
   125 					};
       
   126 					
       
   127 					if (menuType == 'ipod') {
       
   128 						$(event.target).trigger('mouseout');
       
   129 						if ($('.fg-menu-footer').find('a').size() > 0) { $('.fg-menu-footer').find('a').trigger('click'); };
       
   130 						if ($('.fg-menu-header').find('a').size() > 0) { $('.fg-menu-current-crumb').prev().find('a').trigger('click'); };
       
   131 						if ($('.fg-menu-current').prev().is('.fg-menu-indicator')) {
       
   132 							$('.fg-menu-current').prev().trigger('mouseover');							
       
   133 						};						
       
   134 					};
       
   135 					return false;
       
   136 					break;
       
   137 					
       
   138 				case 38: // up arrow 
       
   139 					if ($(event.target).is('.' + options.linkHover)) {	
       
   140 						var prevLink = $(event.target).parent().prev().find('a:eq(0)');						
       
   141 						if (prevLink.size() > 0) {
       
   142 							$(event.target).trigger('mouseout');
       
   143 							prevLink.trigger('mouseover');
       
   144 						};						
       
   145 					}
       
   146 					else { container.find('a:eq(0)').trigger('mouseover'); }
       
   147 					return false;
       
   148 					break;
       
   149 					
       
   150 				case 39: // right arrow 
       
   151 					if ($(event.target).is('.fg-menu-indicator')) {						
       
   152 						if (menuType == 'flyout') {
       
   153 							$(event.target).next().find('a:eq(0)').trigger('mouseover');
       
   154 						}
       
   155 						else if (menuType == 'ipod') {
       
   156 							$(event.target).trigger('click');						
       
   157 							setTimeout(function(){
       
   158 								$(event.target).next().find('a:eq(0)').trigger('mouseover');
       
   159 							}, options.crossSpeed);
       
   160 						};				
       
   161 					}; 
       
   162 					return false;
       
   163 					break;
       
   164 					
       
   165 				case 40: // down arrow 
       
   166 					if ($(event.target).is('.' + options.linkHover)) {
       
   167 						var nextLink = $(event.target).parent().next().find('a:eq(0)');						
       
   168 						if (nextLink.size() > 0) {							
       
   169 							$(event.target).trigger('mouseout');
       
   170 							nextLink.trigger('mouseover');
       
   171 						};				
       
   172 					}
       
   173 					else { container.find('a:eq(0)').trigger('mouseover'); }		
       
   174 					return false;						
       
   175 					break;
       
   176 					
       
   177 				case 27: // escape
       
   178 					killAllMenus();
       
   179 					break;
       
   180 					
       
   181 				case 13: // enter
       
   182 					if ($(event.target).is('.fg-menu-indicator') && menuType == 'ipod') {							
       
   183 						$(event.target).trigger('click');						
       
   184 						setTimeout(function(){
       
   185 							$(event.target).next().find('a:eq(0)').trigger('mouseover');
       
   186 						}, options.crossSpeed);					
       
   187 					}; 
       
   188 					break;
       
   189 			};			
       
   190 		});
       
   191 	};
       
   192 	
       
   193 	this.create = function(){	
       
   194 		container.css({ width: options.width }).appendTo('body').find('ul:first').not('.fg-menu-breadcrumb').addClass('fg-menu');
       
   195 		container.find('ul, li a').addClass('ui-corner-all');
       
   196 		
       
   197 		// aria roles & attributes
       
   198 		container.find('ul').attr('role', 'menu').eq(0).attr('aria-activedescendant','active-menuitem').attr('aria-labelledby', caller.attr('id'));
       
   199 		container.find('li').attr('role', 'menuitem');
       
   200 		container.find('li:has(ul)').attr('aria-haspopup', 'true').find('ul').attr('aria-expanded', 'false');
       
   201 		container.find('a').attr('tabindex', '-1');
       
   202 		
       
   203 		// when there are multiple levels of hierarchy, create flyout or drilldown menu
       
   204 		if (container.find('ul').size() > 1) {
       
   205 			if (options.flyOut) { menu.flyout(container, options); }
       
   206 			else { menu.drilldown(container, options); }	
       
   207 		}
       
   208 		else {
       
   209 			container.find('a').click(function(){
       
   210 				menu.chooseItem(this);
       
   211 				return false;
       
   212 			});
       
   213 		};	
       
   214 		
       
   215 		if (options.linkHover) {
       
   216 			var allLinks = container.find('.fg-menu li a');
       
   217 			allLinks.hover(
       
   218 				function(){
       
   219 					var menuitem = $(this);
       
   220 					$('.'+options.linkHover).removeClass(options.linkHover).blur().parent().removeAttr('id');
       
   221 					$(this).addClass(options.linkHover).focus().parent().attr('id','active-menuitem');
       
   222 				},
       
   223 				function(){
       
   224 					$(this).removeClass(options.linkHover).blur().parent().removeAttr('id');
       
   225 				}
       
   226 			);
       
   227 		};
       
   228 		
       
   229 		if (options.linkHoverSecondary) {
       
   230 			container.find('.fg-menu li').hover(
       
   231 				function(){
       
   232 					$(this).siblings('li').removeClass(options.linkHoverSecondary);
       
   233 					if (options.flyOutOnState) { $(this).siblings('li').find('a').removeClass(options.flyOutOnState); }
       
   234 					$(this).addClass(options.linkHoverSecondary);
       
   235 				},
       
   236 				function(){ $(this).removeClass(options.linkHoverSecondary); }
       
   237 			);
       
   238 		};	
       
   239 		
       
   240 		menu.setPosition(container, caller, options);
       
   241 		menu.menuExists = true;
       
   242 	};
       
   243 	
       
   244 	this.chooseItem = function(item){
       
   245 		// RBA menu.kill();
       
   246 		// edit this for your own custom function/callback:
       
   247 		$('#menuSelection').text($(item).text());	
       
   248 		location.href = $(item).attr('href'); // RBA
       
   249 	};
       
   250 };
       
   251 
       
   252 Menu.prototype.flyout = function(container, options) {
       
   253 	var menu = this;
       
   254 	
       
   255 	this.resetFlyoutMenu = function(){
       
   256 		var allLists = container.find('ul ul');
       
   257 		allLists.removeClass('ui-widget-content').hide();	
       
   258 	};
       
   259 	
       
   260 	container.addClass('fg-menu-flyout').find('li:has(ul)').each(function(){
       
   261 		var linkWidth = container.width();
       
   262 		var showTimer, hideTimer;
       
   263 		var allSubLists = $(this).find('ul');		
       
   264 		
       
   265 		allSubLists.css({ left: linkWidth, width: linkWidth }).hide();
       
   266 			
       
   267 		$(this).find('a:eq(0)').addClass('fg-menu-indicator').html('<span>' + $(this).find('a:eq(0)').text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>').hover(
       
   268 			function(){
       
   269 				clearTimeout(hideTimer);
       
   270 				var subList = $(this).next();
       
   271 				if (!fitVertical(subList, $(this).offset().top)) { subList.css({ top: 'auto', bottom: 0 }); };
       
   272 				if (!fitHorizontal(subList, $(this).offset().left + 100)) { subList.css({ left: 'auto', right: linkWidth, 'z-index': 999 }); };
       
   273 				showTimer = setTimeout(function(){
       
   274 					subList.addClass('ui-widget-content').show(options.showSpeed).attr('aria-expanded', 'true');	
       
   275 				}, 300);	
       
   276 			},
       
   277 			function(){
       
   278 				clearTimeout(showTimer);
       
   279 				var subList = $(this).next();
       
   280 				hideTimer = setTimeout(function(){
       
   281 					subList.removeClass('ui-widget-content').hide(options.showSpeed).attr('aria-expanded', 'false');
       
   282 				}, 400);	
       
   283 			}
       
   284 		);
       
   285 
       
   286 		$(this).find('ul a').hover(
       
   287 			function(){
       
   288 				clearTimeout(hideTimer);
       
   289 				if ($(this).parents('ul').prev().is('a.fg-menu-indicator')) {
       
   290 					$(this).parents('ul').prev().addClass(options.flyOutOnState);
       
   291 				}
       
   292 			},
       
   293 			function(){
       
   294 				hideTimer = setTimeout(function(){
       
   295 					allSubLists.hide(options.showSpeed);
       
   296 					container.find(options.flyOutOnState).removeClass(options.flyOutOnState);
       
   297 				}, 500);	
       
   298 			}
       
   299 		);	
       
   300 	});
       
   301 	
       
   302 	container.find('a').click(function(){
       
   303 		menu.chooseItem(this);
       
   304 		return false;
       
   305 	});
       
   306 };
       
   307 
       
   308 
       
   309 Menu.prototype.drilldown = function(container, options) {
       
   310 	var menu = this;	
       
   311 	var topList = container.find('.fg-menu');	
       
   312 	var breadcrumb = $('<ul class="fg-menu-breadcrumb ui-widget-header ui-corner-all ui-helper-clearfix"></ul>');
       
   313 	var crumbDefaultHeader = $('<li class="fg-menu-breadcrumb-text">'+options.crumbDefaultText+'</li>');
       
   314 	var firstCrumbText = (options.backLink) ? options.backLinkText : options.topLinkText;
       
   315 	var firstCrumbClass = (options.backLink) ? 'fg-menu-prev-list' : 'fg-menu-all-lists';
       
   316 	var firstCrumbLinkClass = (options.backLink) ? 'ui-state-default ui-corner-all' : '';
       
   317 	var firstCrumbIcon = (options.backLink) ? '<span class="ui-icon ui-icon-triangle-1-w"></span>' : '';
       
   318 	var firstCrumb = $('<li class="'+firstCrumbClass+'"><a href="#" class="'+firstCrumbLinkClass+'">'+firstCrumbIcon+firstCrumbText+'</a></li>');
       
   319 	
       
   320 	container.addClass('fg-menu-ipod');
       
   321 	
       
   322 	if (options.backLink) { breadcrumb.addClass('fg-menu-footer').appendTo(container).hide(); }
       
   323 	else { breadcrumb.addClass('fg-menu-header').prependTo(container); };
       
   324 	breadcrumb.append(crumbDefaultHeader);
       
   325 	
       
   326 	var checkMenuHeight = function(el){
       
   327 		if (el.height() > options.maxHeight) { el.addClass('fg-menu-scroll') };	
       
   328 		el.css({ height: options.maxHeight });
       
   329 	};
       
   330 	
       
   331 	var resetChildMenu = function(el){ el.removeClass('fg-menu-scroll').removeClass('fg-menu-current').height('auto'); };
       
   332 	
       
   333 	this.resetDrilldownMenu = function(){
       
   334 		$('.fg-menu-current').removeClass('fg-menu-current');
       
   335 		topList.animate({ left: 0 }, options.crossSpeed, function(){
       
   336 			$(this).find('ul').each(function(){
       
   337 				$(this).hide();
       
   338 				resetChildMenu($(this));				
       
   339 			});
       
   340 			topList.addClass('fg-menu-current');			
       
   341 		});		
       
   342 		$('.fg-menu-all-lists').find('span').remove();	
       
   343 		breadcrumb.empty().append(crumbDefaultHeader);		
       
   344 		$('.fg-menu-footer').empty().hide();	
       
   345 		checkMenuHeight(topList);		
       
   346 	};
       
   347 	
       
   348 	topList
       
   349 		.addClass('fg-menu-content fg-menu-current ui-widget-content ui-helper-clearfix')
       
   350 		.css({ width: container.width() })
       
   351 		.find('ul')
       
   352 			.css({ width: container.width(), left: container.width() })
       
   353 			.addClass('ui-widget-content')
       
   354 			.hide();		
       
   355 	checkMenuHeight(topList);	
       
   356 	
       
   357 	topList.find('a').each(function(){
       
   358 		// if the link opens a child menu:
       
   359 		if ($(this).next().is('ul')) {
       
   360 			$(this)
       
   361 				.addClass('fg-menu-indicator')
       
   362 				.each(function(){ $(this).html('<span>' + $(this).text() + '</span><span class="ui-icon '+options.nextMenuLink+'"></span>'); })
       
   363 				.click(function(){ // ----- show the next menu			
       
   364 					var nextList = $(this).next();
       
   365 		    		var parentUl = $(this).parents('ul:eq(0)');   		
       
   366 		    		var parentLeft = (parentUl.is('.fg-menu-content')) ? 0 : parseFloat(topList.css('left'));    		
       
   367 		    		var nextLeftVal = Math.round(parentLeft - parseFloat(container.width()));
       
   368 		    		var footer = $('.fg-menu-footer');
       
   369 		    		
       
   370 		    		// show next menu   		
       
   371 		    		resetChildMenu(parentUl);
       
   372 		    		checkMenuHeight(nextList);
       
   373 					topList.animate({ left: nextLeftVal }, options.crossSpeed);						
       
   374 		    		nextList.show().addClass('fg-menu-current').attr('aria-expanded', 'true');    
       
   375 		    		
       
   376 		    		var setPrevMenu = function(backlink){
       
   377 		    			var b = backlink;
       
   378 		    			var c = $('.fg-menu-current');
       
   379 			    		var prevList = c.parents('ul:eq(0)');
       
   380 			    		c.hide().attr('aria-expanded', 'false');
       
   381 		    			resetChildMenu(c);
       
   382 		    			checkMenuHeight(prevList);
       
   383 			    		prevList.addClass('fg-menu-current').attr('aria-expanded', 'true');
       
   384 			    		if (prevList.hasClass('fg-menu-content')) { b.remove(); footer.hide(); };
       
   385 		    		};		
       
   386 		
       
   387 					// initialize "back" link
       
   388 					if (options.backLink) {
       
   389 						if (footer.find('a').size() == 0) {
       
   390 							footer.show();
       
   391 							$('<a href="#"><span class="ui-icon ui-icon-triangle-1-w"></span> <span>Back</span></a>')
       
   392 								.appendTo(footer)
       
   393 								.click(function(){ // ----- show the previous menu
       
   394 									var b = $(this);
       
   395 						    		var prevLeftVal = parseFloat(topList.css('left')) + container.width();		    						    		
       
   396 						    		topList.animate({ left: prevLeftVal },  options.crossSpeed, function(){
       
   397 						    			setPrevMenu(b);
       
   398 						    		});			
       
   399 									return false;
       
   400 								});
       
   401 						}
       
   402 					}
       
   403 					// or initialize top breadcrumb
       
   404 		    		else { 
       
   405 		    			if (breadcrumb.find('li').size() == 1){				
       
   406 							breadcrumb.empty().append(firstCrumb);
       
   407 							firstCrumb.find('a').click(function(){
       
   408 								menu.resetDrilldownMenu();
       
   409 								return false;
       
   410 							});
       
   411 						}
       
   412 						$('.fg-menu-current-crumb').removeClass('fg-menu-current-crumb');
       
   413 						var crumbText = $(this).find('span:eq(0)').text();
       
   414 						var newCrumb = $('<li class="fg-menu-current-crumb"><a href="javascript://" class="fg-menu-crumb">'+crumbText+'</a></li>');	
       
   415 						newCrumb
       
   416 							.appendTo(breadcrumb)
       
   417 							.find('a').click(function(){
       
   418 								if ($(this).parent().is('.fg-menu-current-crumb')){
       
   419 									menu.chooseItem(this);
       
   420 								}
       
   421 								else {
       
   422 									var newLeftVal = - ($('.fg-menu-current').parents('ul').size() - 1) * 180;
       
   423 									topList.animate({ left: newLeftVal }, options.crossSpeed, function(){
       
   424 										setPrevMenu();
       
   425 									});
       
   426 								
       
   427 									// make this the current crumb, delete all breadcrumbs after this one, and navigate to the relevant menu
       
   428 									$(this).parent().addClass('fg-menu-current-crumb').find('span').remove();
       
   429 									$(this).parent().nextAll().remove();									
       
   430 								};
       
   431 								return false;
       
   432 							});
       
   433 						newCrumb.prev().append(' <span class="ui-icon '+options.nextCrumbLink+'"></span>');
       
   434 		    		};			
       
   435 		    		return false;    		
       
   436     			});
       
   437 		}
       
   438 		// if the link is a leaf node (doesn't open a child menu)
       
   439 		else {
       
   440 			$(this).click(function(){
       
   441 				menu.chooseItem(this);
       
   442 				return false;
       
   443 			});
       
   444 		};
       
   445 	});
       
   446 };
       
   447 
       
   448 
       
   449 /* Menu.prototype.setPosition parameters (defaults noted with *):
       
   450 	referrer = the link (or other element) used to show the overlaid object 
       
   451 	settings = can override the defaults:
       
   452 		- posX/Y: where the top left corner of the object should be positioned in relation to its referrer.
       
   453 				X: left*, center, right
       
   454 				Y: top, center, bottom*
       
   455 		- offsetX/Y: the number of pixels to be offset from the x or y position.  Can be a positive or negative number.
       
   456 		- directionH/V: where the entire menu should appear in relation to its referrer.
       
   457 				Horizontal: left*, right
       
   458 				Vertical: up, down*
       
   459 		- detectH/V: detect the viewport horizontally / vertically
       
   460 		- linkToFront: copy the menu link and place it on top of the menu (visual effect to make it look like it overlaps the object) */
       
   461 
       
   462 Menu.prototype.setPosition = function(widget, caller, options) { 
       
   463 	var el = widget;
       
   464 	var referrer = caller;
       
   465 	var dims = {
       
   466 		refX: referrer.offset().left,
       
   467 		refY: referrer.offset().top,
       
   468 		refW: referrer.getTotalWidth(),
       
   469 		refH: referrer.getTotalHeight()
       
   470 	};	
       
   471 	var options = options;
       
   472 	var xVal, yVal;
       
   473 	
       
   474 	var helper = $('<div class="positionHelper"></div>');
       
   475 	helper.css({ position: 'absolute', left: dims.refX, top: dims.refY, width: dims.refW, height: dims.refH });
       
   476 	el.wrap(helper);
       
   477 	
       
   478 	// get X pos
       
   479 	switch(options.positionOpts.posX) {
       
   480 		case 'left': 	xVal = 0; 
       
   481 			break;				
       
   482 		case 'center': xVal = dims.refW / 2;
       
   483 			break;				
       
   484 		case 'right': xVal = dims.refW;
       
   485 			break;
       
   486 	};
       
   487 	
       
   488 	// get Y pos
       
   489 	switch(options.positionOpts.posY) {
       
   490 		case 'top': 	yVal = 0;
       
   491 			break;				
       
   492 		case 'center': yVal = dims.refH / 2;
       
   493 			break;				
       
   494 		case 'bottom': yVal = dims.refH;
       
   495 			break;
       
   496 	};
       
   497 	
       
   498 	// add the offsets (zero by default)
       
   499 	xVal += options.positionOpts.offsetX;
       
   500 	yVal += options.positionOpts.offsetY;
       
   501 	
       
   502 	// position the object vertically
       
   503 	if (options.positionOpts.directionV == 'up') {
       
   504 		el.css({ top: 'auto', bottom: yVal });
       
   505 		if (options.positionOpts.detectV && !fitVertical(el)) {
       
   506 			el.css({ bottom: 'auto', top: yVal });
       
   507 		}
       
   508 	} 
       
   509 	else {
       
   510 		el.css({ bottom: 'auto', top: yVal });
       
   511 		if (options.positionOpts.detectV && !fitVertical(el)) {
       
   512 			el.css({ top: 'auto', bottom: yVal });
       
   513 		}
       
   514 	};
       
   515 	
       
   516 	// and horizontally
       
   517 	if (options.positionOpts.directionH == 'left') {
       
   518 		el.css({ left: 'auto', right: xVal });
       
   519 		if (options.positionOpts.detectH && !fitHorizontal(el)) {
       
   520 			el.css({ right: 'auto', left: xVal });
       
   521 		}
       
   522 	} 
       
   523 	else {
       
   524 		el.css({ right: 'auto', left: xVal });
       
   525 		if (options.positionOpts.detectH && !fitHorizontal(el)) {
       
   526 			el.css({ left: 'auto', right: xVal });
       
   527 		}
       
   528 	};
       
   529 	
       
   530 	// if specified, clone the referring element and position it so that it appears on top of the menu
       
   531 	if (options.positionOpts.linkToFront) {
       
   532 		referrer.clone().addClass('linkClone').css({
       
   533 			position: 'absolute', 
       
   534 			top: 0, 
       
   535 			right: 'auto', 
       
   536 			bottom: 'auto', 
       
   537 			left: 0, 
       
   538 			width: referrer.width(), 
       
   539 			height: referrer.height()
       
   540 		}).insertAfter(el);
       
   541 	};
       
   542 };
       
   543 
       
   544 
       
   545 /* Utilities to sort and find viewport dimensions */
       
   546 
       
   547 function sortBigToSmall(a, b) { return b - a; };
       
   548 
       
   549 jQuery.fn.getTotalWidth = function(){
       
   550 	return $(this).width() + parseInt($(this).css('paddingRight')) + parseInt($(this).css('paddingLeft')) + parseInt($(this).css('borderRightWidth')) + parseInt($(this).css('borderLeftWidth'));
       
   551 };
       
   552 
       
   553 jQuery.fn.getTotalHeight = function(){
       
   554 	return $(this).height() + parseInt($(this).css('paddingTop')) + parseInt($(this).css('paddingBottom')) + parseInt($(this).css('borderTopWidth')) + parseInt($(this).css('borderBottomWidth'));
       
   555 };
       
   556 
       
   557 function getScrollTop(){
       
   558 	return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
       
   559 };
       
   560 
       
   561 function getScrollLeft(){
       
   562 	return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
       
   563 };
       
   564 
       
   565 function getWindowHeight(){
       
   566 	var de = document.documentElement;
       
   567 	return self.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
       
   568 };
       
   569 
       
   570 function getWindowWidth(){
       
   571 	var de = document.documentElement;
       
   572 	return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
       
   573 };
       
   574 
       
   575 /* Utilities to test whether an element will fit in the viewport
       
   576 	Parameters:
       
   577 	el = element to position, required
       
   578 	leftOffset / topOffset = optional parameter if the offset cannot be calculated (i.e., if the object is in the DOM but is set to display: 'none') */
       
   579 	
       
   580 function fitHorizontal(el, leftOffset){
       
   581 	var leftVal = parseInt(leftOffset) || $(el).offset().left;
       
   582 	return (leftVal + $(el).width() <= getWindowWidth() + getScrollLeft() && leftVal - getScrollLeft() >= 0);
       
   583 };
       
   584 
       
   585 function fitVertical(el, topOffset){
       
   586 	var topVal = parseInt(topOffset) || $(el).offset().top;
       
   587 	return (topVal + $(el).height() <= getWindowHeight() + getScrollTop() && topVal - getScrollTop() >= 0);
       
   588 };
       
   589 
       
   590 /*-------------------------------------------------------------------- 
       
   591  * javascript method: "pxToEm"
       
   592  * by:
       
   593    Scott Jehl (scott@filamentgroup.com) 
       
   594    Maggie Wachs (maggie@filamentgroup.com)
       
   595    http://www.filamentgroup.com
       
   596  *
       
   597  * Copyright (c) 2008 Filament Group
       
   598  * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
       
   599  *
       
   600  * Description: Extends the native Number and String objects with pxToEm method. pxToEm converts a pixel value to ems depending on inherited font size.  
       
   601  * Article: http://www.filamentgroup.com/lab/retaining_scalable_interfaces_with_pixel_to_em_conversion/
       
   602  * Demo: http://www.filamentgroup.com/examples/pxToEm/	 	
       
   603  *							
       
   604  * Options:  	 								
       
   605  		scope: string or jQuery selector for font-size scoping
       
   606  		reverse: Boolean, true reverses the conversion to em-px
       
   607  * Dependencies: jQuery library						  
       
   608  * Usage Example: myPixelValue.pxToEm(); or myPixelValue.pxToEm({'scope':'#navigation', reverse: true});
       
   609  *
       
   610  * Version: 2.0, 08.01.2008 
       
   611  * Changelog:
       
   612  *		08.02.2007 initial Version 1.0
       
   613  *		08.01.2008 - fixed font-size calculation for IE
       
   614 --------------------------------------------------------------------*/
       
   615 
       
   616 Number.prototype.pxToEm = String.prototype.pxToEm = function(settings){
       
   617 	//set defaults
       
   618 	settings = jQuery.extend({
       
   619 		scope: 'body',
       
   620 		reverse: false
       
   621 	}, settings);
       
   622 	
       
   623 	var pxVal = (this == '') ? 0 : parseFloat(this);
       
   624 	var scopeVal;
       
   625 	var getWindowWidth = function(){
       
   626 		var de = document.documentElement;
       
   627 		return self.innerWidth || (de && de.clientWidth) || document.body.clientWidth;
       
   628 	};	
       
   629 	
       
   630 	/* When a percentage-based font-size is set on the body, IE returns that percent of the window width as the font-size. 
       
   631 		For example, if the body font-size is 62.5% and the window width is 1000px, IE will return 625px as the font-size. 	
       
   632 		When this happens, we calculate the correct body font-size (%) and multiply it by 16 (the standard browser font size) 
       
   633 		to get an accurate em value. */
       
   634 				
       
   635 	if (settings.scope == 'body' && $.browser.msie && (parseFloat($('body').css('font-size')) / getWindowWidth()).toFixed(1) > 0.0) {
       
   636 		var calcFontSize = function(){		
       
   637 			return (parseFloat($('body').css('font-size'))/getWindowWidth()).toFixed(3) * 16;
       
   638 		};
       
   639 		scopeVal = calcFontSize();
       
   640 	}
       
   641 	else { scopeVal = parseFloat(jQuery(settings.scope).css("font-size")); };
       
   642 			
       
   643 	var result = (settings.reverse == true) ? (pxVal * scopeVal).toFixed(2) + 'px' : (pxVal / scopeVal).toFixed(2) + 'em';
       
   644 	return result;
       
   645 };