https://iabeurope.eu/wp-content/plugins/oxyextras/components/assets/accessible-megamenu.js?ver=1.0.0
1/* 2Copyright © 2013 Adobe Systems Incorporated. 3 4Licensed under the Apache License, Version 2.0 (the âLicenseâ); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an âAS ISâ BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17/** 18 * See <a href="http://jquery.com">http://jquery.com</a>. 19 * @name jquery 20 * @class 21 * See the jQuery Library (<a href="http://jquery.com">http://jquery.com</a>) for full details. This just 22 * documents the function and classes that are added to jQuery by this plug-in. 23 */ 24 25/** 26 * See <a href="http://jquery.com">http://jquery.com</a> 27 * @name fn 28 * @class 29 * See the jQuery Library (<a href="http://jquery.com">http://jquery.com</a>) for full details. This just 30 * documents the function and classes that are added to jQuery by this plug-in. 31 * @memberOf jquery 32 */ 33 34/** 35 * @fileOverview accessibleMegaMenu plugin 36 * 37 *<p>Licensed under the Apache License, Version 2.0 (the âLicenseâ) 38 *<br />Copyright © 2013 Adobe Systems Incorporated. 39 *<br />Project page <a href="https://github.com/adobe-accessibility/Accessible-Mega-Menu">https://github.com/adobe-accessibility/Accessible-Mega-Menu</a> 40 * @version 0.1 41 * @author Michael Jordan 42 * @requires jquery 43 */ 44 45/*jslint browser: true, devel: true, plusplus: true, nomen: true */ 46/*global jQuery, window, document */ 47(function ($, window, document) { 48 "use strict"; 49 var pluginName = "accessibleMegaMenu", 50 defaults = { 51 uuidPrefix: "accessible-megamenu", // unique ID's are required to indicate aria-owns, aria-controls and aria-labelledby 52 menuClass: "accessible-megamenu", // default css class used to define the megamenu styling 53 topNavItemClass: "accessible-megamenu-top-nav-item", // default css class for a top-level navigation item in the megamenu 54 panelClass: "accessible-megamenu-panel", // default css class for a megamenu panel 55 panelGroupClass: "accessible-megamenu-panel-group", // default css class for a group of items within a megamenu panel 56 hoverClass: "hover", // default css class for the hover state 57 focusClass: "focus", // default css class for the focus state 58 openClass: "open", // default css class for the open state, 59 toggleButtonClass: "accessible-megamenu-toggle", // default css class responsive toggle button 60 openDelay: 0, // default open delay when opening menu via mouseover 61 closeDelay: 250, // default open delay when opening menu via mouseover 62 openOnMouseover: false // default setting for whether menu should open on mouseover 63 }, 64 Keyboard = { 65 BACKSPACE: 8, 66 COMMA: 188, 67 DELETE: 46, 68 DOWN: 40, 69 END: 35, 70 ENTER: 13, 71 ESCAPE: 27, 72 HOME: 36, 73 LEFT: 37, 74 PAGE_DOWN: 34, 75 PAGE_UP: 33, 76 PERIOD: 190, 77 RIGHT: 39, 78 SPACE: 32, 79 TAB: 9, 80 UP: 38, 81 keyMap: { 82 48: "0", 83 49: "1", 84 50: "2", 85 51: "3", 86 52: "4", 87 53: "5", 88 54: "6", 89 55: "7", 90 56: "8", 91 57: "9", 92 59: ";", 93 65: "a", 94 66: "b", 95 67: "c", 96 68: "d", 97 69: "e", 98 70: "f", 99 71: "g", 100 72: "h", 101 73: "i", 102 74: "j", 103 75: "k", 104 76: "l", 105 77: "m", 106 78: "n", 107 79: "o", 108 80: "p", 109 81: "q", 110 82: "r", 111 83: "s", 112 84: "t", 113 85: "u", 114 86: "v", 115 87: "w", 116 88: "x", 117 89: "y", 118 90: "z", 119 96: "0", 120 97: "1", 121 98: "2", 122 99: "3", 123 100: "4", 124 101: "5", 125 102: "6", 126 103: "7", 127 104: "8", 128 105: "9", 129 190: "." 130 } 131 }, 132 clearTimeout = window.clearTimeout, 133 setTimeout = window.setTimeout, 134 isOpera = window.opera && window.opera.toString() === '[object Opera]'; 135 /**
136 * @desc Creates a new accessible mega menu instance. 137 * @param {jquery} element 138 * @param {object} [options] Mega Menu options 139 * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby 140 * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling 141 * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu 142 * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel 143 * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel 144 * @param {string} [options.hoverClass=hover] - CSS class for the hover state 145 * @param {string} [options.focusClass=focus] - CSS class for the focus state 146 * @param {string} [options.openClass=open] - CSS class for the open state 147 * @constructor 148 */ 149 function AccessibleMegaMenu(element, options) { 150 this.element = element; 151 152 // merge optional settings and defaults into settings 153 this.settings = $.extend({}, defaults, options); 154 155 this._defaults = defaults; 156 this._name = pluginName; 157 158 this.mouseTimeoutID = null; 159 this.focusTimeoutID = null; 160 this.mouseFocused = false; 161 this.justFocused = false; 162 163 this.init(); 164 } 165 166 AccessibleMegaMenu.prototype = (function () { 167 168 /* private attributes and methods ------------------------ */ 169 var uuid = 0, 170 keydownTimeoutDuration = 1000, 171 keydownSearchString = "", 172 isTouch = 'ontouchstart' in window || window.navigator.msMaxTouchPoints, 173 _getPlugin, 174 _addUniqueId, 175 _togglePanel, 176 _clickHandler, 177 _touchmoveHandler, 178 _clickOutsideHandler, 179 _DOMAttrModifiedHandler, 180 _focusInHandler, 181 _focusOutHandler, 182 _keyDownHandler, 183 _mouseDownHandler, 184 _mouseOverHandler, 185 _mouseOutHandler, 186 _clickToggleHandler, 187 _toggleExpandedEventHandlers, 188 _addEventHandlers, 189 _removeEventHandlers; 190 191 /** 192 * @name jQuery.fn.accessibleMegaMenu~_getPlugin 193 * @desc Returns the parent accessibleMegaMenu instance for a given element 194 * @param {jQuery} element 195 * @memberof jQuery.fn.accessibleMegaMenu 196 * @inner 197 * @private 198 */ 199 _getPlugin = function (element) { 200 return $(element).closest(':data(plugin_' + pluginName + ')').data("plugin_" + pluginName); 201 }; 202 203 /** 204 * @name jQuery.fn.accessibleMegaMenu~_addUniqueId 205 * @desc Adds a unique id and element. 206 * The id string starts with the 207 * string defined in settings.uuidPrefix. 208 * @param {jQuery} element 209 * @memberof jQuery.fn.accessibleMegaMenu 210 * @inner 211 * @private 212 */ 213 _addUniqueId = function (element) { 214 element = $(element); 215 var settings = this.settings; 216 if (!element.attr("id")) { 217 element.attr("id", settings.uuidPrefix + "-" + new Date().getTime() + "-" + (++uuid)); 218 } 219 }; 220 221 /** 222 * @name jQuery.fn.accessibleMegaMenu~_togglePanel 223 * @desc Toggle the display of mega menu panels in response to an event. 224 * The optional boolean value 'hide' forces all panels to hide. 225 * @param {event} event 226 * @param {Boolean} [hide] Hide all mega menu panels when true 227 * @memberof jQuery.fn.accessibleMegaMenu 228 * @inner 229 * @private 230 */ 231 _togglePanel = function (event, hide) { 232 var target = $(event.target), 233 that = this, 234 settings = this.settings, 235 menu = this.menu, 236 topli = target.closest('.' + settings.topNavItemClass), 237 panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass), 238 newfocus; 239 240 _toggleExpandedEventHandlers.call(this, true); 241 242 // Hide all panels. 243 if (hide) { 244 // Get the first top level menu item. 245 topli = menu.find('.' + settings.topNavItemClass + ' .' + settings.openClass + ':first').closest('.' + settings.topNavItemClass); 246 // Validate event. 247 if (!(topli.is(event.relatedTarget) || topli.has(event.relatedTarget).length > 0)) { 248 if ((event.type === 'mouseout' || event.type === 'focusout') && topli.has(document.activeElement).length > 0) { 249 return; 250 } 251 252 // Close top level link. 253 topli.find('[aria-expanded]') 254 .attr('aria-expanded', 'false') 255 .removeClass(settings.openClass) 256 // Close panel. 257 topli.find('.' + settings.panelClass) 258 .removeClass(settings.openClass) 259 .attr('aria-hidden', 'true'); 260 261 if ((event.type === 'keydown' && event.keyCode === Keyboard.ESCAPE) || event.type === 'DOMAttrModified') { 262 newfocus = topli.find(':tabbable:first'); 263 setTimeout(function () { 264 menu.find('[aria-expanded].' + that.settings.panelClass).off('DOMAttrModified.accessible-megamenu'); 265 newfocus.focus(); 266 that.justFocused = false;
267 }, 99); 268 } 269 } else if (topli.length === 0) { 270 menu.find('[aria-expanded=true]') 271 .attr('aria-expanded', 'false') 272 .removeClass(settings.openClass) 273 .closest('.' + settings.panelClass) 274 .removeClass(settings.openClass) 275 .attr('aria-hidden', 'true'); 276 } 277 } 278 // Toggle panels. 279 else { 280 clearTimeout(that.focusTimeoutID); 281 // Close previously open top level link and its panel. 282 var openli = menu.find('[aria-expanded=true]').parent(); 283 if (!openli.is(topli)) { 284 openli.find('[aria-expanded]') 285 .attr('aria-expanded', 'false') 286 .removeClass(settings.openClass) 287 .siblings('.' + settings.panelClass) 288 .removeClass(settings.openClass) 289 .attr('aria-hidden', 'true'); 290 } 291 // Open current top level link and its panel. 292 topli.find('[aria-expanded]') 293 .attr('aria-expanded', 'true') 294 .addClass(settings.openClass) 295 .siblings('.' + settings.panelClass) 296 .addClass(settings.openClass) 297 .attr('aria-hidden', 'false'); 298 299 if (event.type === 'mouseover' && target.is(':tabbable') && topli.length === 1 && panel.length === 0 && menu.has(document.activeElement).length > 0) { 300 target.focus(); 301 that.justFocused = false; 302 } 303 304 _toggleExpandedEventHandlers.call(that); 305 } 306 }; 307 308 /** 309 * @name jQuery.fn.accessibleMegaMenu~_clickHandler 310 * @desc Handle click event on mega menu item 311 * @param {event} Event object 312 * @memberof jQuery.fn.accessibleMegaMenu 313 * @inner 314 * @private 315 */ 316 _clickHandler = function (event) { 317 var target = $(event.target).closest(':tabbable'), 318 topli = target.closest('.' + this.settings.topNavItemClass), 319 panel = target.closest('.' + this.settings.panelClass);
320 // With panel. 321 if (topli.length === 1 && panel.length === 0 && topli.find('.' + this.settings.panelClass).length === 1) { 322 // Handle click event. 323 if (!target.hasClass(this.settings.openClass)) { 324 event.preventDefault(); 325 event.stopPropagation(); 326 _togglePanel.call(this, event); 327 this.justFocused = false; 328 } 329 else { 330 // Handle focus event. 331 if (this.justFocused) { 332 event.preventDefault(); 333 event.stopPropagation(); 334 this.justFocused = false; 335 } 336 // Handle touch/click event. 337 else if (isTouch || !isTouch && !this.settings.openOnMouseover) { 338 event.preventDefault(); 339 event.stopPropagation(); 340 _togglePanel.call(this, event, target.hasClass(this.settings.openClass)); 341 } 342 } 343 }
344 // Without panel on enter event. 345 else if (topli.length === 1 && panel.length === 0 && event.type == "keydown") { 346 window.location.href = target.attr("href"); 347 } 348 }; 349 350 /** 351 * @name jQuery.fn.accessibleMegaMenu~_touchmoveHandler 352 * @desc Handle touch move event on menu 353 * @memberof jQuery.fn.accessibleMegaMenu 354 * @inner 355 * @private 356 */ 357 _touchmoveHandler = function () { 358 this.justMoved = true; 359 }; 360 361 /** 362 * @name jQuery.fn.accessibleMegaMenu~_clickOutsideHandler 363 * @desc Handle click event outside of a the megamenu 364 * @param {event} Event object 365 * @memberof jQuery.fn.accessibleMegaMenu 366 * @inner 367 * @private 368 */ 369 _clickOutsideHandler = function (event) { 370 if ($(event.target).closest(this.menu).length === 0) { 371 event.preventDefault(); 372 event.stopPropagation(); 373 _togglePanel.call(this, event, true); 374 } 375 }; 376 377 /** 378 * @name jQuery.fn.accessibleMegaMenu~_DOMAttrModifiedHandler 379 * @desc Handle DOMAttrModified event on panel to respond to Windows 8 Narrator ExpandCollapse pattern 380 * @param {event} Event object 381 * @memberof jQuery.fn.accessibleMegaMenu 382 * @inner 383 * @private 384 */ 385 _DOMAttrModifiedHandler = function (event) { 386 if (event.originalEvent.attrName === 'aria-expanded' 387 && event.originalEvent.newValue === 'false' 388 && $(event.target).hasClass(this.settings.openClass)) { 389 event.preventDefault(); 390 event.stopPropagation(); 391 _togglePanel.call(this, event, true); 392 } 393 }; 394 395 /** 396 * @name jQuery.fn.accessibleMegaMenu~_focusInHandler 397 * @desc Handle focusin event on mega menu item. 398 * @param {event} Event object 399 * @memberof jQuery.fn.accessibleMegaMenu 400 * @inner 401 * @private 402 */ 403 _focusInHandler = function (event) { 404 clearTimeout(this.focusTimeoutID); 405 var target = $(event.target), 406 panel = target.closest('.' + this.settings.panelClass); 407 target 408 .addClass(this.settings.focusClass); 409 this.justFocused = !this.mouseFocused || (!this.settings.openOnMouseover && this.mouseFocused); 410 this.mouseFocused = false; 411 if (this.justFocused && this.panels.not(panel).filter('.' + this.settings.openClass).length) { 412 _togglePanel.call(this, event); 413 } 414 }; 415 416 /** 417 * @name jQuery.fn.accessibleMegaMenu~_focusOutHandler 418 * @desc Handle focusout event on mega menu item. 419 * @param {event} Event object 420 * @memberof jQuery.fn.accessibleMegaMenu 421 * @inner 422 * @private 423 */ 424 _focusOutHandler = function (event) { 425 this.justFocused = false; 426 var that = this, 427 target = $(event.target), 428 topli = target.closest('.' + this.settings.topNavItemClass); 429 target 430 .removeClass(this.settings.focusClass); 431 432 if (typeof window.cvox === 'object' && typeof window.cvox.Api !== 'object') { 433 // If ChromeVox is running... 434 that.focusTimeoutID = setTimeout(function () { 435 window.cvox.Api.getCurrentNode(function (node) { 436 if (topli.has(node).length) { 437 // and the current node being voiced is in 438 // the mega menu, clearTimeout, 439 // so the panel stays open. 440 clearTimeout(that.focusTimeoutID); 441 } else { 442 that.focusTimeoutID = setTimeout(function (scope, event, hide) { 443 _togglePanel.call(scope, event, hide); 444 }, 275, that, event, true); 445 } 446 }); 447 }, 25); 448 } else { 449 that.focusTimeoutID = setTimeout(function () { 450 if (that.mouseFocused && event.relatedTarget === null) { 451 return; 452 } 453 _togglePanel.call(that, event, true); 454 }, 300); 455 } 456 }; 457 458 /** 459 * @name jQuery.fn.accessibleMegaMenu~_keyDownHandler
460 * @desc Handle keydown event on mega menu. 461 * @param {event} Event object 462 * @memberof jQuery.fn.accessibleMegaMenu 463 * @inner 464 * @private 465 */ 466 _keyDownHandler = function (event) { 467 var that = (this.constructor === AccessibleMegaMenu) ? this : _getPlugin(this), // determine the AccessibleMegaMenu plugin instance 468 settings = that.settings, 469 target = $($(this).is('.' + settings.hoverClass + ':tabbable') ? this : event.target), // if the element is hovered the target is this, otherwise, its the focused element 470 menu = that.menu, 471 topnavitems = that.topnavitems, 472 topli = target.closest('.' + settings.topNavItemClass), 473 tabbables = menu.find(':tabbable'), 474 panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass), 475 panelGroups = panel.find('.' + settings.panelGroupClass), 476 currentPanelGroup = target.closest('.' + settings.panelGroupClass), 477 next, 478 keycode = event.keyCode || event.which, 479 start, 480 i, 481 o, 482 label, 483 found = false, 484 newString = Keyboard.keyMap[event.keyCode] || '', 485 regex, 486 isTopNavItem = (topli.length === 1 && panel.length === 0); 487
488 if (target.is("input:focus, select:focus, textarea:focus, button:focus")) { 489 // if the event target is a form element we should handle keydown normally 490 return; 491 } 492 493 if (target.is('.' + settings.hoverClass + ':tabbable')) { 494 $('html').off('keydown.accessible-megamenu'); 495 } 496 497 switch (keycode) { 498 case Keyboard.ESCAPE: 499 this.mouseFocused = false; 500 _togglePanel.call(that, event, true); 501 break; 502 case Keyboard.DOWN: 503 event.preventDefault(); 504 this.mouseFocused = false; 505 if (isTopNavItem) { 506 _togglePanel.call(that, event); 507 found = (topli.find('.' + settings.panelClass + ' :tabbable:first').focus().length === 1); 508 } else { 509 found = (tabbables.filter(':gt(' + tabbables.index(target) + '):first').focus().length === 1); 510 } 511 512 if (!found && isOpera && (event.ctrlKey || event.metaKey)) { 513 tabbables = $(':tabbable'); 514 i = tabbables.index(target); 515 found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 516 } 517 break; 518 case Keyboard.UP: 519 event.preventDefault(); 520 this.mouseFocused = false; 521 if (isTopNavItem && target.hasClass(settings.openClass)) { 522 _togglePanel.call(that, event, true); 523 next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last'); 524 if (next.children('.' + settings.panelClass).length) { 525 found = (next.find('[aria-expanded]') 526 .attr('aria-expanded', 'true') 527 .addClass(settings.openClass) 528 .filter('.' + settings.panelClass) 529 .attr('aria-hidden', 'false') 530 .find(':tabbable:last') 531 .focus() === 1); 532 } 533 } else if (!isTopNavItem) { 534 found = (tabbables.filter(':lt(' + tabbables.index(target) + '):last').focus().length === 1); 535 } 536 537 if (!found && isOpera && (event.ctrlKey || event.metaKey)) { 538 tabbables = $(':tabbable'); 539 i = tabbables.index(target); 540 found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 541 } 542 break; 543 case Keyboard.RIGHT: 544 event.preventDefault(); 545 this.mouseFocused = false; 546 if (isTopNavItem) { 547 found = (topnavitems.filter(':gt(' + topnavitems.index(topli) + '):first').find(':tabbable:first').focus().length === 1); 548 } else { 549 if (panelGroups.length && currentPanelGroup.length) { 550 // if the current panel contains panel groups, and we are able to focus the first tabbable element of the next panel group 551 found = (panelGroups.filter(':gt(' + panelGroups.index(currentPanelGroup) + '):first').find(':tabbable:first').focus().length === 1); 552 } 553 554 if (!found) { 555 found = (topli.find(':tabbable:first').focus().length === 1); 556 } 557 } 558 break; 559 case Keyboard.LEFT: 560 event.preventDefault(); 561 this.mouseFocused = false; 562 if (isTopNavItem) { 563 found = (topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last').find(':tabbable:first').focus().length === 1); 564 } else { 565 if (panelGroups.length && currentPanelGroup.length) { 566 // if the current panel contains panel groups, and we are able to focus the first tabbable element of the previous panel group 567 found = (panelGroups.filter(':lt(' + panelGroups.index(currentPanelGroup) + '):last').find(':tabbable:first').focus().length === 1); 568 } 569 570 if (!found) { 571 found = (topli.find(':tabbable:first').focus().length === 1); 572 } 573 } 574 break; 575 case Keyboard.TAB: 576 this.mouseFocused = false;
577 i = tabbables.index(target); 578 if (event.shiftKey && isTopNavItem && target.hasClass(settings.openClass)) { 579 _togglePanel.call(that, event, true); 580 next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last'); 581 if (next.children('.' + settings.panelClass).length) { 582 found = next.children() 583 .attr('aria-expanded', 'true') 584 .addClass(settings.openClass) 585 .filter('.' + settings.panelClass) 586 .attr('aria-hidden', 'false') 587 .find(':tabbable:last') 588 .focus(); 589 } 590 } else if (event.shiftKey && i > 0) { 591 found = (tabbables.filter(':lt(' + i + '):last').focus().length === 1); 592 } else if (!event.shiftKey && i < tabbables.length - 1) { 593 found = (tabbables.filter(':gt(' + i + '):first').focus().length === 1); 594 } else if (isOpera) { 595 tabbables = $(':tabbable'); 596 i = tabbables.index(target); 597 if (event.shiftKey) { 598 found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):last').focus().length === 1); 599 } else { 600 found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 601 } 602 } 603 604 if (found) { 605 event.preventDefault(); 606 } 607 break; 608 case Keyboard.SPACE: 609 case Keyboard.ENTER: 610 // Top level links. 611 if (isTopNavItem) { 612 event.preventDefault(); 613 // Handle enter event on open top level link as escape event. 614 if (target.hasClass("open")) { 615 this.mouseFocused = false; 616 _togglePanel.call(that, event, true); 617 } 618 // Handle enter event on top level link as a click. 619 else { 620 _clickHandler.call(that, event); 621 } 622 } 623 // Sub level links. 624 else { 625 return true; 626 } 627 break; 628 default: 629 // alphanumeric filter 630 clearTimeout(this.keydownTimeoutID); 631 632 keydownSearchString += newString !== keydownSearchString ? newString : ''; 633 634 if (keydownSearchString.length === 0) { 635 return; 636 } 637 638 this.keydownTimeoutID = setTimeout(function () { 639 keydownSearchString = ''; 640 }, keydownTimeoutDuration); 641 642 if (isTopNavItem && !target.hasClass(settings.openClass)) { 643 tabbables = tabbables.filter(':not(.' + settings.panelClass + ' :tabbable)'); 644 } else { 645 tabbables = topli.find(':tabbable'); 646 } 647 648 if (event.shiftKey) { 649 tabbables = $(tabbables.get() 650 .reverse()); 651 } 652 653 for (i = 0; i < tabbables.length; i++) { 654 o = tabbables.eq(i); 655 if (o.is(target)) { 656 start = (keydownSearchString.length === 1) ? i + 1 : i; 657 break; 658 } 659 } 660 661 regex = new RegExp('^' + keydownSearchString.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'), 'i'); 662 663 for (i = start; i < tabbables.length; i++) { 664 o = tabbables.eq(i); 665 label = $.trim(o.text()); 666 if (regex.test(label)) { 667 found = true; 668 o.focus(); 669 break; 670 } 671 } 672 if (!found) { 673 for (i = 0; i < start; i++) { 674 o = tabbables.eq(i); 675 label = $.trim(o.text()); 676 if (regex.test(label)) { 677 o.focus(); 678 break; 679 } 680 } 681 } 682 break; 683 } 684 that.justFocused = false;
685 }; 686 687 /** 688 * @name jQuery.fn.accessibleMegaMenu~_mouseDownHandler 689 * @desc Handle mousedown event on mega menu. 690 * @param {event} Event object 691 * @memberof accessibleMegaMenu 692 * @inner 693 * @private 694 */ 695 _mouseDownHandler = function (event) { 696 if ($(event.target).closest(this.settings.panelClass) || $(event.target).closest(":focusable").length) { 697 this.mouseFocused = true; 698 if ($(event.target).closest(this.settings.menuClass)) { 699 $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target)); 700 } 701 } 702 clearTimeout(this.mouseTimeoutID); 703 this.mouseTimeoutID = setTimeout(function () { 704 clearTimeout(this.focusTimeoutID); 705 }, 1); 706 }; 707 708 /** 709 * @name jQuery.fn.accessibleMegaMenu~_mouseOverHandler 710 * @desc Handle mouseover event on mega menu. 711 * @param {event} Event object 712 * @memberof jQuery.fn.accessibleMegaMenu 713 * @inner 714 * @private 715 */ 716 _mouseOverHandler = function (event) { 717 clearTimeout(this.mouseTimeoutID); 718 var that = this; 719 720 if (!that.settings.openOnMouseover) { 721 return; 722 } 723 724 this.mouseTimeoutID = setTimeout(function () { 725 $(event.target).addClass(that.settings.hoverClass); 726 _togglePanel.call(that, event); 727 if ($(event.target).closest(that.settings.menuClass)) { 728 $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target)); 729 } 730 }, this.settings.openDelay); 731 }; 732 733 /** 734 * @name jQuery.fn.accessibleMegaMenu~_mouseOutHandler 735 * @desc Handle mouseout event on mega menu. 736 * @param {event} Event object 737 * @memberof jQuery.fn.accessibleMegaMenu 738 * @inner 739 * @private 740 */ 741 _mouseOutHandler = function (event) { 742 clearTimeout(this.mouseTimeoutID); 743 var that = this; 744 745 if (!that.settings.openOnMouseover) { 746 return; 747 } 748 749 $(event.target) 750 .removeClass(that.settings.hoverClass); 751 752 that.mouseTimeoutID = setTimeout(function () { 753 _togglePanel.call(that, event, true); 754 }, this.settings.closeDelay); 755 if ($(event.target).is(':tabbable')) { 756 $('html').off('keydown.accessible-megamenu'); 757 } 758 }; 759 760 /** 761 * @name jQuery.fn.accessibleMegaMenu~_clickToggleHandler 762 * @desc Handle click event on menu toggle button. 763 * @memberof jQuery.fn.accessibleMegaMenu 764 * @inner 765 * @private 766 */ 767 _clickToggleHandler = function () { 768 var isExpanded = this.toggleButton.attr('aria-expanded') === 'true'; 769 this.toggleButton.attr({'aria-expanded': !isExpanded}); 770 }; 771 772 _toggleExpandedEventHandlers = function (hide) { 773 var menu = this.menu; 774 if (hide) { 775 $('html').off('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu, pointerup.outside-accessible-megamenu'); 776 777 menu.find('[aria-expanded].' + this.settings.panelClass).off('DOMAttrModified.accessible-megamenu'); 778 } else { 779 $('html').on('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu, pointerup.outside-accessible-megamenu', $.proxy(_clickOutsideHandler, this)); 780 781 /* Narrator in Windows 8 automatically toggles the aria-expanded property on double tap or click. 782 To respond to the change to collapse the panel, we must add a listener for a DOMAttrModified event. */ 783 menu.find('[aria-expanded=true].' + this.settings.panelClass).on('DOMAttrModified.accessible-megamenu', $.proxy(_DOMAttrModifiedHandler, this)); 784 } 785 }; 786 787 _addEventHandlers = function() { 788 var menu = this.menu, 789 toggleButton = this.toggleButton; 790 menu.on("focusin.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusInHandler, this)) 791 .on("focusout.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusOutHandler, this))
792 .on("keydown.accessible-megamenu", $.proxy(_keyDownHandler, this)) 793 .on("mouseover.accessible-megamenu", $.proxy(_mouseOverHandler, this)) 794 .on("mouseout.accessible-megamenu", $.proxy(_mouseOutHandler, this)) 795 .on("mousedown.accessible-megamenu", $.proxy(_mouseDownHandler, this)) 796 .on("click.accessible-megamenu", $.proxy(_clickHandler, this)); 797 798 toggleButton.on('click.accessible-megamenu', $.proxy(_clickToggleHandler, this)); 799 800 if (isTouch) { 801 menu.on("touchmove.accessible-megamenu", $.proxy(_touchmoveHandler, this)); 802 } 803 804 if ($(document.activeElement).closest(menu).length) { 805 $(document.activeElement).trigger("focusin.accessible-megamenu"); 806 } 807 }; 808 809 _removeEventHandlers = function () { 810 var menu = this.menu, 811 toggleButton = this.toggleButton; 812 menu.off('.accessible-megamenu'); 813 if (menu.find('[aria-expanded=true].' + this.settings.panelClass).length) { 814 _toggleExpandedEventHandlers.call(this, true); 815 } 816 817 toggleButton.off('.accessible-megamenu'); 818 }; 819 820 /* public attributes and methods ------------------------- */ 821 return { 822 constructor: AccessibleMegaMenu, 823 824 /** 825 * @lends jQuery.fn.accessibleMegaMenu 826 * @desc Initializes an instance of the accessibleMegaMenu plugins 827 * @memberof jQuery.fn.accessibleMegaMenu 828 * @instance 829 */ 830 init: function () { 831 var settings = this.settings, 832 nav = $(this.element), 833 menu = nav.children('ol,ul').first(), 834 topnavitems = menu.children(), 835 toggleButton = nav.children('button').first(); 836 this.start(settings, nav, menu, topnavitems, toggleButton); 837 }, 838 839 start: function(settings, nav, menu, topnavitems, toggleButton) { 840 var that = this; 841 this.settings = settings; 842 this.menu = menu; 843 this.topnavitems = topnavitems; 844 this.toggleButton = toggleButton; 845 846 nav.attr("role", "navigation"); 847 _addUniqueId.call(that, menu); 848 menu.addClass(settings.menuClass); 849 menu.addClass(['js', settings.menuClass].join('-')); 850 topnavitems.each(function (i, topnavitem) { 851 var topnavitemlink, topnavitempanel; 852 topnavitem = $(topnavitem); 853 topnavitem.addClass(settings.topNavItemClass); 854 topnavitemlink = topnavitem.find("a").first(); 855 topnavitempanel = topnavitem.find('.' + settings.panelClass); 856 _addUniqueId.call(that, topnavitemlink); 857 // When sub nav exists. 858 if (topnavitempanel.length) { 859 _addUniqueId.call(that, topnavitempanel); 860 // Add attributes to top level link. 861 topnavitemlink.attr({ 862 "role": "button", 863 "aria-controls": topnavitempanel.attr("id"), 864 "aria-expanded": false, 865 "tabindex": 0 866 }); 867 // Add attributes to sub nav. 868 topnavitempanel.attr({ 869 "role": "region", 870 "aria-hidden": true 871 }) 872 .addClass(settings.panelClass) 873 .not("[aria-labelledby]") 874 .attr("aria-labelledby", topnavitemlink.attr("id")); 875 } 876 }); 877 878 this.panels = menu.find("." + settings.panelClass); 879 880 menu.find("hr").attr("role", "separator"); 881 882 toggleButton.addClass(settings.toggleButtonClass); 883 toggleButton.attr({'aria-expanded': false, 'aria-controls': menu.attr('id')}); 884 885 _addEventHandlers.call(this); 886 }, 887 888 /**
889 * @desc Removes maga menu javascript behavior 890 * @example $(selector).accessibleMegaMenu("destroy"); 891 * @return {object} 892 * @memberof jQuery.fn.accessibleMegaMenu 893 * @instance 894 */ 895 destroy: function () { 896 this.menu.removeClass(['js', this.settings.menuClass].join('-')); 897 _removeEventHandlers.call(this, true); 898 }, 899 900 /** 901 * @desc Get default values 902 * @example $(selector).accessibleMegaMenu("getDefaults"); 903 * @return {object} 904 * @memberof jQuery.fn.accessibleMegaMenu 905 * @instance 906 */ 907 getDefaults: function () { 908 return this._defaults; 909 }, 910 911 /** 912 * @desc Get any option set to plugin using its name (as string) 913 * @example $(selector).accessibleMegaMenu("getOption", some_option); 914 * @param {string} opt 915 * @return {string} 916 * @memberof jQuery.fn.accessibleMegaMenu 917 * @instance 918 */ 919 getOption: function (opt) { 920 return this.settings[opt]; 921 }, 922 923 /** 924 * @desc Get all options 925 * @example $(selector).accessibleMegaMenu("getAllOptions"); 926 * @return {object} 927 * @memberof jQuery.fn.accessibleMegaMenu 928 * @instance 929 */ 930 getAllOptions: function () { 931 return this.settings; 932 }, 933 934 /** 935 * @desc Set option 936 * @example $(selector).accessibleMegaMenu("setOption", "option_name", "option_value", reinitialize); 937 * @param {string} opt - Option name 938 * @param {string} val - Option value 939 * @param {boolean} [reinitialize] - boolean to re-initialize the menu. 940 * @memberof jQuery.fn.accessibleMegaMenu 941 * @instance 942 */ 943 setOption: function (opt, value, reinitialize) { 944 this.settings[opt] = value; 945 if (reinitialize) { 946 this.init(); 947 } 948 } 949 }; 950 }()); 951 952 /* lightweight plugin wrapper around the constructor, 953 to prevent against multiple instantiations */ 954 955 /** 956 * @class accessibleMegaMenu 957 * @memberOf jQuery.fn 958 * @classdesc Implements an accessible mega menu as a jQuery plugin. 959 * <p>The mega-menu It is modeled after the mega menu on {@link http://adobe.com|adobe.com} but has been simplified for use by others. A brief description of the interaction design choices can be found in a blog post at {@link http://blogs.adobe.com/accessibility/2013/05/adobe-com.html|Mega menu accessibility on adobe.com}.</p> 960 * <h3>Keyboard Accessibility</h3> 961 * <p>The accessible mega menu supports keyboard interaction modeled after the behavior described in the {@link http://www.w3.org/TR/wai-aria-practices/#menu|WAI-ARIA Menu or Menu bar (widget) design pattern}, however we also try to respect users' general expectations for the behavior of links in a global navigation. To this end, the accessible mega menu implementation permits tab focus on each of the six top-level menu items. When one of the menu items has focus, pressing the Enter key, Spacebar or Down arrow will open the submenu panel, and pressing the Left or Right arrow key will shift focus to the adjacent menu item. Links within the submenu panels are included in the tab order when the panel is open. They can also be navigated with the arrow keys or by typing the first character in the link name, which speeds up keyboard navigation considerably. Pressing the Escape key closes the submenu and restores focus to the parent menu item.</p> 962 * <h3>Screen Reader Accessibility</h3> 963 * <p>The accessible mega menu models its use of WAI-ARIA Roles, States, and Properties after those described in the {@link http://www.w3.org/TR/wai-aria-practices/#menu|WAI-ARIA Menu or Menu bar (widget) design pattern} with some notable exceptions, so that it behaves better with screen reader user expectations for global navigation. We don't use <code class="prettyprint prettyprinted" style=""><span class="pln">role</span><span class="pun">=</span><span class="str">"menu"</span></code> for the menu container and <code class="prettyprint prettyprinted" style=""><span class="pln">role</span><span class="pun">=</span><span class="str">"menuitem"</span></code> for each of the links therein, because if we do, assistive technology will no longer interpret the links as links, but instead, as menu items, and the links in our global navigation will no longer show up when a screen reader user executes a shortcut command to bring up a list of links in the page.</p> 964 * @example <h4>HTML</h4><hr/> 965<nav> 966 <ul class="nav-menu"> 967 <li class="nav-item"> 968 <a href="?movie">Movies</a> 969 <div class="sub-nav"> 970 <ul class="sub-nav-group"> 971 <li><a href="?movie&genre=0">Action &amp; Adventure</a></li> 972 <li><a href="?movie&genre=2">Children &amp; Family</a></li> 973 <li>&#8230;</li> 974 </ul> 975 <ul class="sub-nav-group"> 976 <li><a href="?movie&genre=7">Dramas</a></li> 977 <li><a href="?movie&genre=9">Foreign</a></li> 978 <li>&#8230;</li> 979 </ul> 980 <ul class="sub-nav-group"> 981 <li><a href="?movie&genre=14">Musicals</a></li> 982 <li><a href="?movie&genre=15">Romance</a></li> 983 <li>&#8230;</li> 984 </ul> 985 </div> 986 </li> 987 <li class="nav-item"> 988 <a href="?tv">TV Shows</a> 989 <div class="sub-nav"> 990 <ul class="sub-nav-group"> 991 <li><a href="?tv&genre=20">Classic TV</a></li> 992 <li><a href="?tv&genre=21">Crime TV</a></li> 993 <li>&#8230;</li> 994 </ul> 995 <ul class="sub-nav-group"> 996 <li><a href="?tv&genre=27">Reality TV</a></li> 997 <li><a href="?tv&genre=30">TV Action</a></li> 998 <li>&#8230;</li> 999 </ul> 1000 <ul class="sub-nav-group"> 1001 <li><a href="?tv&genre=33">TV Dramas</a></li> 1002 <li><a href="?tv&genre=34">TV Horror</a></li> 1003 <li>&#8230;</li> 1004 </ul> 1005 </div> 1006 </li> 1007 </ul> 1008</nav> 1009 * @example <h4>CSS</h4><hr/> 1010/* Rudimentary mega menu CSS for demonstration */ 1011 1012/* mega menu list */ 1013.nav-menu { 1014 display: block; 1015 position: relative; 1016 list-style: none; 1017 margin: 0; 1018 padding: 0; 1019 z-index: 15; 1020} 1021 1022/* a top level navigation item in the mega menu */ 1023.nav-item { 1024 list-style: none; 1025 display: inline-block; 1026 padding: 0; 1027 margin: 0; 1028} 1029 1030/* first descendant link within a top level navigation item */ 1031.nav-item > a { 1032 position: relative; 1033 display: inline-block; 1034 padding: 0.5em 1em; 1035 margin: 0 0 -1px 0; 1036 border: 1px solid transparent; 1037} 1038 1039/* focus/open states of first descendant link within a top level 1040 navigation item */ 1041.nav-item > a:focus, 1042.nav-item > a.open { 1043 border: 1px solid #dedede; 1044} 1045 1046/* open state of first descendant link within a top level 1047 navigation item */ 1048.nav-item > a.open { 1049 background-color: #fff; 1050 border-bottom: none; 1051 z-index: 1; 1052} 1053 1054/* sub-navigation panel */ 1055.sub-nav { 1056 position: absolute; 1057 display: none; 1058 top: 2.2em;
1059 margin-top: -1px; 1060 padding: 0.5em 1em; 1061 border: 1px solid #dedede; 1062 background-color: #fff; 1063} 1064 1065/* sub-navigation panel open state */ 1066.sub-nav.open { 1067 display: block; 1068} 1069 1070/* list of items within sub-navigation panel */ 1071.sub-nav ul { 1072 display: inline-block; 1073 vertical-align: top; 1074 margin: 0 1em 0 0; 1075 padding: 0; 1076} 1077 1078/* list item within sub-navigation panel */ 1079.sub-nav li { 1080 display: block; 1081 list-style-type: none; 1082 margin: 0; 1083 padding: 0; 1084} 1085 * @example <h4>JavaScript</h4><hr/> 1086<!-- include jquery --> 1087<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> 1088 1089<!-- include the jquery-accessibleMegaMenu plugin script --> 1090<script src="js/jquery-accessibleMegaMenu.js"></script> 1091 1092<!-- initialize a selector as an accessibleMegaMenu --> 1093<script> 1094 $("nav:first").accessibleMegaMenu({ 1095 /* prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby */ 1096 uuidPrefix: "accessible-megamenu", 1097 1098 /* css class used to define the megamenu styling */ 1099 menuClass: "nav-menu", 1100 1101 /* css class for a top-level navigation item in the megamenu */ 1102 topNavItemClass: "nav-item", 1103 1104 /* css class for a megamenu panel */ 1105 panelClass: "sub-nav", 1106 1107 /* css class for a group of items within a megamenu panel */ 1108 panelGroupClass: "sub-nav-group", 1109 1110 /* css class for the hover state */ 1111 hoverClass: "hover", 1112 1113 /* css class for the focus state */ 1114 focusClass: "focus", 1115 1116 /* css class for the open state */ 1117 openClass: "open" 1118 }); 1119</script> 1120 * @param {object} [options] Mega Menu options 1121 * @param {string} [options.uuidPrefix=accessible-megamenu] - Prefix for generated unique id attributes, which are required to indicate aria-owns, aria-controls and aria-labelledby 1122 * @param {string} [options.menuClass=accessible-megamenu] - CSS class used to define the megamenu styling 1123 * @param {string} [options.topNavItemClass=accessible-megamenu-top-nav-item] - CSS class for a top-level navigation item in the megamenu 1124 * @param {string} [options.panelClass=accessible-megamenu-panel] - CSS class for a megamenu panel 1125 * @param {string} [options.panelGroupClass=accessible-megamenu-panel-group] - CSS class for a group of items within a megamenu panel 1126 * @param {string} [options.hoverClass=hover] - CSS class for the hover state 1127 * @param {string} [options.focusClass=focus] - CSS class for the focus state 1128 * @param {string} [options.openClass=open] - CSS class for the open state 1129 * @param {string} [options.openDelay=0] - Open delay when opening menu via mouseover 1130 * @param {string} [options.closeDelay=250] - Open delay when opening menu via mouseover 1131 * @param {boolean} [options.openOnMouseover=false] - Should menu open on mouseover 1132 */ 1133 $.fn[pluginName] = function (options) { 1134 return this.each(function () { 1135 var pluginInstance = $.data(this, "plugin_" + pluginName); 1136 if (!pluginInstance) { 1137 $.data(this, "plugin_" + pluginName, new $.fn[pluginName].Acce
1137ssibleMegaMenu(this, options)); 1138 } else if (typeof pluginInstance[options] === 'function') { 1139 pluginInstance[options].apply(pluginInstance, Array.prototype.slice.call(arguments, 1)); 1140 } 1141 }); 1142 }; 1143 1144 $.fn[pluginName].AccessibleMegaMenu = AccessibleMegaMenu; 1145 1146 /* :focusable and :tabbable selectors from 1147 https://raw.github.com/jquery/jquery-ui/master/ui/jquery.ui.core.js */ 1148 1149 /** 1150 * @private 1151 */ 1152 function visible(element) { 1153 return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function () { 1154 return $.css(this, "visibility") === "hidden"; 1155 }).length; 1156 } 1157 1158 /** 1159 * @private 1160 */ 1161 function focusable(element, isTabIndexNotNaN) { 1162 var map, mapName, img, 1163 nodeName = element.nodeName.toLowerCase(); 1164 if ("area" === nodeName) { 1165 map = element.parentNode; 1166 mapName = map.name; 1167 if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") { 1168 return false; 1169 } 1170 img = $("img[usemap=#" + mapName + "]")[0]; 1171 return !!img && visible(img); 1172 } 1173 return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled : 1174 "a" === nodeName ? 1175 element.href || isTabIndexNotNaN : 1176 isTabIndexNotNaN) && 1177 // the element and all of its ancestors must be visible 1178 visible(element); 1179 } 1180 1181 $.extend($.expr[":"], { 1182 data: $.expr.createPseudo ? $.expr.createPseudo(function (dataName) { 1183 return function (elem) { 1184 return !!$.data(elem, dataName); 1185 }; 1186 }) : // support: jQuery <1.8 1187 function (elem, i, match) { 1188 return !!$.data(elem, match[3]); 1189 }, 1190 1191 focusable: function (element) { 1192 return focusable(element, !isNaN($.attr(element, "tabindex"))); 1193 }, 1194 1195 tabbable: function (element) { 1196 var tabIndex = $.attr(element, "tabindex"), 1197 isTabIndexNaN = isNaN(tabIndex); 1198 return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN); 1199 } 1200 }); 1201}(jQuery, window, document));
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.