1(function ($, Drupal, once) { 2 'use strict'; 3 4 Drupal.behaviors.DX8Menus = { 5 // Attachment trigger. 6 attach: function (context, settings) { 7 // Cache 8 var menuItemLinks = []; 9 10 // Counters 11 var menuItemLinkCounter; 12 13 // Libs 14 var cmm = new Drupal.CohesionResponsiveBreakpoints(); 15 16 // States 17 var over = false; 18 var $lastAnimatedSubmenu = $(); 19 var hasEntered = false; 20 21 // Constants 22 var onceMenuItem = 'js-coh-menu-item-init'; 23 var onceMenuItemLink = 'js-coh-menu-item-link-init'; 24 var nameSpace = '.coh-menu-item-link'; 25 26 // Constants - classes 27 var cls = { 28 isCollapsed: 'is-collapsed', 29 isExpanded: 'is-expanded', 30 both: 'is-collapsed is-expanded', 31 hasChildren: 'has-children', 32 menuListContainer: 'coh-menu-list-container', 33 menuListItem: 'coh-menu-list-item', 34 menuListLink: 'js-coh-menu-item-link' 35 }; 36 37 // Constants - aria attributes 38 var aria = { 39 expanded: 'aria-expanded', 40 popup: 'aria-haspopup' 41 }; 42 43 // Events 44 var onEnter = ['pointerenter']; 45 var onLeave = ['mouseleave']; 46 var onClick = ['click']; 47 var onFocus = ['focus']; 48 var onFocusOut = ['focusout']; 49 var onKeyDown = ['keydown']; 50 51 /** 52 * Call back for initializing the Drupal menu item link functionality 53 * @param {Object} settings 54 * @returns {undefined} 55 */ 56 function initDrupalMenuItemLink(settings) { 57 var bOnClick = true; 58 59 settings = settings.cohesion; 60 var $a = settings.settings.$a; // Can be either `<a>`, `<button>` or <span> 61 var $li = settings.settings.$li; 62 var isParent = settings.settings.isParent; 63 var thisNameSpace = nameSpace + $a[0].nodeName; 64 65 // Ensure all previous bound events are removed (by nameSpace) 66 $li.off(thisNameSpace); 67 $a.off(thisNameSpace); 68 69 // Grab the current breakpoint setting 70 var setting = settings.settings.breakpoints[settings.key]; 71 72 // If animation target exists, store for later use 73 if (typeof setting.animationTarget !== 'undefined') { 74 $a.data('currentAnimationTarget', $(setting.animationTarget, $li)); 75 } 76 77 // If animation target is stored, remove inline display property 78 if ($a.data('currentAnimationTarget')) { 79 $($a.data('currentAnimationTarget')).css('display',''); 80 } 81 82 var toggleSiblings = setting.link_interaction === 'toggle-on-click-hide-siblings' || setting.button_interaction === 'toggle-on-click-hide-siblings'; 83 84 if ((setting.link_interaction === 'toggle-on-hover' || toggleSiblings || setting.link_interaction === 'toggle-on-click' || setting.button_interaction === 'toggle-on-click' || settings.button_interaction === 'toggle-parent-on-click') && $li.hasClass(cls.hasChildren) ) { 85 86 $a.attr(aria.popup, true); 87 88 } else { 89 $a.removeAttr(aria.popup); 90 $a.removeAttr(aria.expanded); 91 } 92 93 // Hover events 94 if (setting.link_interaction === 'toggle-on-hover') { 95 over = false; 96 97 if ($li.hasClass(cls.hasChildren)) { 98 $li.on(onLeave.join(thisNameSpace + ' ') + thisNameSpace, function (event) { 99 // hotfix/COH-4793 - prevent leave event being able to fire before enter event to stop menu show/hide getting inverted on page load. 100 if (!hasEntered) { 101 return; 102 } 103 window.setTimeout(function () { 104 over = false;
105 }, 200); 106 107 // Disable click events for links and buttons. 108 event.preventDefault(); 109 110 // If the menu is closed when leaving ensure it doesnt get reopened 111 if($li.hasClass(cls.isCollapsed)) { 112 return; 113 } 114 115 toggleSubMenu($li, $a, setting, true, event); 116 }); 117 118 $li.on(onEnter.join(thisNameSpace + ' ') + thisNameSpace, function (event) { 119 // Once the menu is open, make the link clickable - delay so it doesn't happen immediately on touch device. 120 window.setTimeout(function () { 121 over = true; 122 }, 200); 123 124 // Disable click events for links and buttons. 125 event.preventDefault(); 126 // Ensure that the mouseEnter event always fires after a mouseLeave event when mousing between menu elements. 127 setTimeout(function () { 128 toggleSubMenu($li, $a, setting); 129 hasEntered = true; 130 }, 1); 131 132 }); 133 134 $a.on(onClick.join(thisNameSpace + ' ') + thisNameSpace, function (event) { 135 if (!over) { 136 event.preventDefault(); 137 } 138 }); 139 140 } 141 bOnClick = false; 142 } 143 144 // Click events 145 if (bOnClick) { 146 $a.on(onClick.join(thisNameSpace + ' ') + thisNameSpace, function (e) { 147 148 // Click through to link || return to parent 149 if (setting.link_interaction === 'click-through-to-link') { 150 return; 151 } 152 153 // No interaction 154 if (setting.link_interaction === 'no-interaction') { 155 e.preventDefault(); 156 return; 157 } 158 159 // If has children - do something with them 160 if ($li.hasClass(cls.hasChildren)) { 161 162 e.preventDefault(); 163 164 // On click, toggle sub-menu visibility and hide sibling items. If no sub-menu, go to link 165 toggleSubMenu($li, $(this), setting, toggleSiblings); 166 167 if(setting.link_interaction === 'toggle-parent-on-click') { 168 return; 169 } 170 } 171 }); 172 } 173 174 // Focus events 175 $a.on(onFocus.join(thisNameSpace + ' ') + thisNameSpace, function (e) { 176 over = true; 177 178 // Ensure the pointer is always up to date on focus - probably a little overkill, but safer than sorry 179 for (var i = 0; i < menuItemLinks.length; i++) { 180 if ($(this).is(menuItemLinks[i]['$a'])) { 181 menuItemLinkCounter = i; 182 break; 183 } 184 } 185 186 $a.on(onKeyDown.join(thisNameSpace + ' ') + thisNameSpace, function (e) { 187 188 // Handle accessibility keys 189 switch (e.key) { 190 191 case ' ': 192 case 'Spacebar': // IE 193 e.preventDefault(); 194 e.stopPropagation(); 195 196 // If menu list item does not have child menu, space follows link 197 if (!$li.hasClass(cls.hasChildren)) { 198 window.location = $a.attr('href'); 199 } 200 201 // If menu list item has child menu, space toggles child menu 202 if ($li.hasClass(cls.hasChildren)) { 203 toggleSubMenu($li, $a, setting, toggleSiblings); 204 } 205 206 break; 207 208 case 'ArrowDown': 209 case 'ArrowRight': 210 case 'Down': // IE 211 case 'Right': // IE 212 e.preventDefault(); 213 e.stopPropagation(); 214 215 // Handle parent menu items (when collapsed you should be able to navigate left / right) 216 if (isParent && (e.key === 'ArrowRight' || e.key === 'Right') && $li.next('.' + cls.menuListItem) && !$li.hasClass(cls.isExpanded)) { 217 $('.' + cls.menuListLink, $li.next('.' + cls.menuListItem)).eq(0).focus(); 218 break; 219 } 220 221 // If expanded then move into child items 222 if ($li.hasClass(cls.isExpanded) && $li.hasClass(cls.hasChildren)) { 223 focusNextMenuItem($li); 224 } 225 226 // If !expanded then do it 227 if (!$li.hasClass(cls.isExpanded) && $li.hasClass(cls.hasChildren)) { 228 toggleSubMenu($li, $a, setting, toggleSiblings); 229 } 230 231 // If !children move onto the next sibling 232 if (!$li.hasClass(cls.hasChildren)) { 233 focusNextMenuItem($li); 234 } 235 236 break; 237 238 case 'Escape': 239 case 'Esc': // IE 240 e.preventDefault(); 241 e.stopPropagation(); 242 243 if ($li.parent().closest('.' + cls.menuListItem)) { 244 245 toggleSubMenu($li.parent().closest('.' + cls.menuListItem), $li.parent().closest('.' + cls.menuListItem).children('.' + cls.menuListLink), setting, toggleSiblings); 246 247 $li.parent().closest('.' + cls.menuListItem).children('.' + cls.menuListLink).focus(); 248 } 249 250 break; 251 252 case 'ArrowUp': 253 case 'ArrowLeft': 254 case 'Up': // IE 255 case 'Left': // IE 256 e.preventDefault(); 257 e.stopPropagation(); 258 259 // Handle parent menu items (when collapsed you should be able to navigate left / right) 260 if (isParent && (e.key === 'ArrowLeft' || e.key === 'Left') && $li.prev('.' + cls.menuListItem) && !$li.hasClass(cls.isExpanded)) { 261 $('.' + cls.menuListLink, $li.prev('.' + cls.menuListItem)).eq(0).focus(); 262 break; 263 } 264 265 // If !children || isCollapsed then move back to the previous link 266 if (!$li.hasClass(cls.hasChildren) || $li.hasClass(cls.isCollapsed)) { 267 focusPreviousMenuItem(); 268 } 269 270 // If !collapsed then do it, toggle siblings and move back to the previous link 271 if (!$li.hasClass(cls.isCollapsed) && $li.hasClass(cls.hasChildren)) { 272 toggleSubMenu($li, $a, setting, toggleSiblings); 273 } 274 275 break; 276 277 case 'Enter': 278 e.preventDefault(); 279 e.stopPropagation(); 280 // Enter should always follow the link, expanding the menu can be handled with down or space. 281 window.location = $a.attr('href'); 282 283 break; 284 285 default: 286 return; 287 } 288 289 290 }); 291 }); 292 293 $a.on(onFocusOut.join(thisNameSpace + ' ') + thisNameSpace, function (e) { 294 // Remove the previous keyboard events 295 $a.off(onKeyDown.join(thisNameSpace + ' ') + thisNameSpace); 296 over = false;
297 }); 298 } 299 300 /** 301 * 302 * @param {type} settings 303 * @returns {undefined} 304 */ 305 function initDrupalMenuItem(settings) { 306 settings = settings.cohesion; 307 var $a = settings.settings.$a; // Can be either `<a>`, `<button>` or <span> 308 var $li = settings.settings.$li; 309 var bInteracted = false; 310 var $interactees = $a.add($a.siblings('a, button, span')); // used to ensure sibling A and BUTTON elements get same aria values. 311 312 // Grab the current breakpoint setting 313 var setting = settings.settings.breakpoints[settings.key]; 314 315 if (typeof $li.data('interacted') !== 'undefined' && $li.data('interacted') === true) { 316 bInteracted = true; 317 } 318 319 if (!bInteracted) { 320 $li.toggleClass(cls.isCollapsed, setting === 'hidden' || (setting === 'trail' && !$li.hasClass('in-active-trail'))); 321 $li.toggleClass(cls.isExpanded, setting === 'visible' || (setting === 'trail' && $li.hasClass('in-active-trail'))); 322 if ($li.hasClass(cls.hasChildren)) { 323 324 $interactees.each(function () { 325 var $interactee = $(this); 326 327 if ($interactee.attr(aria.popup) === "true") { 328 if ($li.hasClass(cls.isCollapsed)) { 329 $interactee.attr(aria.expanded, false); 330 } 331 if ($li.hasClass(cls.isExpanded)) { 332 $interactee.attr(aria.expanded, true); 333 } 334 } 335 }); 336 } 337 } 338 } 339 340 function toggleSiblingsFn($li, $a, setting) { 341 var $siblings = $li.siblings('li.has-children'); 342 $siblings.children('a, button, span').each(function() { 343 var $this = $(this); 344 if($this.attr(aria.expanded) === "true") { 345 toggleSubMenu($this.parent('li'), $this, setting, false); 346 } 347 }); 348 } 349 350 function toggleSubMenu($li, $a, setting, toggleSiblings) { 351 var $interactees = $a.add($a.siblings('a, button, span')); 352 var $submenu; 353 354 if (setting.animationTarget && setting.animationType) { 355 // hardcoded general sibling selector for animation target, as sub menu will always be a sibling of parent menu's link 356 $submenu = $('~' + setting.animationTarget, $a); 357 358 if(setting.button_interaction === 'toggle-parent-on-click') { 359 $submenu = $('> ' + setting.animationTarget, $li); 360 $interactees = $interactees.add($('> a, > button, > span', $li)); 361 } 362 363 var animationOriginArray; 364 365 if (setting.animationOrigin) { 366 // convert animation origin string to array 367 animationOriginArray = setting.animationOrigin.split(','); 368 } 369 370 if ((setting.link_interaction !== 'toggle-on-click-hide-siblings' || setting.button_interaction !== 'toggle-on-click-hide-siblings') && $lastAnimatedSubmenu.length && !$lastAnimatedSubmenu.is($submenu)) { 371 $lastAnimatedSubmenu.stop(true, true); 372 } 373 374 $submenu.stop(true, true).toggle({ 375 effect: setting.animationType, 376 direction: setting.animationDirection, 377 distance: setting.animationDistance, 378 percent: setting.animationScale, 379 origin: animationOriginArray, 380 size: setting.animationFoldHeight, 381 horizFirst: setting.animationHorizontalFirst, 382 times: setting.animationIterations, 383 easing: setting.animationEasing, 384 duration: setting.animationDuration 385 }); 386 $lastAnimatedSubmenu = $submenu; 387 } 388 $li.toggleClass(cls.both); 389 390 $interactees.each(function () { 391 var $interactee = $(this); 392 if ($interactee.attr(aria.popup) === "true") { 393 if ($li.hasClass(cls.isCollapsed)) { 394 $interactee.attr(aria.expanded, false); 395 } 396 if ($li.hasClass(cls.isExpanded)) { 397 $interactee.attr(aria.expanded, true); 398 } 399 } 400 }); 401 402 $li.data('interacted', true); 403 404 // Call the call back if defined 405 if (toggleSiblings) { 406 toggleSiblingsFn($li, $a, setting); 407 } 408 } 409 410 function focusPreviousMenuItem() { 411 if (menuItemLinkCounter > 0) { 412 menuItemLinkCounter--; 413 menuItemLinks[menuItemLinkCounter]['$a'].focus(); 414 } 415 } 416 417 function focusNextMenuItem() { 418 if (menuItemLinkCounter + 1 < menuItemLinks.length) { 419 menuItemLinkCounter++; 420 menuItemLinks[menuItemLinkCounter]['$a'].focus(); 421 } 422 } 423 424 function focusMenuItem() { 425 menuItemLinks[menuItemLinkCounter]['$a'].focus(); 426 } 427 428 // Each menu item link 429 var menuItems = $(once(onceMenuItemLink, '.js-coh-menu-item-link, .js-coh-menu-item-button')); 430 menuItems.each(function (i, e) { 431 var $this = $(this), 432 $li = $(this).closest('.coh-menu-list-item'), 433 responsiveSettings = $this.data('cohSettings'), 434 key, 435 settings = { 436 $a: $this, 437 $li: $li, 438 isParent: !$li.parent().closest('.' + cls.menuListItem).length, 439 breakpoints: {} 440 }; 441 442 // Keep a list of the menu links 443 menuItemLinks.push({ 444 $a: $this, 445 $li: $li, 446 tabindex: $this.attr('tabindex') || i 447 }); 448 449 // Ensure they are in the correct order 450 if (i + 1 === menuItems.length) { 451 452 menuItemLinks.sort(function (a, b) { 453 return a.tabindex - b.tabindex; 454 }); 455 } 456 457 for (var i = 0; i < cmm.breakpoints.length; i++) { 458 459 key = cmm.breakpoints[i].key; 460 461 // Populate all breakpoints regardless of whether the settings are set or not to simulate inheritance 462 settings.breakpoints[key] = {}; 463 if (responsiveSettings && typeof responsiveSettings[key] !== 'undefined') { 464 465 settings.breakpoints[key] = responsiveSettings[key]; 466 467 var previous = responsiveSettings[key]; 468 469 } else { 470 471 if (typeof cmm.breakpoints[i - 1] !== 'undefined' && typeof previous !== 'undefined') { 472 settings.breakpoints[key] = previous; 473 } 474 } 475 } 476 477 cmm.addListeners(settings, initDrupalMenuItemLink); 478 }); 479 480 // Each menu item 481 $(once(onceMenuItem, '.js-coh-menu-item')).each(function () { 482 483 var $this = $(this), 484 responsiveSettings = $this.data('cohSettings'), 485 key, 486 settings = { 487 $li: $this, 488 $a: $('> a, > button, > span', $this), 489 breakpoints: {} 490 }; 491 492 // No children or settings then just return 493 if ($this.hasClass(cls.hasChildren)) { 494 for (var i = 0; i < cmm.breakpoints.length; i++) { 495 496 key = cmm.breakpoints[i].key; 497 498 // Populate all breakpoints regardless of whether the settings are set or not to simulate inheritance 499 settings.breakpoints[key] = {}; 500 if (typeof responsiveSettings[key] !== 'undefined') { 501 502 settings.breakpoints[key] = responsiveSettings[key]; 503 504 var previous = responsiveSettings[key]; 505 506 } else { 507 508 if (typeof cmm.breakpoints[i - 1] !== 'undefined' && typeof previous !== 'undefined') { 509 settings.breakpoints[key] = previous; 510 } 511 } 512 } 513 514 cmm.addListeners(settings, initDrupalMenuItem); 515 } 516 }); 517 } 518 }; 519 520})(jQuery, Drupal, 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.