1/** 2 * @file 3 * Defines Javascript behaviors for MegaMenu frontend. 4 */ 5 6(function ($, Drupal, drupalSettings, once) { 7 "use strict"; 8 9 Drupal.TBMegaMenu = Drupal.TBMegaMenu || {}; 10 11 Drupal.TBMegaMenu.oldWindowWidth = 0; 12 Drupal.TBMegaMenu.displayedMenuMobile = false; 13 Drupal.TBMegaMenu.supportedScreens = [980]; 14 Drupal.TBMegaMenu.focusableElements = 'a:not([disabled]), button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), details:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])'; 15 Drupal.TBMegaMenu.menuResponsive = function () { 16 var windowWidth = window.innerWidth ? window.innerWidth : $(window).width(); 17 var navCollapse = $('.tb-megamenu').children('.nav-collapse'); 18 if (windowWidth < Drupal.TBMegaMenu.supportedScreens[0]) { 19 navCollapse.addClass('collapse'); 20 if (Drupal.TBMegaMenu.displayedMenuMobile) { 21 navCollapse.css({height: 'auto', overflow: 'visible'}); 22 } 23 else { 24 navCollapse.css({height: 0, overflow: 'hidden'}); 25 } 26 } 27 else { 28 // If width of window is greater than 980 (supported screen). 29 navCollapse.removeClass('collapse'); 30 if (navCollapse.height() <= 0) { 31 navCollapse.css({height: 'auto', overflow: 'visible'}); 32 } 33 } 34 }; 35 36 Drupal.TBMegaMenu.focusNextPrevElement = function (direction) { 37 // Add all the elements we want to include in our selection 38 var $current = $(document.activeElement); 39 40 if ($current.length) { 41 var $focusable = $(Drupal.TBMegaMenu.focusableElements).filter(function () { 42 var $this = $(this); 43 return $this.closest('.tb-megamenu-subnav').length === 0 && $this.is(':visible'); 44 }) 45 46 var index = $focusable.index($current); 47 if (index > -1) { 48 if (direction === 'next') { 49 var nextElement = $focusable[index + 1] || $focusable[0]; 50 } 51 else { 52 var nextElement = $focusable[index - 1] || $focusable[0]; 53 } 54 55 nextElement.focus(); 56 } 57 } 58 } 59 60 61 Drupal.behaviors.tbMegaMenuAction = { 62 attach: function (context, settings) { 63 64 $(once('tb-megamenu', '.tb-megamenu', context)).each(function () { 65 66 /* Keyboard Control Setup */ 67 // Semi-Global Variables 68 var navParent = document.querySelector('.tb-megamenu'), 69 linkArray = new Array(), 70 curPos = new Array(-1,-1,-1); 71 72 // Each Top-Level Link 73 $(this).find('.level-1').children('a, span').not('.mobile-only').each(function (i, toplink) { 74 linkArray[i] = new Array(); 75 76 // Add Link to Array 77 linkArray[i][-1] = toplink; 78 79 // Determine Coordinates 80 $(toplink).data({ coordinate: [i, -1] }); 81 82 // Each Column 83 $(toplink).next().children().children().children('.mega-col-nav').each(function (j, column) { 84 // Only add to the linkArray if menu items exist. 85 // TODO - this does not allow for tabbing to links in blocks, only menu item links. 86 if ($(column).find(Drupal.TBMegaMenu.focusableElements).length > 0) { 87 88 linkArray[i][j] = new Array(); 89 90 // Each Link 91 $(column).find(Drupal.TBMegaMenu.focusableElements).each(function (k, sublink) { 92 93 // Add Link to Array 94 linkArray[i][j][k] = sublink; 95 96 // Determine Coordinates 97 $(sublink).data({ coordinate: [i, j, k] }); 98 }); // each link 99 } 100 101 }); // each column 102 103 }); // each top-level link 104 105 // Update Position on Focus 106 $(this).find(Drupal.TBMegaMenu.focusableElements).focus(function () { 107 curPos = $(this).data('coordinate'); 108 }); 109 110 // Key Pressed 111 function keydownEvent(k) { 112 // Determine Key 113 switch(k.keyCode) { 114 115 // TAB 116 case 9: 117 k.preventDefault(); 118 nav_tab(k); 119 break; 120 121 // RETURN 122 case 13: 123 nav_open_link(); 124 break; 125 126 // ESC 127 case 27: 128 nav_esc(); 129 break; 130 131 // LEFT 132 case 37: 133 k.preventDefault(); 134 nav_left(); 135 break; 136 137 // UP 138 case 38: 139 k.preventDefault(); 140 nav_up(); 141 break; 142 143 // RIGHT 144 case 39: 145 k.preventDefault(); 146 nav_right(); 147 break; 148 149 // DOWN 150 case 40: 151 k.preventDefault(); 152 nav_down(); 153 break; 154 155 // HOME 156 case 36: 157 nav_home(); 158 break; 159 160 // END 161 case 35: 162 nav_end(); 163 break; 164 165 // Else 166 default:
167 // Do nothing 168 169 } // determine key 170 } // keydownEvent 171 172 /* Keypress Functions */ 173 // Tab 174 function nav_tab(k) { 175 if (nav_is_toplink()) { 176 if (k.shiftKey) { 177 nav_prev_toplink(); 178 } 179 else { 180 nav_next_toplink(); 181 } 182 } 183 else { 184 if (k.shiftKey) { 185 nav_up(); 186 } 187 else { 188 nav_down(); 189 } 190 } 191 } 192 193 // Open Link 194 function nav_open_link() { 195 linkArray[curPos[0]][curPos[1]][curPos[2]].click(); 196 } 197 198 // Escape 199 function nav_esc() { 200 nav_close_megamenu(); 201 } 202 203 // Left 204 function nav_left() { 205 if (nav_is_toplink()) { 206 nav_prev_toplink(); 207 } 208 else { 209 nav_prev_column(); 210 } 211 } 212 213 // Right 214 function nav_right() { 215 if (nav_is_toplink()) { 216 nav_next_toplink(); 217 } 218 else { 219 nav_next_column(); 220 } 221 } 222 223 // Up 224 function nav_up() { 225 if (nav_is_toplink()) { 226 nav_prev_toplink(); 227 } 228 else { 229 if (linkArray[curPos[0]][curPos[1]][curPos[2] - 1]) { 230 // If the previous link in the array is hidden (ie, it's in a 231 // submenu that is not currently expanded), then skip to the next 232 // item in the array until we find one that's visible. 233 if ($(linkArray[curPos[0]][curPos[1]][curPos[2] - 1]).is(':visible')) { 234 linkArray[curPos[0]][curPos[1]][curPos[2] - 1].focus(); 235 } 236 else { 237 curPos = [curPos[0], curPos[1], curPos[2] - 1]; 238 nav_up(); 239 } 240 } 241 else { 242 nav_prev_column(); 243 } 244 } 245 } 246 247 // Down 248 function nav_down() { 249 if (nav_is_toplink()) { 250 nav_next_column(); 251 } 252 else { 253 if (linkArray[curPos[0]][curPos[1]][curPos[2] + 1]) { 254 linkArray[curPos[0]][curPos[1]][curPos[2] + 1].focus(); 255 } 256 else { 257 nav_next_column(); 258 } 259 } 260 } 261 262 // Home Button 263 function nav_home() { 264 if (nav_is_toplink()) { 265 linkArray[0][-1].focus(); 266 } 267 else { 268 linkArray[curPos[0]][0][0].focus(); 269 } 270 } 271 272 // End Button 273 function nav_end() { 274 if (nav_is_toplink()) { 275 linkArray.slice(-1)[0][-1].focus(); 276 } 277 else { 278 linkArray[curPos[0]].slice(-1)[0].slice(-1)[0].focus(); 279 } 280 } 281 282 /* Helper Functions */ 283 // Determine Link Level 284 function nav_is_toplink() { 285 return (curPos[1] < 0); 286 } 287 288 // Close Mega Menu 289 function nav_close_megamenu() { 290 $('.tb-megamenu .open').removeClass('open'); 291 ariaCheck(); 292 } 293 294 // Next Toplink 295 function nav_next_toplink() { 296 if (linkArray[curPos[0] + 1]) { 297 linkArray[curPos[0] + 1][-1].focus(); 298 } 299 else { 300 nav_close_megamenu(); 301 302 // Focus on the next element. 303 Drupal.TBMegaMenu.focusNextPrevElement('next'); 304 } 305 } 306 307 // Previous Toplink 308 function nav_prev_toplink() { 309 if (linkArray[curPos[0] - 1]) { 310 linkArray[curPos[0] - 1][-1].focus(); 311 } 312 else { 313 // Focus on the previous element. 314 Drupal.TBMegaMenu.focusNextPrevElement('prev'); 315 } 316 } 317 318 // Previous Column 319 function nav_prev_column() { 320 if (linkArray[curPos[0]][curPos[1] - 1][0]) { 321 linkArray[curPos[0]][curPos[1] - 1][0].focus(); 322 } 323 else { 324 nav_parent_toplink(); 325 } 326 } 327 328 // Next Column 329 function nav_next_column() { 330 if (linkArray[curPos[0]][curPos[1] + 1]) { 331 linkArray[curPos[0]][curPos[1] + 1][0].focus(); 332 } 333 else { 334 nav_parent_toplink(); 335 } 336 } 337 338 // Go to Parent Toplink 339 function nav_parent_toplink() { 340 linkArray[curPos[0]][-1].focus(); 341 } 342 343 var ariaCheck = function () { 344 $("li.tb-megamenu-item", this).each(function () { 345 if ($(this).is('.mega-group')) { 346 // Mega menu item has mega class (it's a true mega menu) 347 if (!$(this).parents().is('.open')) { 348 // Mega menu item has mega class and its ancestor is closed, so apply appropriate ARIA attributes 349 $(this).children().attr('aria-expanded', 'false'); 350 } 351 else if ($(this).parents().is('.open')) { 352 // Mega menu item has mega class and its ancestor is o
352pen, so apply appropriate ARIA attributes 353 $(this).children().attr('aria-expanded', 'true'); 354 } 355 } 356 else if ($(this).is('.dropdown') || $(this).is('.dropdown-submenu')) { 357 // Mega menu item has dropdown (it's a flyout menu) 358 if (!$(this).is('.open')) { 359 // Mega menu item has dropdown class and is closed, so apply appropriate ARIA attributes 360 $(this).children().attr('aria-expanded', 'false'); 361 } 362 else if ($(this).is('.open')) { 363 // Mega menu item has dropdown class and is open, so apply appropriate ARIA attributes 364 $(this).children().attr('aria-expanded', 'true'); 365 } 366 } 367 else { 368 // Mega menu item is neither a mega or dropdown class, so remove ARIA attributes (it doesn't have children) 369 $(this).children().removeAttr('aria-expanded'); 370 } 371 }); 372 }; 373 374 var showMenu = function ($subMenu, mm_timeout) { 375 if ($subMenu.hasClass('mega')) { 376 $subMenu.addClass('animating'); 377 clearTimeout($subMenu.data('animatingTimeout')); 378 $subMenu.data('animatingTimeout', setTimeout(function () { 379 $subMenu.removeClass('animating') 380 }, mm_timeout)); 381 clearTimeout($subMenu.data('hoverTimeout')); 382 $subMenu.data('hoverTimeout', setTimeout(function () { 383 $subMenu.addClass('open'); 384 ariaCheck(); 385 }, 100)); 386 } 387 else { 388 clearTimeout($subMenu.data('hoverTimeout')); 389 $subMenu.data('hoverTimeout', 390 setTimeout(function () { 391 $subMenu.addClass('open'); 392 ariaCheck(); 393 }, 100)); 394 } 395 }; 396 397 var hideMenu = function ($subMenu, mm_timeout) { 398 $subMenu.children('.dropdown-toggle').attr('aria-expanded', 'false'); 399 if ($subMenu.hasClass('mega')) { 400 $subMenu.addClass('animating'); 401 clearTimeout($subMenu.data('animatingTimeout')); 402 $subMenu.data('animatingTimeout', setTimeout(function () { 403 $subMenu.removeClass('animating') 404 }, mm_timeout)); 405 clearTimeout($subMenu.data('hoverTimeout')); 406 $subMenu.data('hoverTimeout', setTimeout(function () { 407 $subMenu.removeClass('open'); 408 ariaCheck(); 409 }, 100)); 410 } 411 else { 412 clearTimeout($subMenu.data('hoverTimeout')); 413 $subMenu.data('hoverTimeout', setTimeout(function () { 414 $subMenu.removeClass('open'); 415 ariaCheck(); 416 }, 100)); 417 } 418 }; 419 420 $('.tb-megamenu-button', this).click(function () { 421 if (parseInt($(this).parent().children('.nav-collapse').height())) { 422 $(this).parent().children('.nav-collapse').css({height: 0, overflow: 'hidden'}); 423 Drupal.TBMegaMenu.displayedMenuMobile = false; 424 } 425 else { 426 $(this).parent().children('.nav-collapse').css({height: 'auto', overflow: 'visible'}); 427 Drupal.TBMegaMenu.displayedMenuMobile = true; 428 } 429 }); 430 431 var isTouch = window.matchMedia('(pointer: coarse)').matches; 432 433 if (!isTouch) { 434 var mm_duration = 0; 435 436 $('.tb-megamenu', context).each(function () { 437 if ($(this).data('duration')) { 438 mm_duration = $(this).data('duration'); 439 } 440 }); 441 442 var mm_timeout = mm_duration ? 100 + mm_duration : 500; 443 $('.nav > li, li.mega', context).bind('mouseenter', function (event) { 444 showMenu($(this), mm_timeout); 445 }); 446 447 $('.nav > li > .dropdown-toggle, li.mega > .dropdown-toggle', context).bind('focus', function (event) { 448 var $this = $(this); 449 var $subMenu = $this.closest('li'); 450 showMenu($subMenu, mm_timeout); 451 // If the focus moves outside of the subMenu, close it. 452 $(document).bind('focusin', function (event) { 453 if ($subMenu.has(event.target).length) { 454 return; 455 } 456 $(document).unbind(event); 457 hideMenu($subMenu, mm_timeout); 458 }); 459 }); 460 461 $('.nav > li, li.mega', context).bind('mouseleave', function (event) { 462 hideMenu($(this), mm_timeout); 463 }); 464 465 /** 466 * Allow tabbing by appending the open class. 467 * Works in tandem with CSS changes that utilize opacity rather than 468 * display none 469 */ 470 // If the selected anchor is not in the TB Megamenu, remove all "open" 471 // class occurrences 472 $('a, span').focus(function (event) { 473 if (!$(this).parent().hasClass('tb-megamenu-item') && !$(this).parents('.tb-megamenu-block').length) {
474 nav_close_megamenu(); 475 } 476 }); 477 478 $('.nav > li > a, li.mega > a').focus(function (event) { 479 // Remove all occurrences of "open" from other menu trees 480 var siblings = $(this).parents('.tb-megamenu-item').siblings(); 481 // var siblings = $(this).closest('.tb-megamenu-item.level-1').siblings(); 482 $.each(siblings, function (i, v) { 483 var cousins = $(v).find('.open'); 484 $.each(cousins, function (index, value) { 485 $(value).removeClass('open'); 486 ariaCheck($(this)); 487 }); 488 $(v).removeClass('open'); 489 ariaCheck(); 490 }); 491 // Open the submenu if the selected item has one 492 if ($(this).next(".tb-megamenu-submenu").length > 0) { 493 if (!$(this).parent().hasClass("open")) { 494 $(this).parent().addClass("open"); 495 } 496 } 497 // If the anchor's top-level parent is not open, open it 498 if (!$(this).closest('.tb-megamenu-item.dropdown').hasClass('open') && $(this).closest('.tb-megamenu-item.dropdown').find('.tb-megamenu-submenu').length > 0) { 499 $(this).closest('.tb-megamenu-item.dropdown').addClass('open'); 500 ariaCheck(); 501 } 502 // If anchor's parent submenus are not open, open them 503 var parents = $(this).parents('.tb-megamenu-item.dropdown-submenu'); 504 $.each(parents, function (i, v) { 505 if (!$(v).hasClass('open')) { 506 $(v).addClass('open'); 507 ariaCheck(); 508 } 509 }); 510 }); 511 } 512 513 // Define actions for touch devices. 514 var createTouchMenu = function (items) { 515 items.children("a, span").each(function () { 516 var $item = $(this); 517 var tbitem = $(this).parent(); 518 519 $item.click(function (event) { 520 // If the menu link has already been clicked once... 521 if ($item.hasClass("tb-megamenu-clicked")) { 522 var $uri = $item.attr("href"); 523 524 // If the menu link has a URI, go to the link. 525 // <nolink> menu items will not have a URI. 526 if ($uri) { 527 window.location.href = $uri; 528 } 529 else { 530 $item.removeClass("tb-megamenu-clicked"); 531 hideMenu(tbitem, mm_timeout); 532 } 533 } 534 else { 535 event.preventDefault(); 536 537 // Hide any already open menus. 538 nav_close_megamenu(); 539 $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked"); 540 541 // Open the submenu. 542 $item.addClass("tb-megamenu-clicked"); 543 showMenu(tbitem, mm_timeout); 544 } 545 }); 546 }); 547 548 // Anytime there's a click outside the menu, close the menu. 549 $(document).on('click', function (event) { 550 if ($(event.target).closest('.tb-megamenu-nav').length === 0) { 551 nav_close_megamenu(); 552 $(".tb-megamenu").find(".tb-megamenu-clicked").removeClass("tb-megamenu-clicked"); 553 }; 554 }) 555 }; 556 557 if (isTouch) { 558 createTouchMenu($(".tb-megamenu ul.nav li.mega", context).has(".dropdown-menu")); 559 }; 560 561 $(window).on('load resize', function () { 562 var windowWidth = window.innerWidth ? window.innerWidth : $(window).width(); 563 if (windowWidth != Drupal.TBMegaMenu.oldWindowWidth) { 564 Drupal.TBMegaMenu.oldWindowWidth = windowWidth; 565 Drupal.TBMegaMenu.menuResponsive(); 566 567 if (windowWidth >= Drupal.TBMegaMenu.supportedScreens[0]) { 568 navParent.addEventListener('keydown',keydownEvent); 569 } 570 else { 571 navParent.removeEventListener('keydown',keydownEvent); 572 } 573 } 574 }); 575 576 }); 577 578 } 579 } 580})(jQuery, Drupal, drupalSettings, once);
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.