1/* Copyright 2013 Adobe Systems Incorporated. */ 2(function ($, window, document) { 3 "use strict"; 4 var pluginName = "accessibleMegaMenu", 5 defaults = { 6 uuidPrefix: "accessible-megamenu", 7 menuClass: "accessible-megamenu", 8 topNavItemClass: "accessible-megamenu-top-nav-item", 9 panelClass: "accessible-megamenu-panel", 10 panelGroupClass: "accessible-megamenu-panel-group", 11 hoverClass: "hover", 12 focusClass: "focus", 13 openClass: "open", 14 toggleButtonClass: "accessible-megamenu-toggle", 15 openDelay: 0, 16 closeDelay: 250, 17 openOnMouseover: false 18 }, 19 Keyboard = { 20 BACKSPACE: 8, 21 COMMA: 188, 22 DELETE: 46, 23 DOWN: 40, 24 END: 35, 25 ENTER: 13, 26 ESCAPE: 27, 27 HOME: 36, 28 LEFT: 37, 29 PAGE_DOWN: 34, 30 PAGE_UP: 33, 31 PERIOD: 190, 32 RIGHT: 39, 33 SPACE: 32, 34 TAB: 9, 35 UP: 38, 36 keyMap: { 37 48: "0", 38 49: "1", 39 50: "2", 40 51: "3", 41 52: "4", 42 53: "5", 43 54: "6", 44 55: "7", 45 56: "8", 46 57: "9", 47 59: ";", 48 65: "a", 49 66: "b", 50 67: "c", 51 68: "d", 52 69: "e", 53 70: "f", 54 71: "g", 55 72: "h", 56 73: "i", 57 74: "j", 58 75: "k", 59 76: "l", 60 77: "m", 61 78: "n", 62 79: "o", 63 80: "p", 64 81: "q", 65 82: "r", 66 83: "s", 67 84: "t", 68 85: "u", 69 86: "v", 70 87: "w", 71 88: "x", 72 89: "y", 73 90: "z", 74 96: "0", 75 97: "1", 76 98: "2", 77 99: "3", 78 100: "4", 79 101: "5", 80 102: "6", 81 103: "7", 82 104: "8", 83 105: "9", 84 190: "." 85 } 86 }, 87 clearTimeout = window.clearTimeout, 88 setTimeout = window.setTimeout, 89 isOpera = window.opera && window.opera.toString() === '[object Opera]'; 90 /* @desc Creates a new accessible mega menu instance. */ 91 function AccessibleMegaMenu(element, options) { 92 this.element = element; 93 // merge optional settings and defaults into settings 94 this.settings = $.extend({}, defaults, options); 95 this._defaults = defaults; 96 this._name = pluginName; 97 this.mouseTimeoutID = null; 98 this.focusTimeoutID = null; 99 this.mouseFocused = false; 100 this.justFocused = false; 101 this.init(); 102 } 103 AccessibleMegaMenu.prototype = (function () { 104 var uuid = 0, 105 keydownTimeoutDuration = 1000, 106 keydownSearchString = "", 107 isTouch = 'ontouchstart' in window || window.navigator.msMaxTouchPoints, 108 _getPlugin, 109 _addUniqueId, 110 _togglePanel, 111 _clickHandler, 112 _touchmoveHandler, 113 _clickOutsideHandler, 114 _DOMAttrModifiedHandler, 115 _focusInHandler, 116 _focusOutHandler, 117 _keyDownHandler, 118 _mouseDownHandler, 119 _mouseOverHandler, 120 _mouseOutHandler, 121 _clickToggleHandler, 122 _toggleExpandedEventHandlers, 123 _addEventHandlers, 124 _removeEventHandlers; 125 /* @desc Returns the parent accessibleMegaMenu instance for a given element */ 126 _getPlugin = function (element) { 127 return $(element).closest(':data(plugin_' + pluginName + ')').data("plugin_" + pluginName); 128 }; 129 /* @desc Adds a unique id and element. */ 130 _addUniqueId = function (element) { 131 element = $(element); 132 var settings = this.settings; 133 if (!element.attr("id")) { 134 element.attr("id", settings.uuidPrefix + "-" + new Date().getTime() + "-" + (++uuid)); 135 } 136 }; 137 /* @desc Toggle the display of mega menu panels in response to an event. */ 138 _togglePanel = function (event, hide) { 139 var target = $(event.target), 140 that = this, 141 settings = this.settings, 142 menu = this.menu, 143 topli = target.closest('.' + settings.topNavItemClass), 144 panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass), 145 newfocus; 146 _toggleExpandedEventHandlers.call(this, true); 147 // Hide all panels. 148 if (hide) { 149 // Get the first top level menu item. 150 topli = menu.find('.' + settings.topNavItemClass + ' .' + settings.openClass + ':first').closest('.' + settings.topNavItemClass); 151 // Validate event. 152 if (!(topli.is(event.relatedTarget) || topli.has(event.relatedTarget).length > 0)) { 153 if ((event.type === 'mouseout' || event.type === 'focusout') && topli.has(document.activeElement).length > 0) { 154 return; 155 } 156 // Close top level link. 157 topli.find('[aria-expanded]') 158 .attr('aria-expanded', 'false') 159 .removeClass(settings.openClass); 160 // Close panel. 161 topli.find('.' + settings.panelClass) 162 .removeClass(settings.openClass); 163 if ((event.type === 'keydown' && event.keyCode === Keyboard.ESCAPE) || event.type === 'DOMAttrModified') { 164 newfocus = topli.find(':tabbable:first'); 165 setTimeout(function () { 166 menu.find('[aria-expanded].' + that.settings.panelClass).off('DOMAttrModified.accessible-megamenu'); 167 newfocus.focus(); 168 that.justFocused = false; 169 }, 99); 170 } 171 } else if (topli.length === 0) { 172 menu.find('[aria-expanded=true]') 173 .attr('aria-expanded', 'false') 174 .removeClass(settings.openClass) 175 .closest('.' + settings.topNavItemClass) 176 .find('.' + settings.panelClass) 177 .removeClass(settings.openClass); 178 } 179 } 180 // Toggle panels. 181 else { 182 clearTimeout(that.focusTimeoutID); 183 // Close previously open top level link and its panel. 184 var openli = menu.find('[aria-expanded=true]').closest('.' + settings.topNavItemClass); 185 if (!openli.is(topli)) { 186 openli.find('[aria-expanded]') 187 .attr('aria-expanded', 'false') 188 .removeClass(settings.openClass); 189 openli.find('.' + settings.panelClass) 190 .removeClass(settings.openClass); 191 } 192 // Open current top level link and its panel. 193 topli.find('[aria-expanded]') 194 .attr('aria-expanded', 'true') 195 .addClass(settings.openClass); 196 topli.find('.' + settings.panelClass) 197 .addClass(settings.openClass); 198 199 if (topli.length) { 200 var pageScrollPosition = $('html')[0].scrollTop; 201 var openPanelTopPosition = topli.offset().top; 202 203 if (pageScrollPosition > openPanelTopPosition) { 204 $('html')[0].scrollTop = openPanelTopPosition; 205 } 206 } 207 208 if (event.type === 'mouseover' && target.is(':tabbable') && topli.length === 1 && panel.length === 0 && menu.has(document.activeElement).length > 0) {
209 target.focus(); 210 that.justFocused = false; 211 } 212 213 _toggleExpandedEventHandlers.call(that); 214 } 215 }; 216 /* @desc Handle click event on mega menu item */ 217 _clickHandler = function (event) { 218 var target = $(event.target).closest(':tabbable'), 219 topli = target.closest('.' + this.settings.topNavItemClass), 220 panel = target.closest('.' + this.settings.panelClass); 221 // With panel. 222 if (topli.length === 1 && panel.length === 0 && topli.find('.' + this.settings.panelClass).length === 1) { 223 // Handle click event. 224 if (!target.hasClass(this.settings.openClass)) { 225 event.preventDefault(); 226 event.stopPropagation(); 227 _togglePanel.call(this, event); 228 this.justFocused = false; 229 } 230 else { 231 // Handle focus event. 232 if (this.justFocused) { 233 event.preventDefault(); 234 event.stopPropagation(); 235 this.justFocused = false; 236 } 237 // Handle touch/click event. 238 else if (isTouch || !isTouch && !this.settings.openOnMouseover) { 239 event.preventDefault(); 240 event.stopPropagation(); 241 _togglePanel.call(this, event, target.hasClass(this.settings.openClass)); 242 } 243 } 244 }
245 // Without panel on enter event. 246 else if (topli.length === 1 && panel.length === 0 && event.type == "keydown" && target.hasAttribute("href")) { 247 window.location.href = target.attr("href"); 248 } 249 }; 250 /* @desc Handle touch move event on menu */ 251 _touchmoveHandler = function () { 252 this.justMoved = true; 253 }; 254 /* @desc Handle click event outside of a the megamenu */ 255 _clickOutsideHandler = function (event) { 256 if ($(event.target).closest(this.menu).length === 0) { 257 event.preventDefault(); 258 event.stopPropagation(); 259 _togglePanel.call(this, event, true); 260 } 261 }; 262 /* @desc Handle DOMAttrModified event on panel to respond to Windows 8 Narrator */ 263 _DOMAttrModifiedHandler = function (event) { 264 if (event.originalEvent.attrName === 'aria-expanded' 265 && event.originalEvent.newValue === 'false' 266 && $(event.target).hasClass(this.settings.openClass)) { 267 event.preventDefault(); 268 event.stopPropagation(); 269 _togglePanel.call(this, event, true); 270 } 271 }; 272 /* @desc Handle focusin event on mega menu item. */ 273 _focusInHandler = function (event) { 274 clearTimeout(this.focusTimeoutID); 275 var target = $(event.target), 276 panel = target.closest('.' + this.settings.panelClass); 277 target 278 .addClass(this.settings.focusClass); 279 this.justFocused = !this.mouseFocused || (!this.settings.openOnMouseover && this.mouseFocused); 280 this.mouseFocused = false; 281 if (this.justFocused && this.panels.not(panel).filter('.' + this.settings.openClass).length) { 282 _togglePanel.call(this, event); 283 } 284 }; 285 /* @desc Handle focusout event on mega menu item. */ 286 _focusOutHandler = function (event) { 287 this.justFocused = false; 288 var that = this, 289 target = $(event.target), 290 topli = target.closest('.' + this.settings.topNavItemClass); 291 target 292 .removeClass(this.settings.focusClass); 293 if (typeof window.cvox === 'object' && typeof window.cvox.Api !== 'object') { 294 // If ChromeVox is running... 295 that.focusTimeoutID = setTimeout(function () { 296 window.cvox.Api.getCurrentNode(function (node) { 297 if (topli.has(node).length) { 298 // and the current node being voiced is in 299 // the mega menu, clearTimeout, 300 // so the panel stays open. 301 clearTimeout(that.focusTimeoutID); 302 } else { 303 that.focusTimeoutID = setTimeout(function (scope, event, hide) { 304 _togglePanel.call(scope, event, hide); 305 }, 275, that, event, true); 306 } 307 }); 308 }, 25); 309 } else { 310 that.focusTimeoutID = setTimeout(function () { 311 if (that.mouseFocused && event.relatedTarget === null) { 312 return; 313 } 314 _togglePanel.call(that, event, true); 315 }, 300); 316 } 317 }; 318 /* @desc Handle keydown event on mega menu. */ 319 _keyDownHandler = function (event) { 320 var that = (this.constructor === AccessibleMegaMenu) ? this : _getPlugin(this), // determine the AccessibleMegaMenu plugin instance 321 settings = that.settings, 322 target = $($(this).is('.' + settings.hoverClass + ':tabbable') ? this : event.target), // if the element is hovered the target is this, otherwise, its the focused element 323 menu = that.menu, 324 topnavitems = that.topnavitems, 325 topli = target.closest('.' + settings.topNavItemClass), 326 tabbables = menu.find(':tabbable'), 327 panel = target.hasClass(settings.panelClass) ? target : target.closest('.' + settings.panelClass), 328 panelGroups = panel.find('.' + settings.panelGroupClass), 329 currentPanelGroup = target.closest('.' + settings.panelGroupClass), 330 next, 331 keycode = event.keyCode || event.which, 332 start, 333 i, 334 o, 335 label, 336 found = false, 337 newString = Keyboard.keyMap[event.keyCode] || '', 338 regex, 339 isTopNavItem = (topli.length === 1 && panel.length === 0); 340
341 if (target.is("input:focus, select:focus, textarea:focus, button:focus")) { 342 // if the event target is a form element we should handle keydown normally 343 return; 344 } 345 if (target.is('.' + settings.hoverClass + ':tabbable')) { 346 $('html').off('keydown.accessible-megamenu'); 347 } 348 switch (keycode) { 349 case Keyboard.ESCAPE: 350 this.mouseFocused = false; 351 _togglePanel.call(that, event, true); 352 break; 353 case Keyboard.DOWN: 354 event.preventDefault(); 355 this.mouseFocused = false; 356 if (isTopNavItem) { 357 _togglePanel.call(that, event); 358 found = (topli.find('.' + settings.panelClass + ' :tabbable:first').focus().length === 1); 359 } else { 360 found = (tabbables.filter(':gt(' + tabbables.index(target) + '):first').focus().length === 1); 361 } 362 363 if (!found && isOpera && (event.ctrlKey || event.metaKey)) { 364 tabbables = $(':tabbable'); 365 i = tabbables.index(target); 366 found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 367 } 368 break; 369 case Keyboard.UP: 370 event.preventDefault(); 371 this.mouseFocused = false; 372 if (isTopNavItem && target.hasClass(settings.openClass)) { 373 _togglePanel.call(that, event, true); 374 next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last'); 375 if (next.children('.' + settings.panelClass).length) { 376 next.find('[aria-expanded]') 377 .attr('aria-expanded', 'true') 378 .addClass(settings.openClass); 379 found = (next.children('.' + settings.panelClass) 380 .addClass(settings.openClass) 381 .find(':tabbable:last') 382 .focus() === 1); 383 } 384 } else if (!isTopNavItem) { 385 found = (tabbables.filter(':lt(' + tabbables.index(target) + '):last').focus().length === 1); 386 } 387 388 if (!found && isOpera && (event.ctrlKey || event.metaKey)) { 389 tabbables = $(':tabbable'); 390 i = tabbables.index(target); 391 found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 392 } 393 break; 394 case Keyboard.RIGHT: 395 event.preventDefault(); 396 this.mouseFocused = false; 397 if (isTopNavItem) { 398 found = (topnavitems.filter(':gt(' + topnavitems.index(topli) + '):first').find(':tabbable:first').focus().length === 1); 399 } else { 400 if (panelGroups.length && currentPanelGroup.length) { 401 // if the current panel contains panel groups, and we are able to focus the first tabbable element of the next panel group 402 found = (panelGroups.filter(':gt(' + panelGroups.index(currentPanelGroup) + '):first').find(':tabbable:first').focus().length === 1); 403 } 404 405 if (!found) { 406 found = (topli.find(':tabbable:first').focus().length === 1); 407 } 408 } 409 break; 410 case Keyboard.LEFT: 411 event.preventDefault(); 412 this.mouseFocused = false; 413 if (isTopNavItem) { 414 found = (topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last').find(':tabbable:first').focus().length === 1); 415 } else { 416 if (panelGroups.length && currentPanelGroup.length) { 417 // if the current panel contains panel groups, and we are able to focus the first tabbable element of the previous panel group 418 found = (panelGroups.filter(':lt(' + panelGroups.index(currentPanelGroup) + '):last').find(':tabbable:first').focus().length === 1); 419 } 420 421 if (!found) { 422 found = (topli.find(':tabbable:first').focus().length === 1); 423 } 424 } 425 break; 426 case Keyboard.TAB: 427 this.mouseFocused = false; 428 i = tabbables.index(target); 429 if (event.shiftKey && isTopNavItem && target.hasClass(settings.openClass)) { 430 _togglePanel.call(that, event, true); 431 next = topnavitems.filter(':lt(' + topnavitems.index(topli) + '):last'); 432 if (next.children('.' + settings.panelClass).length) { 433 found = next.children() 434 .attr('aria-expanded', 'true') 435 .addClass(settings.openClass) 436 .filter('.' + settings.panelClass) 437 .find(':tabbable:last') 438 .focus(); 439 } 440 } else if (event.shiftKey && i > 0) { 441 found = (tabbables.filter(':lt(' + i + '):last').focus().length === 1); 442 } else if (!event.shiftKey && i < tabbables.length - 1) { 443 found = (tabbables.filter(':gt(' + i + '):first').focus().length === 1); 444 } else if (isOpera) { 445 tabbables = $(':tabbable'); 446 i = tabbables.index(target); 447 if (event.shiftKey) { 448 found = ($(':tabbable:lt(' + $(':tabbable').index(target) + '):last').focus().length === 1); 449 } else { 450 found = ($(':tabbable:gt(' + $(':tabbable').index(target) + '):first').focus().length === 1); 451 } 452 } 453 if (found) { 454 event.preventDefault(); 455 } 456 break; 457 case Keyboard.SPACE: 458 case Keyboard.ENTER: 459 // Top level links. 460 if (isTopNavItem) { 461 event.preventDefault(); 462 // Handle enter event on open top level link as escape event. 463 if (target.hasClass("open")) { 464 this.mouseFocused = false; 465 _togglePanel.call(that, event, true); 466 } 467 // Handle enter event on top level link as a click. 468 else { 469 _clickHandler.call(that, event); 470 } 471 } 472 // Sub level links. 473 else { 474 return true; 475 } 476 break; 477 default: 478 // alphanumeric filter 479 clearTimeout(this.keydownTimeoutID); 480 keydownSearchString += newString !== keydownSearchString ? newString : ''; 481 if (keydownSearchString.length === 0) { 482 return; 483 } 484 this.keydownTimeoutID = setTimeout(function () { 485 keydownSearchString = ''; 486 }, keydownTimeoutDuration); 487 if (isTopNavItem && !target.hasClass(settings.openClass)) { 488 tabbables = tabbables.filter(':not(.' + settings.panelClass + ' :tabbable)'); 489 } else { 490 tabbables = topli.find(':tabbable'); 491 } 492 if (event.shiftKey) { 493 tabbables = $(tabbables.get() 494 .reverse()); 495 } 496 for (i = 0; i < tabbables.length; i++) { 497 o = tabbables.eq(i); 498 if (o.is(target)) { 499 start = (keydownSearchString.length === 1) ? i + 1 : i; 500 break; 501 } 502 } 503 regex = new RegExp('^' + keydownSearchString.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&'), 'i'); 504 for (i = start; i < tabbables.length; i++) { 505 o = tabbables.eq(i); 506 label = $.trim(o.text()); 507 if (regex.test(label)) { 508 found = true; 509 o.focus(); 510 break; 511 } 512 } 513 if (!found) { 514 for (i = 0; i < start; i++) { 515 o = tabbables.eq(i); 516 label = $.trim(o.text()); 517 if (regex.test(label)) { 518 o.focus(); 519 break; 520 } 521 } 522 } 523 break; 524 } 525 that.justFocused = false; 526 }; 527 /* @desc Handle mousedown event on mega menu. */ 528 _mouseDownHandler = function (event) { 529 if ($(event.target).closest(this.settings.panelClass) || $(event.target).closest(":focusable").length) { 530 this.mouseFocused = true; 531 if ($(event.target).closest(this.settings.menuClass)) { 532 $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target)); 533 } 534 } 535 clearTimeout(this.mouseTimeoutID); 536 this.mouseTimeoutID = setTimeout(function () { 537 clearTimeout(this.focusTimeoutID); 538 }, 1); 539 }; 540 /* @desc Handle mouseover event on mega menu. */ 541 _mouseOverHandler = function (event) { 542 clearTimeout(this.mouseTimeoutID); 543 var that = this; 544 545 if (!that.settings.openOnMouseover) { 546 return; 547 } 548 this.mouseTimeoutID = setTimeout(function () { 549 $(event.target).addClass(that.settings.hoverClass); 550 _togglePanel.call(that, event); 551 if ($(event.target).closest(that.settings.menuClass)) { 552 $('html').on('keydown.accessible-megamenu', $.proxy(_keyDownHandler, event.target)); 553 } 554 }, this.settings.openDelay); 555 }; 556 /* @desc Handle mouseout event on mega menu. */ 557 _mouseOutHandler = function (event) { 558 clearTimeout(this.mouseTimeoutID); 559 var that = this; 560 561 if (!that.settings.openOnMouseover) { 562 return; 563 } 564 $(event.target) 565 .removeClass(that.settings.hoverClass); 566 that.mouseTimeoutID = setTimeout(function () { 567 _togglePanel.call(that, event, true); 568 }, this.settings.closeDelay); 569 if ($(event.target).is(':tabbable')) { 570 $('html').off('keydown.accessible-megamenu'); 571 } 572 }; 573 /* @desc Handle click event on menu toggle button. */ 574 _clickToggleHandler = function () { 575 var isExpanded = this.toggleButton.attr('aria-expanded') === 'true'; 576 this.toggleButton.attr({'aria-expanded': !isExpanded}); 577 }; 578 _toggleExpandedEventHandlers = function (hide) { 579 var menu = this.menu; 580 if (hide) { 581 $('html').off('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu, pointerup.outside-accessible-megamenu'); 582 583 menu.find('[aria-expanded].' + this.settings.panelClass).off('DOMAttrModified.accessible-megamenu'); 584 } else { 585 $('html').on('mouseup.outside-accessible-megamenu, touchend.outside-accessible-megamenu, mspointerup.outside-accessible-megamenu, pointerup.outside-accessible-megamenu', $.proxy(_clickOutsideHandler, this)); 586 menu.find('[aria-expanded=true].' + this.settings.panelClass).on('DOMAttrModified.accessible-megamenu', $.proxy(_DOMAttrModifiedHandler, this)); 587 } 588 }; 589 _addEventHandlers = function() { 590 var menu = this.menu, 591 toggleButton = this.toggleButton; 592 menu.on("focusin.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusInHandler, this)) 593 .on("focusout.accessible-megamenu", ":focusable, ." + this.settings.panelClass, $.proxy(_focusOutHandler, this))
594 .on("keydown.accessible-megamenu", $.proxy(_keyDownHandler, this)) 595 .on("mouseover.accessible-megamenu", $.proxy(_mouseOverHandler, this)) 596 .on("mouseout.accessible-megamenu", $.proxy(_mouseOutHandler, this)) 597 .on("mousedown.accessible-megamenu", $.proxy(_mouseDownHandler, this)) 598 .on("click.accessible-megamenu", $.proxy(_clickHandler, this)); 599 600 toggleButton.on('click.accessible-megamenu', $.proxy(_clickToggleHandler, this)); 601 602 if (isTouch) { 603 menu.on("touchmove.accessible-megamenu", $.proxy(_touchmoveHandler, this)); 604 } 605 606 if ($(document.activeElement).closest(menu).length) { 607 $(document.activeElement).trigger("focusin.accessible-megamenu"); 608 } 609 }; 610 _removeEventHandlers = function () { 611 var menu = this.menu, 612 toggleButton = this.toggleButton; 613 menu.off('.accessible-megamenu'); 614 if (menu.find('[aria-expanded=true].' + this.settings.panelClass).length) { 615 _toggleExpandedEventHandlers.call(this, true); 616 } 617 618 toggleButton.off('.accessible-megamenu'); 619 }; 620 return { 621 constructor: AccessibleMegaMenu, 622 /* @desc Initializes an instance of the accessibleMegaMenu plugins */ 623 init: function () { 624 var settings = this.settings, 625 nav = $(this.element), 626 menu = nav.children('ol,ul').first(), 627 topnavitems = menu.children(), 628 toggleButton = nav.children('button').first(); 629 this.start(settings, nav, menu, topnavitems, toggleButton); 630 }, 631 start: function(settings, nav, menu, topnavitems, toggleButton) { 632 var that = this; 633 this.settings = settings; 634 this.menu = menu; 635 this.topnavitems = topnavitems; 636 this.toggleButton = toggleButton; 637 _addUniqueId.call(that, menu); 638 menu.addClass(settings.menuClass); 639 menu.addClass(['js', settings.menuClass].join('-')); 640 topnavitems.each(function (i, topnavitem) { 641 var topnavitemlink, topnavitempanel; 642 topnavitem = $(topnavitem); 643 topnavitem.addClass(settings.topNavItemClass); 644 topnavitemlink = topnavitem.find(":tabbable:first"); 645 topnavitempanel = topnavitem.children(":not(:tabbable):last"); 646 _addUniqueId.call(that, topnavitemlink); 647 // When sub nav exists. 648 if (topnavitempanel.length) { 649 _addUniqueId.call(that, topnavitempanel); 650 // Add attributes to top level link. 651 topnavitemlink.attr({ 652 "aria-expanded": false, 653 "tabindex": 0 654 }); 655 // Add attributes to sub nav. 656 topnavitempanel.attr({ 657 658 }) 659 .addClass(settings.panelClass) 660 .not("[aria-labelledby]") 661 .attr("aria-labelledby", topnavitemlink.attr("id")); 662 } 663 }); 664 this.panels = menu.find("." + settings.panelClass); 665 toggleButton.addClass(settings.toggleButtonClass); 666 toggleButton.attr({'aria-expanded': false}); 667 _addEventHandlers.call(this); 668 }, 669 /* @example $(selector).accessibleMegaMenu("destroy"); */ 670 destroy: function () { 671 this.menu.removeClass(['js', this.settings.menuClass].join('-')); 672 _removeEventHandlers.call(this, true); 673 }, 674 /* @example $(selector).accessibleMegaMenu("getDefaults"); */ 675 getDefaults: function () { 676 return this._defaults; 677 }, 678 679 /* @example $(selector).accessibleMegaMenu("getOption", some_option); */ 680 getOption: function (opt) { 681 return this.settings[opt]; 682 }, 683 684 /* @example $(selector).accessibleMegaMenu("getAllOptions"); */ 685 getAllOptions: function () { 686 return this.settings; 687 }, 688 /* @example $(selector).accessibleMegaMenu */ 689 setOption: function (opt, value, reinitialize) { 690 this.settings[opt] = value; 691 if (reinitialize) { 692 this.init(); 693 } 694 } 695 }; 696 }()); 697 $.fn[pluginName] = function (options) { 698 return this.each(function () { 699 var pluginInstance = $.data(this, "plugin_" + pluginName); 700 if (!pluginInstance) { 701 $.data(this, "plugin_" + pluginName, new $.fn[pluginName].Acce
701ssibleMegaMenu(this, options)); 702 } else if (typeof pluginInstance[options] === 'function') { 703 pluginInstance[options].apply(pluginInstance, Array.prototype.slice.call(arguments, 1)); 704 } 705 }); 706 }; 707 $.fn[pluginName].AccessibleMegaMenu = AccessibleMegaMenu; 708 /* :focusable and :tabbable selectors */ 709 function visible(element) { 710 return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function () { 711 return $.css(this, "visibility") === "hidden"; 712 }).length; 713 } 714 function focusable(element, isTabIndexNotNaN) { 715 var map, mapName, img, 716 nodeName = element.nodeName.toLowerCase(); 717 if ("area" === nodeName) { 718 map = element.parentNode; 719 mapName = map.name; 720 if (!element.href || !mapName || map.nodeName.toLowerCase() !== "map") { 721 return false; 722 } 723 img = $("img[usemap=#" + mapName + "]")[0]; 724 return !!img && visible(img); 725 } 726 return (/input|select|textarea|button|object/.test(nodeName) ? !element.disabled : 727 "a" === nodeName ? 728 element.href || isTabIndexNotNaN : 729 isTabIndexNotNaN) && 730 // the element and all of its ancestors must be visible 731 visible(element); 732 } 733 $.extend($.expr[":"], { 734 data: $.expr.createPseudo ? $.expr.createPseudo(function (dataName) { 735 return function (elem) { 736 return !!$.data(elem, dataName); 737 }; 738 }) : // support: jQuery <1.8 739 function (elem, i, match) { 740 return !!$.data(elem, match[3]); 741 }, 742 focusable: function (element) { 743 return focusable(element, !isNaN($.attr(element, "tabindex"))); 744 }, 745 tabbable: function (element) { 746 var tabIndex = $.attr(element, "tabindex"), 747 isTabIndexNaN = isNaN(tabIndex); 748 return (isTabIndexNaN || tabIndex >= 0) && focusable(element, !isTabIndexNaN); 749 } 750 }); 751}(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.