/javascripts/fg.menu.js

http://raihan.googlecode.com/ · JavaScript · 645 lines · 495 code · 75 blank · 75 comment · 103 complexity · 4f556f95a35e425d92d1e4c7ae1bf91f MD5 · raw file

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