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