|
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 }; |