1// global pause variable 2window.menusPaused = false; 3 4// menu nav controller object 5function MenuNav(nav, options) { 6 7 // set up the object 8 var self = this; 9 var $nav = $(nav); 10 $nav[0].controller = this; 11 var lastOver = null; 12 var $otherMenuNavs = $(null); 13 14 // information about self 15 var isRightAligned = $nav.is(".horizontal.rightAligned"); 16 var isHorizontal = $nav.is(".horizontal"); 17 var isFullWidth = $nav.is(".horizontal.block.fullWidth"); 18 var isSlab = $nav.is(".horizontal.block.slab"); 19 var isSlabText = $nav.is(".horizontal.block.slab.slabtext") && $("nav.horizontal.block.slab.slabtext > div").is(".slabtext"); 20 var isSlabMaxTwo = $nav.is(".horizontal.block.slab.slabmaxtwocol"); 21 var isSlabMaxThree = $nav.is(".horizontal.block.slab.slabmaxthreecol"); 22 var isOneDeep = $nav.is(".horizontal.block.slab.onedeep"); 23 var isClean = $nav.is(".horizontal.block.clean"); 24 25 this.$menus = $nav.find("ul ul"); 26 27 this.options = $.extend({ 28 foo: "bar", 29 leftOverlap: 5, 30 topOverlap: 5, 31 bounceOffset: 25, 32 fadeOutDuration: 400 33 }, options || {}); 34 35 function initialize() { 36 37 // Add in the triangles 38 if (isClean) 39 createTrianglesClean(); 40 else 41 createTriangles(); 42 43 if (isSlab) { 44 buildSlab(); 45 } 46 // find other menu navs to talk to 47 $("nav.menu").each(function () { 48 if (this !== $nav[0]) { 49 $otherMenuNavs = $otherMenuNavs.add(this); 50 } 51 }); 52 53 // event delegates for all menus 54 $nav.on("mouseenter", "ul li", onMouseEnter) 55 .on("mouseleave", "ul li", onMouseLeave) 56 // .on("blur", "ul li", onMouseLeave) 57 .on("click", "ul li .triangle", arrowClick) 58 .on("keydown", "ul li .triangle", arrowClick) 59 .on("click", "ul li a", onLinkClicked) 60 .on("touchstart", "ul li a", onLinkTouchStart) 61 .on("touchmove", "ul li a", onLinkTouchMove) 62 .on("touchend", "ul li a", onLinkTouchEnd); 63 } 64 65 function buildSlab() { 66 var $topLevelSectionsWithChildren = $nav.children("ul").children("li").has("ul"); 67 $topLevelSectionsWithChildren.each(function (i) { 68 var numOfCat = $(this).children("ul").children("li").length; 69 //remove the deep links - they won't be shown, and they just mess up the counts 70 $(this).children("ul").children("li").children("ul").children("li").children("ul").remove(); 71 if (isOneDeep) 72 $(this).children("ul").children("li").children("ul").remove(); 73 var numOfLinks = numOfCat + $(this).children("ul").children("li").children("ul").children("li").length; 74 if ((numOfCat >= 12 || numOfLinks >= 40 && numOfCat >= 4) && !isSlabMaxTwo && !isSlabMaxThree) { 75 $(this).addClass("fourcol"); 76 var forth = parseInt(numOfLinks / 4); 77 var cut = 0; 78 var $links = $(this).children("ul").children("li").find("a").slice(forth, forth + 1); 79 if ($links.parentsUntil("nav").length === 6) 80 cut = $links.parent().parent().parent().index(); 81 else if ($links.parentsUntil("nav").length === 4) 82 cut = $links.parent().index(); 83 if (numOfCat > 5 && cut < 3) cut++; 84 if (cut == 0) cut = 1; 85 $(this).children("ul").children("li").slice(0, cut).wrapAll("<li class='col one'><ul>"); 86 var $links = $(this).children("ul").children("li").find("a").slice(forth * 2, forth * 2 + 1); 87 if ($links.parentsUntil("nav").length === 6) 88 cut = $links.parent().parent().parent().index() + 1; 89 else if ($links.parentsUntil("nav").length === 4) 90 cut = $links.parent().index() + 1; 91 $(this).children("ul").children("li").slice(1, cut).wrapAll("<li class='col two'><ul>"); 92 var $links = $(this).children("ul").children("li").find("a").slice(forth * 3, forth * 3 + 1); 93 if ($links.parentsUntil("nav").length === 6) 94 cut = $links.parent().parent().parent().index() + 1; 95 else if ($links.parentsUntil("nav").length === 4) 96 cut = $links.parent().index() + 1; 97 $(this).children("ul").children("li").slice(2, cut).wrapAll("<li class='col three'><ul>"); 98 $(this).children("ul").children("li").slice(3).wrapAll("<li class='col four'><ul>"); 99 } 100 else if ((numOfCat >= 9 || numOfLinks >= 30 && numOfCat >= 3) && !isSlabMaxTwo) { 101 $(this).addClass("threecol"); 102 var third = parseInt(numOfLinks / 3); 103 var cut = 0; 104 var $links = $(this).children("ul").children("li").find("a").slice(third, third + 1); 105 if ($links.parentsUntil("nav").length === 6) 106 cut = $links.parent().parent().parent().index(); 107 else if ($links.parentsUntil("nav").length === 4) 108 cut = $links.parent().index(); 109 if (numOfCat >= 5 && cut < 2) cut++; 110 if (cut == 0) cut = 1; 111 //console.log(cut); 112 $(this).children("ul").children("li").slice(0, cut).wrapAll("<li class='col one'><ul>"); 113 var $links = $(this).children("ul").children("li").find("a").slice(third*2,third*2+1); 114 if ($links.parentsUntil("nav").length === 6) 115 cut = $links.parent().parent().parent().index() + 1; 116 else if ($links.parentsUntil("nav").length === 4) 117 cut = $links.parent().index() + 1; 118 $(this).children("ul").children("li").slice(1, cut).wrapAll("<li class='col two'><ul>"); 119 $(this).children("ul").children("li").slice(2).wrapAll("<li class='col three'><ul>"); 120 } 121 else if (numOfCat >= 2 && numOfLinks >= 10) { 122 $(this).addClass("twocol"); 123 var half = parseInt(numOfLinks / 2); 124 var cut = 0; 125 //find the middle 126 var $links = $(this).children("ul").children("li").find("a").slice(half, half+1);
127 if ($links.parentsUntil("nav").length === 6) 128 cut = $links.parent().parent().parent().index(); 129 else if ($links.parentsUntil("nav").length === 4) 130 cut = $links.parent().index(); 131 if (cut == 0) cut = 1; 132 $(this).children("ul").children("li").slice(0, cut).wrapAll("<li class='col one'><ul>"); 133 $(this).children("ul").children("li").slice(1).wrapAll("<li class='col two'><ul>"); 134 } 135 else { 136 $(this).addClass("onecol"); 137 $(this).children("ul").children("li").slice(0).wrapAll("<li class='col one'><ul>"); 138 } 139 }); 140 if (isSlabText) { 141 $('div.slabtext').removeClass('hidden').detach().appendTo('nav.horizontal.block.slab.slabtext > ul > li > ul').wrap("<li class='slabtext'>"); 142 } 143 else 144 { 145 $nav.removeClass('slabtext'); 146 } 147 } 148 149 function createTriangles() { 150 if (isHorizontal) { 151 var $topLevelSectionsWithChildren = $nav.children("ul").children("li").has("ul"); 152 $topLevelSectionsWithChildren.children("a").each(function (index, value) { 153 $(value).attr('aria-haspopup', 'true').attr('aria-expanded', 'false') 154 .append($("<button class='triangle' tabindex='0'><span class='hidden'>show submenu for " + $(value).text() + "</span>▼</button>")); 155 }); 156 if (!$nav.is(".slab")) { 157 // add triangles to subsections 158 $topLevelSectionsWithChildren.find("li").has("ul").children("a").each(function (index, value) { 159 $(value).attr('aria-haspopup', 'true').attr('aria-expanded', 'false') 160 .prepend($("<button class='triangle' tabindex='0'><span class='hidden'>show submenu for " + $(value).text() + "</span>►</button>")); 161 }); 162 } 163 } else { 164 $nav.find("li").has("ul").children("a").each(function (index, value) { 165 $(value).attr('aria-haspopup', 'true').attr('aria-expanded', 'false') 166 .prepend($("<button class='triangle' tabindex='0'><span class='hidden'>show submenu for " + $(value).text() + "</span>►</button>")); 167 }); 168 } 169 } 170 171 function createTrianglesClean() { 172 if (isHorizontal) { 173 var $topLevelSectionsWithChildren = $nav.children("ul").children("li").has("ul"); 174 $topLevelSectionsWithChildren.children("a").each(function (index, value) { 175 let $childmenuid = $(value).next('ul').attr('id'); 176 $(value).after($("<button class='triangle' aria-expanded='false' aria-controls='" + $childmenuid + "' aria-label='show submenu for " + $(value).text() + "'>▼</button>")); 177 }); 178 if (!$nav.is(".slab")) { 179 //let $childmenuid = $(value).next('ul').attr('id'); 180 $topLevelSectionsWithChildren.find("li").has("ul").children("a").each(function (index, value) { 181 $(value).after($("<button class='triangle' aria-label='show submenu for " + $(value).text() + "'>►</button>")); 182 }); 183 } 184 } else { 185 //let $childmenuid = $(value).next('ul').attr('id'); 186 $nav.find("li").has("ul").children("a").each(function (index, value) { 187 $(value).after($("<button class='triangle' aria-label='show submenu for " + $(value).text() + "'>►</button>")); 188 }); 189 } 190 } 191 192 // event handlers 193 function onMouseEnter(event) { 194 var $thisLI = $(this); // the element the event landed on 195 if (hasNoMenu($thisLI)) { return; } // we have no business here 196 var $menuUL = $thisLI.children("ul"); 197 var $openSiblings = self.getOpenSiblingMenus($menuUL); 198 199 // we're over a menu title, open the menu! 200 if (!$thisLI.parent().parent().is("nav.horizontal.block") && $openSiblings.length) { 201 if (isInRightBit(event, $thisLI) && fromAdjacent(this)) { 202 // we should delay this open a bit to see if user was 203 // aiming for an already open menu and is here by accident 204 self.dwelledOpenTimer = setTimeout(function () { 205 self.openMenu($menuUL); 206 }, 500); 207 } else { // nah, just open the menu right away 208 if (self.dwelledOpenTimer) { 209 clearTimeout(self.dwelledOpenTimer); 210 } 211 self.openMenu($menuUL); 212 } 213 } else { // no other sibling menus open, all clear 214 if (isSlab) 215 self.dwelledOpenTimer = setTimeout(function () { self.openMenu($menuUL); }, 400); 216 else 217 self.openMenu($menuUL); 218 } 219 lastOver = this; 220 } 221 function arrowClick(event) { 222 if (event.keyCode === 13 || event.type === 'click') { 223 var $thisLI = $(this).parent().parent(); // the element the event landed on 224 if (hasNoMenu($thisLI)) { $thisLI = $(this).parent(); } // maybe new clean version 225 if (hasNoMenu($thisLI)) { return; } // we have no business here 226 var $menuUL = $thisLI.children("ul"); 227 var $openSiblings = self.getOpenSiblingMenus($menuUL); 228
229 if ($menuUL.hasClass('arrowclicked')) { 230 $menuUL.removeClass('arrowclicked'); 231 // clearTimeout(self.dwelledOpenTimer); 232 self.closeMenu($menuUL); 233 } 234 else { 235 // we've clicked an arrow open the menu 236 if (!$thisLI.parent().parent().is("nav.horizontal.block") && $openSiblings.length) { 237 if (self.dwelledOpenTimer) { clearTimeout(self.dwelledOpenTimer); } 238 self.openMenu($menuUL); 239 } else { // no other sibling menus open, all clear 240 self.openMenu($menuUL); 241 } 242 $menuUL.addClass('arrowclicked'); 243 } 244 event.stopPropagation(); 245 event.preventDefault(); 246 return false; 247 } 248 } 249 250 function onMouseLeave() { 251 var $this = $(this); // the element the event landed on 252 var $menu = $this.children("ul"); 253 if (hasNoMenu($this)) { return; } // we have no business here 254 if ($menu.hasClass('arrowclicked')) { return; } 255 256 // if we leave the menu item's LI (which includes all its sub menus), we need to set a close timer 257 this.closeTimer = setTimeout(function () { 258 self.closeMenu($menu); 259 }, 1000); 260 261 // if this is a dwelled-upon rightmost third (or whatever) of an item, and cursor leaves it, we need to cancel the delayed open timer 262 if (self.dwelledOpenTimer) { 263 clearTimeout(self.dwelledOpenTimer); 264 } 265 } 266 267 function onLinkClicked() { 268 // console.log("onLinkClicked"); 269 } 270 271 function onLinkTouchStart(event) { 272 var firstTouch = event.originalEvent.touches[0]; 273 // console.log("onLinkTouchStart"); 274 275 // keep track of the movement of this touch. 276 // SWIPE HANDLING DEPRECATED in favor of the global swipe tracking 277 278 self.swipeDistance = 0; 279 self.swipeOrigin = { 280 x: firstTouch.pageX, 281 y: firstTouch.pageY 282 }; 283 284 event.preventDefault(); 285 event.stopPropagation(); 286 } 287 288 function onLinkTouchMove(event) { 289 // SWIPE HANDLING DEPRECATED in favor of the global swipe tracking 290 var firstTouch = event.originalEvent.touches[0]; 291 var position = { 292 x: firstTouch.pageX, 293 y: firstTouch.pageY 294 }; 295 self.swipeDistance = calculateSwipeDistance(position); 296 297 // console.log("onLinkTouchMove"); 298 // console.log("swipeDistance = ", self.swipeDistance); 299 300 // call preventDefault(), or else a mouseenter event will be synthesized 301 event.preventDefault(); 302 event.stopPropagation(); 303 } 304 305 function onLinkTouchEnd(event) { 306 // console.log("onLinkTouchEnd"); 307 308 self.swipeDistance = 0; 309 self.swipeOrigin = null; 310 311 var $this = $(this), 312 $li = $this.parent(), 313 $menu = $li.children("ul"); 314 315 if (wasATap()) { 316 // console.log("that was a tap."); 317 } 318 319 // alert("touched"); 320 // if there's no menu under this, we want it to act like a normal link. 321 // if it has an already open menu, we want this second tap to be treated 322 // as an attempt to navigate to the menu title's section. 323 if (hasNoMenu($li) || $menu.is(".open")) { 324 // go to the link 325 // console.log("navigating to link..."); 326 window.location.href = $this.attr("href"); 327 } else { 328 // console.log("opening menu."); 329 self.openMenu($menu); 330 } 331 332 // call event.preventDefault(), or else a click event will be synthesized 333 event.preventDefault(); 334 event.stopPropagation(); 335 } 336 337 // event handler utilities 338 function wasATap() { 339 return (window.globalMenuNavController.swipeState === null || 340 (window.globalMenuNavController.swipeState && 341 window.globalMenuNavController.swipeState.isARealSwipe === false)); 342 } 343 344 function isInRightBit(event, $e) { 345 var width = $e.outerWidth(); 346 var itemLeft = $e.offset().left; 347 var bit = (itemLeft + ((8 / 10) * width)); 348 349 return (event.pageX > bit); 350 } 351 // returns true if the mouse arrived at "item" from an adjacent 352 // menu title note: does NOT include those without any children 353 // (and therefore a menu) 354 function fromAdjacent(item) { 355 var $neighbors = $(item).prev().add($(item).next()); 356 if ($neighbors.is($(lastOver))) { 357 return true; 358 } else { 359 return false; 360 } 361 } 362 363 // actions 364 this.openMenu = function openMenu(menu) { 365 if (window.menusPaused) { return; } 366 367 var $menu = $(menu); 368 var $title = $menu.parent(); 369 370 // clear close timer and/or cancel animations 371 if ($title[0].closeTimer) { clearTimeout($title[0].closeTimer); } 372 373 if ($menu.is(".open")) { 374 // the menu is still technically open, we just need to 375 // stop it from animating closed if that's what it's doing 376 $title.add($menu).removeClass("closing"); 377 $menu.stop().css("opacity", ""); 378 } else { 379 // if we're in a deep subsection somewhere, and we're trying 380 // to open the menu of its top-level ancestor, that top-level 381 // section will (and should) be marked "open", so we need to 382 // remember that so we don't blow that marker away once we 383 // close the menu 384 // if ($title.is(".open")) 385 // $title.data("wasOpen", true); 386 387 self.closeOtherMenuNavs(); 388 389 // this is necessary to get IE6/7 to play nice 390 $menu.css({ visibility: "hidden" }); 391 392 // this seems verbose, but in profiling, this was the 393 // fastest way to do it 394 $title.addClass("open"); 395 $menu.addClass("open"); 396 $nav.addClass("open"); 397 //$title.children('a').attr('aria-expanded', 'true'); 398 $title.children('button').attr('aria-expanded', 'true'); 399 400 if (!isSlab) 401 positionMenu($menu); 402 403 // self.raiseBox(); 404 $menu.css({ visibility: "visible" }); 405 self.closeSiblingMenus($menu); 406 407 if (isSlab) { 408 var highestCol = 0; 409 if ($menu.children('li.col.three').length)
410 highestCol = Math.max(Math.max($menu.children('li.col.one').height(),$menu.children('li.col.two').height()), $menu.children('li.col.three').height()); 411 else if ($menu.children('li.col.two').length) 412 highestCol = Math.max($menu.children('li.col.one').height(),$menu.children('li.col.two').height()); 413 else 414 highestCol = $menu.children('li.col.one').height(); 415 if (isSlabText) 416 highestCol = Math.max(highestCol, $menu.children('li.slabtext').height()); 417 if (highestCol > 101) 418 $menu.children('li.col').height(highestCol); 419 } 420 } 421 }; 422 423 this.closeMenu = function closeMenu(menu, closeImmediately) { 424 var $menu = $(menu); 425 if ($menu.length === 0 || window.menusPaused) { return; } 426 var $title = $(menu).parent(); 427 428 function closeNow() { 429 // clear any close timer still pending, we're going to close right 430 // away instead 431 if ($title[0].closeTimer) { clearTimeout($title[0].closeTimer); } 432 433 // halt animations on descendants and self 434 // $menu.find("ul").andSelf().stop(); 435 $menu.find("ul").addBack().stop(); 436 437 // $title.removeClass("closing" + ($title.data("wasOpen") ? "" : " open")); 438 $title.removeClass("closing open"); 439 $menu.removeClass("closing open"); 440 //$title.children('a').attr('aria-expanded', 'false'); 441 $title.children('button').attr('aria-expanded', 'false'); 442 443 if ($nav.find("ul.open").length === 0) { 444 $nav.removeClass("open"); 445 // self.lowerBox(); 446 } 447 448 // if it was in the middle of an animation before we 449 // pulled the plug, reset it 450 $menu.css({opacity: ""}); 451 452 // clean up any stray descendant menus 453 closeDescendantMenus(menu); 454 } 455 456 if (closeImmediately || $title.siblings(".open").length) { 457 closeNow(); 458 } else { 459 $menu.add($menu.parent()).addClass("closing"); 460 $menu.animate({opacity: 0}, self.options.fadeOutDuration, function () { 461 closeNow(); 462 }); 463 } 464 }; 465 this.toggleMenu = function toggleMenu(menu) { 466 var $menu = $(menu); 467 // UNFINISHED 468 }; 469 470 this.closeAllMenus = function closeAllMenus() { 471 self.$menus.filter(".open").each(function (i, menu) { 472 self.closeMenu(menu, true); 473 }); 474 }; 475 476 function closeDescendantMenus(menu) { 477 var $menu = $(menu); 478 var $openChildren = $menu.children("li.open").children("ul"); 479 if ($openChildren.length) { 480 $openChildren.each(function () { 481 self.closeMenu(this, true); 482 }); 483 } 484 } 485 486 // this should get moved into the global menu nav controller 487 this.closeOtherMenuNavs = function closeOtherMenuNavs() { 488 $otherMenuNavs.filter(".open").each(function () { 489 this.controller.closeAllMenus(); 490 }); 491 }; 492 this.closeSiblingMenus = function closeSiblingMenus(menu) { 493 var $title = $(menu).parent(); 494 $title.siblings(".open").children("ul").each(function () { 495 self.closeMenu(this, true); 496 }); 497 }; 498 499 // accessors 500 this.getSiblingMenus = function getSiblingMenus(menuUL) { 501 return $(menuUL).parent().siblings("li").children("ul"); 502 }; 503 this.getOpenSiblingMenus = function getOpenSiblingMenus(menuUL) { 504 return this.getSiblingMenus(menuUL).filter(".open"); 505 }; 506 this.getOrientation = function getOrientation() { 507 return $nav.hasClass("vertical") ? "vertical" : "horizontal"; 508 }; 509 this.isSlab = function isSlab() { 510 return $nav.hasClass("slab"); 511 }; 512 this.isSub = function isSub(menuUL) { 513 var $parentSections = $(menuUL).parents("nav li"); 514 var depth = $parentSections.length - 1; 515 return depth; 516 }; 517 // function getMenuTitle(menuUL) { 518 // return $(menuUL).parent().children("a").text(); 519 // } 520 // takes an section's LI. returns true if the section has children 521 function hasNoMenu(li) { 522 var $menu = $(li).children("ul"); 523 return ($menu.length === 0 || (self.isSlab() && sectionLevel(li) > 0)); 524 } 525 // returns the apparent "level" of a section. "home" and children of "home" 526 // are level 0, the children of those are level 1, etc. 527 function sectionLevel(li) { 528 return $(li).parentsUntil("nav", "li").length; 529 } 530 // returns an integer. takes a current position object to compare against 531 // the last recorded swipe origin position 532 function calculateSwipeDistance(currentPosition) { 533 var delta = lineLength(currentPosition.x, currentPosition.y, 534 self.swipeOrigin.x, self.swipeOrigin.y); 535 return Math.round(Math.abs(delta)); 536 } 537 538 // =================================================== 539 // internal positioning and utility functions 540 // =================================================== 541 // takes a menu (ul), and a positionInfo hash 542 function calcLeft(menu, i) { 543 var $menu = $(menu); 544 545 if ($menu.parents(".bounced").length) { 546 return -i.menu.outerWidth + self.options.leftOverlap; 547 } else { 548 if (self.getOrientation() === "vertical" || i.menu.isSub) { 549 return i.parent.outerWidth - self.options.leftOverlap; 550 } else { // horizontal 551 if (isFullWidth && $("html").is(".mozilla-cell-context-bug")) { 552 // work around FF's lack of position: relative on 553 // display:table-cell elements 554 return i.parent.position.left + i.navLink.position.left; 555 } else if (isSlab) { 556 return 0; 557 } else if (isRightAligned) { 558 // console.log(i.navLink.position.left); 559 return i.navLink.position.left + (i.navLink.outerWidth - i.menu.outerWidth);
560 } 561 else { 562 return i.navLink.position.left; 563 } 564 // return 0; 565 } 566 } 567 } 568 569 // takes a menu (ul), and a positionInfo hash 570 function calcTop(menu, i) { 571 var $menu = $(menu); 572 var topBorder = parseInt($menu.css("border-top-width"), 10); 573 var paddingTop = parseInt($menu.css("padding-top"), 10); 574 if (isNaN(topBorder)) { topBorder = 0; } 575 if (isNaN(paddingTop)) { paddingTop = 0; } 576 var topOverlap = (topBorder + paddingTop); 577 // var topOverlap = self.options.topOverlap; 578 var top; 579 580 if (self.getOrientation() === "vertical" || i.menu.isSub) { 581 top = i.navLink.position.top - topOverlap; 582 } else { // horizontal 583 top = i.navLink.outerHeight + i.navLink.position.top; 584 } 585 586 return top; 587 } 588 589 // FIXME: this overreacts to tall root horizontal menus in short windows, like with open debuggers 590 // takes a menu (ul), and a positionInfo hash 591 function bounceBottom($menu, i) { 592 // do the bottom bounce 593 var bottomOverlap = (i.menu.offset.top + i.menu.outerHeight) - (i.window.height + i.window.scrollTop); 594 if (bottomOverlap > 0) { 595 var bouncedMenuTop = i.menu.position.top - bottomOverlap; 596 597 // we don't bounce if this is a top-level horizontal menu 598 if (isHorizontal && !self.isSub($menu[0])) { return; } 599 600 // if the bottom bounce puts it beyond the top of the window, then just start at the top of the window. 601 //if (bouncedMenuTop < 0) bouncedMenuTop = (i.window.scrollTop - i.menu.offset.top); 602 603 // keep accounting up to date 604 i.menu.position.top = bouncedMenuTop; 605 606 // move the menu 607 $menu.css({ top: i.menu.position.top + "px" }); 608 } 609 } 610 611 // takes a menu (ul), and a positionInfo hash 612 function bounceTop($menu, i) { 613 // do the top bounce 614 var topOverlap = i.window.scrollTop - i.menu.offset.top; 615 // we don't bounce if this is a top-level horizontal menu 616 if (isHorizontal && !self.isSub($menu[0])) { return; } 617 618 console.log(i.menu.position.top); 619 if (topOverlap > 0) { 620 // keep accounting up to date 621 i.menu.position.top = i.menu.position.top + topOverlap; 622 $menu.css({ top: i.menu.position.top + "px" }); 623 } 624 console.log(i.menu.position.top); 625 if (i.menu.position.top < topOverlap) { 626 i.menu.position.top = topOverlap; 627 $menu.css({ top: i.menu.position.top + "px" }); 628 } 629 console.log(i.menu.position.top); 630 631 } 632 633 // takes a menu (ul), and a positionInfo hash 634 function bounceRight($menu, i) { 635 // do the right edge bounce -- this one is more involved 636 var rightOverlap = i.window.width - (i.menu.offset.left + i.menu.outerWidth); 637 if (rightOverlap < 0) { 638 var bouncedMenuLeft = i.menu.position.left + rightOverlap; 639 //var bouncedMenuLeft = i.menu.position.left - (2 * i.menu.outerWidth) + (2 * self.options.leftOverlap); 640 641 // if this is a 1st level subsection's menu in a 642 // horizontal nav, and we need to bounce, we should just 643 // go left instead of doing a naive bounce, which will 644 // obscure the subsection's title with the new submenu 645 if (isHorizontal && i.menu.isSub === 1) { 646 bouncedMenuLeft = -i.menu.outerWidth + self.options.leftOverlap; 647 } 648 649 $menu.css({ left: bouncedMenuLeft + "px" }); 650 651 // keep our accounting up to date 652 i.menu.position.left = bouncedMenuLeft; 653 $menu.addClass("bounced"); 654 655 // if this is a second sub-menu, offset it vertically a bit 656 if (i.menu.isSub > 1) { 657 // keep our accounting up to date 658 i.menu.position.top = i.menu.position.top + self.options.bounceOffset; 659 $menu.css({ top: i.menu.position.top + "px" }); 660 } 661 } 662 return false; 663 } 664 665 function matchTitleWidth($menu, i) { 666 if (!isHorizontal) { return; } 667 668 var titleWidth = $menu.closest("li").outerWidth(); 669 if (titleWidth > i.menu.outerWidth) { 670 $menu.css({ width: titleWidth + 20 + "px" }); 671 // keep our accounting of the width up to date 672 i.menu.outerWidth = $menu.outerWidth(); 673 } 674 } 675 676 function positionMenu(menu) { 677 // menu must not be display:none when we get here 678 var $menu = $(menu); 679 var $navLink = $menu.parent().children("a"); 680 681 // EXPERIMENTAL STUFF, commented out for now 682 // if ($menu[0].positioned) return; 683 684 // var originalStyle = { 685 // visibility: $menu.css("visibility"), 686 // display: $menu.css("display") 687 // }; 688 // $menu.css({visibility:"hidden", display:"block"}); 689 690 // capture all relevant position data, minus the calculated 691 // position of the menu 692 var positionInfo = { 693 menu: { 694 dom: $menu, 695 outerHeight: $menu.outerHeight(), 696 outerWidth: $menu.outerWidth(), 697 // offset: $menu.offset(), 698 // position: $menu.position(), 699 offset: null, 700 position: null, 701 isSub: self.isSub($menu), 702 $navLink: $navLink 703 }, 704 parent: { 705 outerWidth: $menu.parent().outerWidth(), 706 outerHeight: $menu.parent().outerHeight(), 707 position: $menu.parent().position() 708 }, 709 navLink: { 710 dom: $navLink, 711 position: $navLink.position(), 712 outerWidth: $navLink.outerWidth(), 713 outerHeight: $navLink.outerHeight() 714 }, 715 window: { 716 width: $(window).width(), 717 height: $(window).height(), 718 scrollTop: $(window).scrollTop(), 719 offset: $menu.offset() 720 } 721 }; 722 723 // place it, initially 724 $menu.css({ 725 left: calcLeft($menu, positionInfo) + "px", 726 top: calcTop($menu, positionInfo) + "px" 727 }); 728 729 // now record the calculated position of the menu 730 positionInfo.menu.offset = $menu.offset(); 731 positionInfo.menu.position = $menu.position(); 732 733 // make adjustments and do bounces 734 if (!positionInfo.menu.isSub) { 735 matchTitleWidth($menu, positionInfo); 736 } 737 bounceBottom($menu, positionInfo); 738 bounceTop($menu, positionInfo); 739 bounceRight($menu, positionInfo); 740 741 // $menu.css(originalStyle); 742 $menu[0].positioned = true; 743 } 744 745 // do the setup 746 initialize(); 747} 748 749function GlobalMenuNavController() 750{ 751 var self = this; 752 753 this.swipeState = null; 754 this.swipeThreshold = 5; 755 756 // PUBLIC PROPERTIES 757 this.navs = []; // list of all menu navs controllers 758 759 // EVENT HANDLERS 760 function onWindowKeyPress(event) { 761 // console.log("got key press, code " + event.keyCode); 762 if (event.keyCode === 80) { 763 if (window.menusPaused) { 764 // console.log("unpausing menus."); 765 window.menusPaused = false; 766 } else { 767 if (getOpenMenus().length > 0) { 768 // console.log("pausing menus."); 769 window.menusPaused = true; 770 } 771 } 772 } 773 } 774 775 function onWindowTouchMove(event) { 776 var oe = event.originalEvent; 777
778 // touch move, this means a scroll or something has happened that 779 // shouldn't close all menus when the touch event ends. 780 if (self.swipeState) { 781 self.swipeState.currentPosition = { 782 x: oe.touches[0].pageX, 783 y: oe.touches[0].pageY 784 }; 785 self.swipeState.swipeLength = calculateSwipeDistance(self.swipeState); 786 self.swipeState.isARealSwipe = (self.swipeState.swipeLength >= self.swipeThreshold); 787 788 // console.log("onWindowTouchMove: swipe length: " + self.swipeState.swipeLength + (self.swipeState.isARealSwipe ? "; real swipe now" : "; not a real swipe")); 789 } else { 790 self.swipeState = { 791 completed: false, 792 currentPosition: { 793 x: null, 794 y: null 795 }, 796 startPosition: { 797 x: oe.touches[0].pageX, 798 y: oe.touches[0].pageY 799 } 800 }; 801 } 802 } 803 804 function onWindowTouchEnd() { 805 // var oe = event.originalEvent; 806 // console.log("onWindowTouchEnd"); 807 if (self.swipeState && self.swipeState.isARealSwipe) { 808 // console.log("self.swipeState.isARealSwipe = " + self.swipeState.isARealSwipe); 809 } 810 if (!self.swipeState || !self.swipeState.isARealSwipe) { 811 closeAllMenus(); 812 } 813 814 // above here was the last call for responding to the swipe state 815 self.swipeState = null; 816 } 817 818 // ACTIONS 819 function closeAllMenus() { 820 // if the cancel flag is set, reset it and don't close the menus...but 821 // just this once. 822 // if (cancelCloseAll) { 823 // cancelCloseAll = false; 824 // return; 825 // } 826 $.each(self.navs, function () { 827 this.closeAllMenus(); 828 }); 829 } 830 this.initializeAllMenuNavs = function () { 831 $("nav.menu").each(function (i, nav) { 832 self.addNav(nav); 833 }); 834 }; 835 836 this.addNav = function (navElement) { 837 self.navs.push(new MenuNav(navElement)); 838 }; 839 840 // INFO FUNCTIONS 841 function getOpenMenus() { 842 return $("nav.menu li.open"); 843 } 844 845 // returns an integer. takes a current position object to compare against 846 // the last recorded swipe origin position 847 function calculateSwipeDistance(swipeState) { 848 var delta = lineLength(swipeState.currentPosition.x, swipeState.currentPosition.y, 849 swipeState.startPosition.x, swipeState.startPosition.y); 850 return Math.round(Math.abs(delta)); 851 } 852 853 // WIRE UP EVENTS 854 $(window) 855 .on("keyup", onWindowKeyPress) 856 .on("touchmove", onWindowTouchMove) 857 .on("touchend", onWindowTouchEnd); 858} 859 860// FREE FUNCTIONS 861function lineLength(x, y, x0, y0) { 862 return Math.sqrt((x -= x0) * x + (y -= y0) * y); 863} 864 865// BUG TESTS 866// this tests for this bug: 867// https://bugzilla.mozilla.org/show_bug.cgi?id=63895 868function detectMozillaCellContextBug() { 869 var debug = false; 870 var $box = $("<ul id='fftest'style='" + (debug ? "" : "visibility:hidden;") + "padding:0;margin:0;display:table;position:absolute;top:0;left:0;'><li style='margin:0;background:blue;position:relative;display:table-cell;'>woo woo</li><li style='margin:0;background:blue;position:relative;display:table-cell;'>hahhaha<ul style='padding:0;display:block;position:absolute;left:0;top:0;'><li class='spud' style='margin:0;display:block;background:red;height:5px;width:5px;'></li></ul></li></ul>"); 871 872 // test 873 var originalPosition = $(document.body).css("position"); 874 $(document.body).css("position", "static").append($box); 875 var offsetLeft = $box.find(".spud").offset().left; 876 var result = (offsetLeft === 0 && $.browser.mozilla); 877 878 // clean up 879 $(document.body).css("position", originalPosition); 880 if (debug) { 881 // console.log("spud offset: " + offsetLeft); 882 // console.log("firefox context bug: " + (result ? "present" : "not present")); 883 } else { 884 $box.remove(); 885 } 886 887 // report 888 if (result) { $("html").addClass("mozilla-cell-context-bug"); } 889 return result; 890} 891 892// instantiate controllers for each menu nav on the page 893$(document).ready(function documentReady() { 894 // fire up the global menu nav controller and init all menus navs 895 var gmnc = window.globalMenuNavController = new GlobalMenuNavController(); 896 gmnc.initializeAllMenuNavs(); 897 898 // runtime test for nasty gecko bug 899 // detectMozillaCellContextBug(); 900});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.