PageRenderTime 89ms CodeModel.GetById 25ms app.highlight 57ms RepoModel.GetById 1ms app.codeStats 1ms

/javascripts/fg.menu.js

http://raihan.googlecode.com/
JavaScript | 645 lines | 495 code | 75 blank | 75 comment | 103 complexity | 4f556f95a35e425d92d1e4c7ae1bf91f MD5 | raw file
  1/*-------------------------------------------------------------------- 
  2Scripts for creating and manipulating custom menus based on standard <ul> markup
  3Version: 3.0, 03.31.2009
  4
  5By: 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		
  9Copyright (c) 2009 Filament Group
 10Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
 11--------------------------------------------------------------------*/
 12
 13
 14var 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
 33function 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		menu.kill();
246		// edit this for your own custom function/callback:
247		$('#menuSelection').text($(item).text());	
248		location.href = $(item).attr('href');
249	};
250};
251
252Menu.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
309Menu.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
462Menu.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
547function sortBigToSmall(a, b) { return b - a; };
548
549jQuery.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
553jQuery.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
557function getScrollTop(){
558	return self.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
559};
560
561function getScrollLeft(){
562	return self.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
563};
564
565function getWindowHeight(){
566	var de = document.documentElement;
567	return self.innerHeight || (de && de.clientHeight) || document.body.clientHeight;
568};
569
570function 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	
580function fitHorizontal(el, leftOffset){
581	var leftVal = parseInt(leftOffset) || $(el).offset().left;
582	return (leftVal + $(el).width() <= getWindowWidth() + getScrollLeft() && leftVal - getScrollLeft() >= 0);
583};
584
585function 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
616Number.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};