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