[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('node-menunav', function (Y, NAME) { 9 10 /** 11 * <p>The MenuNav Node Plugin makes it easy to transform existing list-based 12 * markup into traditional, drop down navigational menus that are both accessible 13 * and easy to customize, and only require a small set of dependencies.</p> 14 * 15 * 16 * <p>To use the MenuNav Node Plugin, simply pass a reference to the plugin to a 17 * Node instance's <code>plug</code> method.</p> 18 * 19 * <p> 20 * <code> 21 * <script type="text/javascript"> <br> 22 * <br> 23 * // Call the "use" method, passing in "node-menunav". This will <br> 24 * // load the script and CSS for the MenuNav Node Plugin and all of <br> 25 * // the required dependencies. <br> 26 * <br> 27 * YUI().use("node-menunav", function(Y) { <br> 28 * <br> 29 * // Use the "contentready" event to initialize the menu when <br> 30 * // the subtree of element representing the root menu <br> 31 * // (<div id="menu-1">) is ready to be scripted. <br> 32 * <br> 33 * Y.on("contentready", function () { <br> 34 * <br> 35 * // The scope of the callback will be a Node instance <br> 36 * // representing the root menu (<div id="menu-1">). <br> 37 * // Therefore, since "this" represents a Node instance, it <br> 38 * // is possible to just call "this.plug" passing in a <br> 39 * // reference to the MenuNav Node Plugin. <br> 40 * <br> 41 * this.plug(Y.Plugin.NodeMenuNav); <br> 42 * <br> 43 * }, "#menu-1"); <br> 44 * <br> 45 * }); <br> 46 * <br> 47 * </script> <br> 48 * </code> 49 * </p> 50 * 51 * <p>The MenuNav Node Plugin has several configuration properties that can be 52 * set via an object literal that is passed as a second argument to a Node 53 * instance's <code>plug</code> method. 54 * </p> 55 * 56 * <p> 57 * <code> 58 * <script type="text/javascript"> <br> 59 * <br> 60 * // Call the "use" method, passing in "node-menunav". This will <br> 61 * // load the script and CSS for the MenuNav Node Plugin and all of <br> 62 * // the required dependencies. <br> 63 * <br> 64 * YUI().use("node-menunav", function(Y) { <br> 65 * <br> 66 * // Use the "contentready" event to initialize the menu when <br> 67 * // the subtree of element representing the root menu <br> 68 * // (<div id="menu-1">) is ready to be scripted. <br> 69 * <br> 70 * Y.on("contentready", function () { <br> 71 * <br> 72 * // The scope of the callback will be a Node instance <br> 73 * // representing the root menu (<div id="menu-1">). <br> 74 * // Therefore, since "this" represents a Node instance, it <br> 75 * // is possible to just call "this.plug" passing in a <br> 76 * // reference to the MenuNav Node Plugin. <br> 77 * <br> 78 * this.plug(Y.Plugin.NodeMenuNav, { mouseOutHideDelay: 1000 }); 79 * <br><br> 80 * }, "#menu-1"); <br> 81 * <br> 82 * }); <br> 83 * <br> 84 * </script> <br> 85 * </code> 86 * </p> 87 * 88 DEPRECATED. The MenuNav Node Plugin has been deprecated as of YUI 3.9.0. This module will be removed from the library in a future version. If you require functionality similar to the one provided by this module, consider taking a look at the various modules in the YUI Gallery <http://yuilibrary.com/gallery/>. 89 90 @module node-menunav 91 @deprecated 3.9.0 92 */ 93 94 95 // Util shortcuts 96 97 var UA = Y.UA, 98 later = Y.later, 99 getClassName = Y.ClassNameManager.getClassName, 100 101 102 103 // Frequently used strings 104 105 MENU = "menu", 106 MENUITEM = "menuitem", 107 HIDDEN = "hidden", 108 PARENT_NODE = "parentNode", 109 CHILDREN = "children", 110 OFFSET_HEIGHT = "offsetHeight", 111 OFFSET_WIDTH = "offsetWidth", 112 PX = "px", 113 ID = "id", 114 PERIOD = ".", 115 HANDLED_MOUSEOUT = "handledMouseOut", 116 HANDLED_MOUSEOVER = "handledMouseOver", 117 ACTIVE = "active", 118 LABEL = "label", 119 LOWERCASE_A = "a", 120 MOUSEDOWN = "mousedown", 121 KEYDOWN = "keydown", 122 CLICK = "click", 123 EMPTY_STRING = "", 124 FIRST_OF_TYPE = "first-of-type", 125 ROLE = "role", 126 PRESENTATION = "presentation", 127 DESCENDANTS = "descendants", 128 UI = "UI", 129 ACTIVE_DESCENDANT = "activeDescendant", 130 USE_ARIA = "useARIA", 131 ARIA_HIDDEN = "aria-hidden", 132 CONTENT = "content", 133 HOST = "host", 134 ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change", 135 136 137 // Attribute keys 138 139 AUTO_SUBMENU_DISPLAY = "autoSubmenuDisplay", 140 MOUSEOUT_HIDE_DELAY = "mouseOutHideDelay", 141 142 143 // CSS class names 144 145 CSS_MENU = getClassName(MENU), 146 CSS_MENU_HIDDEN = getClassName(MENU, HIDDEN), 147 CSS_MENU_HORIZONTAL = getClassName(MENU, "horizontal"), 148 CSS_MENU_LABEL = getClassName(MENU, LABEL), 149 CSS_MENU_LABEL_ACTIVE = getClassName(MENU, LABEL, ACTIVE), 150 CSS_MENU_LABEL_MENUVISIBLE = getClassName(MENU, LABEL, (MENU + "visible")), 151 CSS_MENUITEM = getClassName(MENUITEM), 152 CSS_MENUITEM_ACTIVE = getClassName(MENUITEM, ACTIVE), 153 154 155 // CSS selectors 156 157 MENU_SELECTOR = PERIOD + CSS_MENU, 158 MENU_TOGGLE_SELECTOR = (PERIOD + getClassName(MENU, "toggle")), 159 MENU_CONTENT_SELECTOR = PERIOD + getClassName(MENU, CONTENT), 160 MENU_LABEL_SELECTOR = PERIOD + CSS_MENU_LABEL, 161 162 STANDARD_QUERY = ">" + MENU_CONTENT_SELECTOR + ">ul>li>a", 163 EXTENDED_QUERY = ">" + MENU_CONTENT_SELECTOR + ">ul>li>" + MENU_LABEL_SELECTOR + ">a:first-child"; 164 165 // Utility functions 166 167 168 var getPreviousSibling = function (node) { 169 170 var oPrevious = node.previous(), 171 oChildren; 172 173 if (!oPrevious) { 174 oChildren = node.get(PARENT_NODE).get(CHILDREN); 175 oPrevious = oChildren.item(oChildren.size() - 1); 176 } 177 178 179 return oPrevious; 180 181 }; 182 183 184 var getNextSibling = function (node) { 185 186 var oNext = node.next(); 187 188 if (!oNext) { 189 oNext = node.get(PARENT_NODE).get(CHILDREN).item(0); 190 } 191 192 return oNext; 193 194 }; 195 196 197 var isAnchor = function (node) { 198 199 var bReturnVal = false; 200 201 if (node) { 202 bReturnVal = node.get("nodeName").toLowerCase() === LOWERCASE_A; 203 } 204 205 return bReturnVal; 206 207 }; 208 209 210 var isMenuItem = function (node) { 211 212 return node.hasClass(CSS_MENUITEM); 213 214 }; 215 216 217 var isMenuLabel = function (node) { 218 219 return node.hasClass(CSS_MENU_LABEL); 220 221 }; 222 223 224 var isHorizontalMenu = function (menu) { 225 226 return menu.hasClass(CSS_MENU_HORIZONTAL); 227 228 }; 229 230 231 var hasVisibleSubmenu = function (menuLabel) { 232 233 return menuLabel.hasClass(CSS_MENU_LABEL_MENUVISIBLE); 234 235 }; 236 237 238 var getItemAnchor = function (node) { 239 240 return isAnchor(node) ? node : node.one(LOWERCASE_A); 241 242 }; 243 244 245 var getNodeWithClass = function (node, className, searchAncestors) { 246 247 var oItem; 248 249 if (node) { 250 251 if (node.hasClass(className)) { 252 oItem = node; 253 } 254 255 if (!oItem && searchAncestors) { 256 oItem = node.ancestor((PERIOD + className)); 257 } 258 259 } 260 261 return oItem; 262 263 }; 264 265 266 var getParentMenu = function (node) { 267 268 return node.ancestor(MENU_SELECTOR); 269 270 }; 271 272 273 var getMenu = function (node, searchAncestors) { 274 275 return getNodeWithClass(node, CSS_MENU, searchAncestors); 276 277 }; 278 279 280 var getMenuItem = function (node, searchAncestors) { 281 282 var oItem; 283 284 if (node) { 285 oItem = getNodeWithClass(node, CSS_MENUITEM, searchAncestors); 286 } 287 288 return oItem; 289 290 }; 291 292 293 var getMenuLabel = function (node, searchAncestors) { 294 295 var oItem; 296 297 if (node) { 298 299 if (searchAncestors) { 300 oItem = getNodeWithClass(node, CSS_MENU_LABEL, searchAncestors); 301 } 302 else { 303 oItem = getNodeWithClass(node, CSS_MENU_LABEL) || 304 node.one((PERIOD + CSS_MENU_LABEL)); 305 } 306 307 } 308 309 return oItem; 310 311 }; 312 313 314 var getItem = function (node, searchAncestors) { 315 316 var oItem; 317 318 if (node) { 319 oItem = getMenuItem(node, searchAncestors) || 320 getMenuLabel(node, searchAncestors); 321 } 322 323 return oItem; 324 325 }; 326 327 328 var getFirstItem = function (menu) { 329 330 return getItem(menu.one("li")); 331 332 }; 333 334 335 var getActiveClass = function (node) { 336 337 return isMenuItem(node) ? CSS_MENUITEM_ACTIVE : CSS_MENU_LABEL_ACTIVE; 338 339 }; 340 341 342 var handleMouseOverForNode = function (node, target) { 343 344 return node && !node[HANDLED_MOUSEOVER] && 345 (node.compareTo(target) || node.contains(target)); 346 347 }; 348 349 350 var handleMouseOutForNode = function (node, relatedTarget) { 351 352 return node && !node[HANDLED_MOUSEOUT] && 353 (!node.compareTo(relatedTarget) && !node.contains(relatedTarget)); 354 355 }; 356 357 /** 358 * The NodeMenuNav class is a plugin for a Node instance. The class is used via 359 * the <a href="Node.html#method_plug"><code>plug</code></a> method of Node and 360 * should not be instantiated directly. 361 * @namespace plugin 362 * @class NodeMenuNav 363 */ 364 var NodeMenuNav = function () { 365 366 NodeMenuNav.superclass.constructor.apply(this, arguments); 367 368 }; 369 370 NodeMenuNav.NAME = "nodeMenuNav"; 371 NodeMenuNav.NS = "menuNav"; 372 373 374 /** 375 * @property SHIM_TEMPLATE_TITLE 376 * @description String representing the value for the <code>title</code> 377 * attribute for the shim used to prevent <code><select></code> elements 378 * from poking through menus in IE 6. 379 * @default "Menu Stacking Shim" 380 * @type String 381 */ 382 NodeMenuNav.SHIM_TEMPLATE_TITLE = "Menu Stacking Shim"; 383 384 385 /** 386 * @property SHIM_TEMPLATE 387 * @description String representing the HTML used to create the 388 * <code><iframe></code> shim used to prevent 389 * <code><select></code> elements from poking through menus in IE 6. 390 * @default "<iframe frameborder="0" tabindex="-1" 391 * class="yui-shim" title="Menu Stacking Shim" 392 * src="javascript:false;"></iframe>" 393 * @type String 394 */ 395 396 // <iframe> shim notes: 397 // 398 // 1) Need to set the "frameBorder" property to 0 to suppress the default 399 // <iframe> border in IE. (Setting the CSS "border" property alone doesn't 400 // suppress it.) 401 // 402 // 2) The "src" attribute of the <iframe> is set to "javascript:false;" so 403 // that it won't load a page inside it, preventing the secure/nonsecure 404 // warning in IE when using HTTPS. 405 // 406 // 3) Since the role of the <iframe> shim is completely presentational, its 407 // "tabindex" attribute is set to "-1" and its title attribute is set to 408 // "Menu Stacking Shim". Both strategies help users of screen readers to 409 // avoid mistakenly interacting with the <iframe> shim. 410 411 NodeMenuNav.SHIM_TEMPLATE = '<iframe frameborder="0" tabindex="-1" class="' + 412 getClassName("shim") + 413 '" title="' + NodeMenuNav.SHIM_TEMPLATE_TITLE + 414 '" src="javascript:false;"></iframe>'; 415 416 417 NodeMenuNav.ATTRS = { 418 419 /** 420 * Boolean indicating if use of the WAI-ARIA Roles and States should be 421 * enabled for the menu. 422 * 423 * @attribute useARIA 424 * @readOnly 425 * @writeOnce 426 * @default true 427 * @type boolean 428 */ 429 useARIA: { 430 431 value: true, 432 writeOnce: true, 433 lazyAdd: false, 434 setter: function (value) { 435 436 var oMenu = this.get(HOST), 437 oMenuLabel, 438 oMenuToggle, 439 oSubmenu, 440 sID; 441 442 if (value) { 443 444 oMenu.set(ROLE, MENU); 445 446 oMenu.all("ul,li," + MENU_CONTENT_SELECTOR).set(ROLE, PRESENTATION); 447 448 oMenu.all((PERIOD + getClassName(MENUITEM, CONTENT))).set(ROLE, MENUITEM); 449 450 oMenu.all((PERIOD + CSS_MENU_LABEL)).each(function (node) { 451 452 oMenuLabel = node; 453 oMenuToggle = node.one(MENU_TOGGLE_SELECTOR); 454 455 if (oMenuToggle) { 456 oMenuToggle.set(ROLE, PRESENTATION); 457 oMenuLabel = oMenuToggle.previous(); 458 } 459 460 oMenuLabel.set(ROLE, MENUITEM); 461 oMenuLabel.set("aria-haspopup", true); 462 463 oSubmenu = node.next(); 464 465 if (oSubmenu) { 466 467 oSubmenu.set(ROLE, MENU); 468 469 oMenuLabel = oSubmenu.previous(); 470 oMenuToggle = oMenuLabel.one(MENU_TOGGLE_SELECTOR); 471 472 if (oMenuToggle) { 473 oMenuLabel = oMenuToggle; 474 } 475 476 sID = Y.stamp(oMenuLabel); 477 478 if (!oMenuLabel.get(ID)) { 479 oMenuLabel.set(ID, sID); 480 } 481 482 oSubmenu.set("aria-labelledby", sID); 483 oSubmenu.set(ARIA_HIDDEN, true); 484 485 } 486 487 }); 488 489 } 490 491 } 492 493 }, 494 495 496 /** 497 * Boolean indicating if submenus are automatically made visible when the 498 * user mouses over the menu's items. 499 * 500 * @attribute autoSubmenuDisplay 501 * @readOnly 502 * @writeOnce 503 * @default true 504 * @type boolean 505 */ 506 autoSubmenuDisplay: { 507 508 value: true, 509 writeOnce: true 510 511 }, 512 513 514 /** 515 * Number indicating the time (in milliseconds) that should expire before a 516 * submenu is made visible when the user mouses over the menu's label. 517 * 518 * @attribute submenuShowDelay 519 * @readOnly 520 * @writeOnce 521 * @default 250 522 * @type Number 523 */ 524 submenuShowDelay: { 525 526 value: 250, 527 writeOnce: true 528 529 }, 530 531 532 /** 533 * Number indicating the time (in milliseconds) that should expire before a 534 * submenu is hidden when the user mouses out of a menu label heading in the 535 * direction of a submenu. 536 * 537 * @attribute submenuHideDelay 538 * @readOnly 539 * @writeOnce 540 * @default 250 541 * @type Number 542 */ 543 submenuHideDelay: { 544 545 value: 250, 546 writeOnce: true 547 548 }, 549 550 551 /** 552 * Number indicating the time (in milliseconds) that should expire before a 553 * submenu is hidden when the user mouses out of it. 554 * 555 * @attribute mouseOutHideDelay 556 * @readOnly 557 * @writeOnce 558 * @default 750 559 * @type Number 560 */ 561 mouseOutHideDelay: { 562 563 value: 750, 564 writeOnce: true 565 566 } 567 568 }; 569 570 571 Y.extend(NodeMenuNav, Y.Plugin.Base, { 572 573 // Protected properties 574 575 /** 576 * @property _rootMenu 577 * @description Node instance representing the root menu in the menu. 578 * @default null 579 * @protected 580 * @type Node 581 */ 582 _rootMenu: null, 583 584 585 /** 586 * @property _activeItem 587 * @description Node instance representing the menu's active descendent: 588 * the menuitem or menu label the user is currently interacting with. 589 * @default null 590 * @protected 591 * @type Node 592 */ 593 _activeItem: null, 594 595 596 /** 597 * @property _activeMenu 598 * @description Node instance representing the menu that is the parent of 599 * the menu's active descendent. 600 * @default null 601 * @protected 602 * @type Node 603 */ 604 _activeMenu: null, 605 606 607 /** 608 * @property _hasFocus 609 * @description Boolean indicating if the menu has focus. 610 * @default false 611 * @protected 612 * @type Boolean 613 */ 614 _hasFocus: false, 615 616 617 // In gecko-based browsers a mouseover and mouseout event will fire even 618 // if a DOM element moves out from under the mouse without the user 619 // actually moving the mouse. This bug affects NodeMenuNav because the 620 // user can hit the Esc key to hide a menu, and if the mouse is over the 621 // menu when the user presses Esc, the _onMenuMouseOut handler will be 622 // called. To fix this bug the following flag (_blockMouseEvent) is used 623 // to block the code in the _onMenuMouseOut handler from executing. 624 625 /** 626 * @property _blockMouseEvent 627 * @description Boolean indicating whether or not to handle the 628 * "mouseover" event. 629 * @default false 630 * @protected 631 * @type Boolean 632 */ 633 _blockMouseEvent: false, 634 635 636 /** 637 * @property _currentMouseX 638 * @description Number representing the current x coordinate of the mouse 639 * inside the menu. 640 * @default 0 641 * @protected 642 * @type Number 643 */ 644 _currentMouseX: 0, 645 646 647 /** 648 * @property _movingToSubmenu 649 * @description Boolean indicating if the mouse is moving from a menu 650 * label to its corresponding submenu. 651 * @default false 652 * @protected 653 * @type Boolean 654 */ 655 _movingToSubmenu: false, 656 657 658 /** 659 * @property _showSubmenuTimer 660 * @description Timer used to show a submenu. 661 * @default null 662 * @protected 663 * @type Object 664 */ 665 _showSubmenuTimer: null, 666 667 668 /** 669 * @property _hideSubmenuTimer 670 * @description Timer used to hide a submenu. 671 * @default null 672 * @protected 673 * @type Object 674 */ 675 _hideSubmenuTimer: null, 676 677 678 /** 679 * @property _hideAllSubmenusTimer 680 * @description Timer used to hide a all submenus. 681 * @default null 682 * @protected 683 * @type Object 684 */ 685 _hideAllSubmenusTimer: null, 686 687 688 /** 689 * @property _firstItem 690 * @description Node instance representing the first item (menuitem or menu 691 * label) in the root menu of a menu. 692 * @default null 693 * @protected 694 * @type Node 695 */ 696 _firstItem: null, 697 698 699 // Public methods 700 701 702 initializer: function (config) { 703 704 var menuNav = this, 705 oRootMenu = this.get(HOST), 706 aHandlers = [], 707 oDoc; 708 709 Y.log("WARNING: Node-MenuNav is a deprecated module as of YUI 3.9.0. This module will be removed from a later version of the library.", "warn"); 710 711 if (oRootMenu) { 712 713 menuNav._rootMenu = oRootMenu; 714 715 oRootMenu.all("ul:first-child").addClass(FIRST_OF_TYPE); 716 717 // Hide all visible submenus 718 719 oRootMenu.all(MENU_SELECTOR).addClass(CSS_MENU_HIDDEN); 720 721 722 // Wire up all event handlers 723 724 aHandlers.push(oRootMenu.on("mouseover", menuNav._onMouseOver, menuNav)); 725 aHandlers.push(oRootMenu.on("mouseout", menuNav._onMouseOut, menuNav)); 726 aHandlers.push(oRootMenu.on("mousemove", menuNav._onMouseMove, menuNav)); 727 aHandlers.push(oRootMenu.on(MOUSEDOWN, menuNav._toggleSubmenuDisplay, menuNav)); 728 aHandlers.push(Y.on("key", menuNav._toggleSubmenuDisplay, oRootMenu, "down:13", menuNav)); 729 aHandlers.push(oRootMenu.on(CLICK, menuNav._toggleSubmenuDisplay, menuNav)); 730 aHandlers.push(oRootMenu.on("keypress", menuNav._onKeyPress, menuNav)); 731 aHandlers.push(oRootMenu.on(KEYDOWN, menuNav._onKeyDown, menuNav)); 732 733 oDoc = oRootMenu.get("ownerDocument"); 734 735 aHandlers.push(oDoc.on(MOUSEDOWN, menuNav._onDocMouseDown, menuNav)); 736 aHandlers.push(oDoc.on("focus", menuNav._onDocFocus, menuNav)); 737 738 this._eventHandlers = aHandlers; 739 740 menuNav._initFocusManager(); 741 742 } 743 744 745 }, 746 747 destructor: function () { 748 749 var aHandlers = this._eventHandlers; 750 751 if (aHandlers) { 752 753 Y.Array.each(aHandlers, function (handle) { 754 handle.detach(); 755 }); 756 757 this._eventHandlers = null; 758 759 } 760 761 this.get(HOST).unplug("focusManager"); 762 763 }, 764 765 766 767 // Protected methods 768 769 /** 770 * @method _isRoot 771 * @description Returns a boolean indicating if the specified menu is the 772 * root menu in the menu. 773 * @protected 774 * @param {Node} menu Node instance representing a menu. 775 * @return {Boolean} Boolean indicating if the specified menu is the root 776 * menu in the menu. 777 */ 778 _isRoot: function (menu) { 779 780 return this._rootMenu.compareTo(menu); 781 782 }, 783 784 785 /** 786 * @method _getTopmostSubmenu 787 * @description Returns the topmost submenu of a submenu hierarchy. 788 * @protected 789 * @param {Node} menu Node instance representing a menu. 790 * @return {Node} Node instance representing a menu. 791 */ 792 _getTopmostSubmenu: function (menu) { 793 794 var menuNav = this, 795 oMenu = getParentMenu(menu), 796 returnVal; 797 798 799 if (!oMenu) { 800 returnVal = menu; 801 } 802 else if (menuNav._isRoot(oMenu)) { 803 returnVal = menu; 804 } 805 else { 806 returnVal = menuNav._getTopmostSubmenu(oMenu); 807 } 808 809 return returnVal; 810 811 }, 812 813 814 /** 815 * @method _clearActiveItem 816 * @description Clears the menu's active descendent. 817 * @protected 818 */ 819 _clearActiveItem: function () { 820 821 var menuNav = this, 822 oActiveItem = menuNav._activeItem; 823 824 if (oActiveItem) { 825 oActiveItem.removeClass(getActiveClass(oActiveItem)); 826 } 827 828 menuNav._activeItem = null; 829 830 }, 831 832 833 /** 834 * @method _setActiveItem 835 * @description Sets the specified menuitem or menu label as the menu's 836 * active descendent. 837 * @protected 838 * @param {Node} item Node instance representing a menuitem or menu label. 839 */ 840 _setActiveItem: function (item) { 841 842 var menuNav = this; 843 844 if (item) { 845 846 menuNav._clearActiveItem(); 847 848 item.addClass(getActiveClass(item)); 849 850 menuNav._activeItem = item; 851 852 } 853 854 }, 855 856 857 /** 858 * @method _focusItem 859 * @description Focuses the specified menuitem or menu label. 860 * @protected 861 * @param {Node} item Node instance representing a menuitem or menu label. 862 */ 863 _focusItem: function (item) { 864 865 var menuNav = this, 866 oMenu, 867 oItem; 868 869 if (item && menuNav._hasFocus) { 870 871 oMenu = getParentMenu(item); 872 oItem = getItemAnchor(item); 873 874 if (oMenu && !oMenu.compareTo(menuNav._activeMenu)) { 875 menuNav._activeMenu = oMenu; 876 menuNav._initFocusManager(); 877 } 878 879 menuNav._focusManager.focus(oItem); 880 881 } 882 883 }, 884 885 886 /** 887 * @method _showMenu 888 * @description Shows the specified menu. 889 * @protected 890 * @param {Node} menu Node instance representing a menu. 891 */ 892 _showMenu: function (menu) { 893 894 var oParentMenu = getParentMenu(menu), 895 oLI = menu.get(PARENT_NODE), 896 aXY = oLI.getXY(); 897 898 899 if (this.get(USE_ARIA)) { 900 menu.set(ARIA_HIDDEN, false); 901 } 902 903 904 if (isHorizontalMenu(oParentMenu)) { 905 aXY[1] = aXY[1] + oLI.get(OFFSET_HEIGHT); 906 } 907 else { 908 aXY[0] = aXY[0] + oLI.get(OFFSET_WIDTH); 909 } 910 911 menu.setXY(aXY); 912 913 if (UA.ie && UA.ie < 8) { 914 915 if (UA.ie === 6 && !menu.hasIFrameShim) { 916 917 menu.appendChild(Y.Node.create(NodeMenuNav.SHIM_TEMPLATE)); 918 menu.hasIFrameShim = true; 919 920 } 921 922 // Clear previous values for height and width 923 924 menu.setStyles({ height: EMPTY_STRING, width: EMPTY_STRING }); 925 926 // Set the width and height of the menu's bounding box - this is 927 // necessary for IE 6 so that the CSS for the <iframe> shim can 928 // simply set the <iframe>'s width and height to 100% to ensure 929 // that dimensions of an <iframe> shim are always sync'd to the 930 // that of its parent menu. Specifying a width and height also 931 // helps when positioning decorator elements (for creating effects 932 // like rounded corners) inside a menu's bounding box in IE 7. 933 934 menu.setStyles({ 935 height: (menu.get(OFFSET_HEIGHT) + PX), 936 width: (menu.get(OFFSET_WIDTH) + PX) }); 937 938 } 939 940 menu.previous().addClass(CSS_MENU_LABEL_MENUVISIBLE); 941 menu.removeClass(CSS_MENU_HIDDEN); 942 943 }, 944 945 946 /** 947 * @method _hideMenu 948 * @description Hides the specified menu. 949 * @protected 950 * @param {Node} menu Node instance representing a menu. 951 * @param {Boolean} activateAndFocusLabel Boolean indicating if the label 952 * for the specified 953 * menu should be focused and set as active. 954 */ 955 _hideMenu: function (menu, activateAndFocusLabel) { 956 957 var menuNav = this, 958 oLabel = menu.previous(), 959 oActiveItem; 960 961 oLabel.removeClass(CSS_MENU_LABEL_MENUVISIBLE); 962 963 964 if (activateAndFocusLabel) { 965 menuNav._focusItem(oLabel); 966 menuNav._setActiveItem(oLabel); 967 } 968 969 oActiveItem = menu.one((PERIOD + CSS_MENUITEM_ACTIVE)); 970 971 if (oActiveItem) { 972 oActiveItem.removeClass(CSS_MENUITEM_ACTIVE); 973 } 974 975 // Clear the values for top and left that were set by the call to 976 // "setXY" when the menu was shown so that the hidden position 977 // specified in the core CSS file will take affect. 978 979 menu.setStyles({ left: EMPTY_STRING, top: EMPTY_STRING }); 980 981 menu.addClass(CSS_MENU_HIDDEN); 982 983 if (menuNav.get(USE_ARIA)) { 984 menu.set(ARIA_HIDDEN, true); 985 } 986 987 }, 988 989 990 /** 991 * @method _hideAllSubmenus 992 * @description Hides all submenus of the specified menu. 993 * @protected 994 * @param {Node} menu Node instance representing a menu. 995 */ 996 _hideAllSubmenus: function (menu) { 997 998 var menuNav = this; 999 1000 menu.all(MENU_SELECTOR).each(Y.bind(function (submenuNode) { 1001 1002 menuNav._hideMenu(submenuNode); 1003 1004 }, menuNav)); 1005 1006 }, 1007 1008 1009 /** 1010 * @method _cancelShowSubmenuTimer 1011 * @description Cancels the timer used to show a submenu. 1012 * @protected 1013 */ 1014 _cancelShowSubmenuTimer: function () { 1015 1016 var menuNav = this, 1017 oShowSubmenuTimer = menuNav._showSubmenuTimer; 1018 1019 if (oShowSubmenuTimer) { 1020 oShowSubmenuTimer.cancel(); 1021 menuNav._showSubmenuTimer = null; 1022 } 1023 1024 }, 1025 1026 1027 /** 1028 * @method _cancelHideSubmenuTimer 1029 * @description Cancels the timer used to hide a submenu. 1030 * @protected 1031 */ 1032 _cancelHideSubmenuTimer: function () { 1033 1034 var menuNav = this, 1035 oHideSubmenuTimer = menuNav._hideSubmenuTimer; 1036 1037 1038 if (oHideSubmenuTimer) { 1039 oHideSubmenuTimer.cancel(); 1040 menuNav._hideSubmenuTimer = null; 1041 } 1042 1043 }, 1044 1045 1046 /** 1047 * @method _initFocusManager 1048 * @description Initializes and updates the Focus Manager so that is is 1049 * always managing descendants of the active menu. 1050 * @protected 1051 */ 1052 _initFocusManager: function () { 1053 1054 var menuNav = this, 1055 oRootMenu = menuNav._rootMenu, 1056 oMenu = menuNav._activeMenu || oRootMenu, 1057 sSelectorBase = 1058 menuNav._isRoot(oMenu) ? EMPTY_STRING : ("#" + oMenu.get("id")), 1059 oFocusManager = menuNav._focusManager, 1060 sKeysVal, 1061 sDescendantSelector, 1062 sQuery; 1063 1064 if (isHorizontalMenu(oMenu)) { 1065 1066 sDescendantSelector = sSelectorBase + STANDARD_QUERY + "," + 1067 sSelectorBase + EXTENDED_QUERY; 1068 1069 sKeysVal = { next: "down:39", previous: "down:37" }; 1070 1071 } 1072 else { 1073 1074 sDescendantSelector = sSelectorBase + STANDARD_QUERY; 1075 sKeysVal = { next: "down:40", previous: "down:38" }; 1076 1077 } 1078 1079 1080 if (!oFocusManager) { 1081 1082 oRootMenu.plug(Y.Plugin.NodeFocusManager, { 1083 descendants: sDescendantSelector, 1084 keys: sKeysVal, 1085 circular: true 1086 }); 1087 1088 oFocusManager = oRootMenu.focusManager; 1089 1090 sQuery = "#" + oRootMenu.get("id") + MENU_SELECTOR + " a," + 1091 MENU_TOGGLE_SELECTOR; 1092 1093 oRootMenu.all(sQuery).set("tabIndex", -1); 1094 1095 oFocusManager.on(ACTIVE_DESCENDANT_CHANGE, 1096 this._onActiveDescendantChange, oFocusManager, this); 1097 1098 oFocusManager.after(ACTIVE_DESCENDANT_CHANGE, 1099 this._afterActiveDescendantChange, oFocusManager, this); 1100 1101 menuNav._focusManager = oFocusManager; 1102 1103 } 1104 else { 1105 1106 oFocusManager.set(ACTIVE_DESCENDANT, -1); 1107 oFocusManager.set(DESCENDANTS, sDescendantSelector); 1108 oFocusManager.set("keys", sKeysVal); 1109 1110 } 1111 1112 }, 1113 1114 1115 // Event handlers for discrete pieces of pieces of the menu 1116 1117 1118 /** 1119 * @method _onActiveDescendantChange 1120 * @description "activeDescendantChange" event handler for menu's 1121 * Focus Manager. 1122 * @protected 1123 * @param {Object} event Object representing the Attribute change event. 1124 * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance. 1125 */ 1126 _onActiveDescendantChange: function (event, menuNav) { 1127 1128 if (event.src === UI && menuNav._activeMenu && 1129 !menuNav._movingToSubmenu) { 1130 1131 menuNav._hideAllSubmenus(menuNav._activeMenu); 1132 1133 } 1134 1135 }, 1136 1137 1138 /** 1139 * @method _afterActiveDescendantChange 1140 * @description "activeDescendantChange" event handler for menu's 1141 * Focus Manager. 1142 * @protected 1143 * @param {Object} event Object representing the Attribute change event. 1144 * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance. 1145 */ 1146 _afterActiveDescendantChange: function (event, menuNav) { 1147 1148 var oItem; 1149 1150 if (event.src === UI) { 1151 oItem = getItem(this.get(DESCENDANTS).item(event.newVal), true); 1152 menuNav._setActiveItem(oItem); 1153 } 1154 1155 }, 1156 1157 1158 /** 1159 * @method _onDocFocus 1160 * @description "focus" event handler for the owner document of the MenuNav. 1161 * @protected 1162 * @param {Object} event Object representing the DOM event. 1163 */ 1164 _onDocFocus: function (event) { 1165 1166 var menuNav = this, 1167 oActiveItem = menuNav._activeItem, 1168 oTarget = event.target, 1169 oMenu; 1170 1171 1172 if (menuNav._rootMenu.contains(oTarget)) { // The menu has focus 1173 1174 if (menuNav._hasFocus) { 1175 1176 oMenu = getParentMenu(oTarget); 1177 1178 // If the element that was focused is a descendant of the 1179 // root menu, but is in a submenu not currently being 1180 // managed by the Focus Manager, update the Focus Manager so 1181 // that it is now managing the submenu that is the parent of 1182 // the element that was focused. 1183 1184 if (!menuNav._activeMenu.compareTo(oMenu)) { 1185 1186 menuNav._activeMenu = oMenu; 1187 menuNav._initFocusManager(); 1188 menuNav._focusManager.set(ACTIVE_DESCENDANT, oTarget); 1189 menuNav._setActiveItem(getItem(oTarget, true)); 1190 1191 } 1192 1193 } 1194 else { // Initial focus 1195 1196 // First time the menu has been focused, need to setup focused 1197 // state and established active active descendant 1198 1199 menuNav._hasFocus = true; 1200 1201 oActiveItem = getItem(oTarget, true); 1202 1203 if (oActiveItem) { 1204 menuNav._setActiveItem(oActiveItem); 1205 } 1206 1207 } 1208 1209 } 1210 else { // The menu has lost focus 1211 1212 menuNav._clearActiveItem(); 1213 1214 menuNav._cancelShowSubmenuTimer(); 1215 menuNav._hideAllSubmenus(menuNav._rootMenu); 1216 1217 menuNav._activeMenu = menuNav._rootMenu; 1218 menuNav._initFocusManager(); 1219 1220 menuNav._focusManager.set(ACTIVE_DESCENDANT, 0); 1221 1222 menuNav._hasFocus = false; 1223 1224 } 1225 1226 }, 1227 1228 1229 /** 1230 * @method _onMenuMouseOver 1231 * @description "mouseover" event handler for a menu. 1232 * @protected 1233 * @param {Node} menu Node instance representing a menu. 1234 * @param {Object} event Object representing the DOM event. 1235 */ 1236 _onMenuMouseOver: function (menu, event) { 1237 1238 var menuNav = this, 1239 oHideAllSubmenusTimer = menuNav._hideAllSubmenusTimer; 1240 1241 if (oHideAllSubmenusTimer) { 1242 oHideAllSubmenusTimer.cancel(); 1243 menuNav._hideAllSubmenusTimer = null; 1244 } 1245 1246 menuNav._cancelHideSubmenuTimer(); 1247 1248 // Need to update the FocusManager in advance of focus a new 1249 // Menu in order to avoid the FocusManager thinking that 1250 // it has lost focus 1251 1252 if (menu && !menu.compareTo(menuNav._activeMenu)) { 1253 menuNav._activeMenu = menu; 1254 1255 if (menuNav._hasFocus) { 1256 menuNav._initFocusManager(); 1257 } 1258 1259 } 1260 1261 if (menuNav._movingToSubmenu && isHorizontalMenu(menu)) { 1262 menuNav._movingToSubmenu = false; 1263 } 1264 1265 }, 1266 1267 1268 /** 1269 * @method _hideAndFocusLabel 1270 * @description Hides all of the submenus of the root menu and focuses the 1271 * label of the topmost submenu 1272 * @protected 1273 */ 1274 _hideAndFocusLabel: function () { 1275 1276 var menuNav = this, 1277 oActiveMenu = menuNav._activeMenu, 1278 oSubmenu; 1279 1280 menuNav._hideAllSubmenus(menuNav._rootMenu); 1281 1282 if (oActiveMenu) { 1283 1284 // Focus the label element for the topmost submenu 1285 oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu); 1286 menuNav._focusItem(oSubmenu.previous()); 1287 1288 } 1289 1290 }, 1291 1292 1293 /** 1294 * @method _onMenuMouseOut 1295 * @description "mouseout" event handler for a menu. 1296 * @protected 1297 * @param {Node} menu Node instance representing a menu. 1298 * @param {Object} event Object representing the DOM event. 1299 */ 1300 _onMenuMouseOut: function (menu, event) { 1301 1302 var menuNav = this, 1303 oActiveMenu = menuNav._activeMenu, 1304 oRelatedTarget = event.relatedTarget, 1305 oActiveItem = menuNav._activeItem, 1306 oParentMenu, 1307 oMenu; 1308 1309 1310 if (oActiveMenu && !oActiveMenu.contains(oRelatedTarget)) { 1311 1312 oParentMenu = getParentMenu(oActiveMenu); 1313 1314 1315 if (oParentMenu && !oParentMenu.contains(oRelatedTarget)) { 1316 1317 if (menuNav.get(MOUSEOUT_HIDE_DELAY) > 0) { 1318 1319 menuNav._cancelShowSubmenuTimer(); 1320 1321 menuNav._hideAllSubmenusTimer = 1322 1323 later(menuNav.get(MOUSEOUT_HIDE_DELAY), 1324 menuNav, menuNav._hideAndFocusLabel); 1325 1326 } 1327 1328 } 1329 else { 1330 1331 if (oActiveItem) { 1332 1333 oMenu = getParentMenu(oActiveItem); 1334 1335 if (!menuNav._isRoot(oMenu)) { 1336 menuNav._focusItem(oMenu.previous()); 1337 } 1338 1339 } 1340 1341 } 1342 1343 } 1344 1345 }, 1346 1347 1348 /** 1349 * @method _onMenuLabelMouseOver 1350 * @description "mouseover" event handler for a menu label. 1351 * @protected 1352 * @param {Node} menuLabel Node instance representing a menu label. 1353 * @param {Object} event Object representing the DOM event. 1354 */ 1355 _onMenuLabelMouseOver: function (menuLabel, event) { 1356 1357 var menuNav = this, 1358 oActiveMenu = menuNav._activeMenu, 1359 bIsRoot = menuNav._isRoot(oActiveMenu), 1360 bUseAutoSubmenuDisplay = 1361 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot), 1362 submenuShowDelay = menuNav.get("submenuShowDelay"), 1363 oSubmenu; 1364 1365 1366 var showSubmenu = function (delay) { 1367 1368 menuNav._cancelHideSubmenuTimer(); 1369 menuNav._cancelShowSubmenuTimer(); 1370 1371 if (!hasVisibleSubmenu(menuLabel)) { 1372 1373 oSubmenu = menuLabel.next(); 1374 1375 if (oSubmenu) { 1376 menuNav._hideAllSubmenus(oActiveMenu); 1377 menuNav._showSubmenuTimer = later(delay, menuNav, menuNav._showMenu, oSubmenu); 1378 } 1379 1380 } 1381 1382 }; 1383 1384 1385 menuNav._focusItem(menuLabel); 1386 menuNav._setActiveItem(menuLabel); 1387 1388 1389 if (bUseAutoSubmenuDisplay) { 1390 1391 if (menuNav._movingToSubmenu) { 1392 1393 // If the user is moving diagonally from a submenu to 1394 // another submenu and they then stop and pause on a 1395 // menu label for an amount of time equal to the amount of 1396 // time defined for the display of a submenu then show the 1397 // submenu immediately. 1398 // http://yuilibrary.com/projects/yui3/ticket/2528316 1399 1400 //Y.message("Pause path"); 1401 1402 menuNav._hoverTimer = later(submenuShowDelay, menuNav, function () { 1403 showSubmenu(0); 1404 }); 1405 1406 } 1407 else { 1408 showSubmenu(submenuShowDelay); 1409 } 1410 1411 } 1412 1413 }, 1414 1415 1416 /** 1417 * @method _onMenuLabelMouseOut 1418 * @description "mouseout" event handler for a menu label. 1419 * @protected 1420 * @param {Node} menuLabel Node instance representing a menu label. 1421 * @param {Object} event Object representing the DOM event. 1422 */ 1423 _onMenuLabelMouseOut: function (menuLabel, event) { 1424 1425 var menuNav = this, 1426 bIsRoot = menuNav._isRoot(menuNav._activeMenu), 1427 bUseAutoSubmenuDisplay = 1428 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot), 1429 1430 oRelatedTarget = event.relatedTarget, 1431 oSubmenu = menuLabel.next(), 1432 hoverTimer = menuNav._hoverTimer; 1433 1434 if (hoverTimer) { 1435 hoverTimer.cancel(); 1436 } 1437 1438 menuNav._clearActiveItem(); 1439 1440 if (bUseAutoSubmenuDisplay) { 1441 1442 if (menuNav._movingToSubmenu && 1443 !menuNav._showSubmenuTimer && oSubmenu) { 1444 1445 // If the mouse is moving diagonally toward the submenu and 1446 // another submenu isn't in the process of being displayed 1447 // (via a timer), then hide the submenu via a timer to give 1448 // the user some time to reach the submenu. 1449 1450 menuNav._hideSubmenuTimer = 1451 later(menuNav.get("submenuHideDelay"), menuNav, 1452 menuNav._hideMenu, oSubmenu); 1453 1454 } 1455 else if (!menuNav._movingToSubmenu && oSubmenu && (!oRelatedTarget || 1456 (oRelatedTarget && 1457 !oSubmenu.contains(oRelatedTarget) && 1458 !oRelatedTarget.compareTo(oSubmenu)))) { 1459 1460 // If the mouse is not moving toward the submenu, cancel any 1461 // submenus that might be in the process of being displayed 1462 // (via a timer) and hide this submenu immediately. 1463 1464 menuNav._cancelShowSubmenuTimer(); 1465 1466 menuNav._hideMenu(oSubmenu); 1467 1468 } 1469 1470 } 1471 1472 }, 1473 1474 1475 /** 1476 * @method _onMenuItemMouseOver 1477 * @description "mouseover" event handler for a menuitem. 1478 * @protected 1479 * @param {Node} menuItem Node instance representing a menuitem. 1480 * @param {Object} event Object representing the DOM event. 1481 */ 1482 _onMenuItemMouseOver: function (menuItem, event) { 1483 1484 var menuNav = this, 1485 oActiveMenu = menuNav._activeMenu, 1486 bIsRoot = menuNav._isRoot(oActiveMenu), 1487 bUseAutoSubmenuDisplay = 1488 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot); 1489 1490 1491 menuNav._focusItem(menuItem); 1492 menuNav._setActiveItem(menuItem); 1493 1494 1495 if (bUseAutoSubmenuDisplay && !menuNav._movingToSubmenu) { 1496 1497 menuNav._hideAllSubmenus(oActiveMenu); 1498 1499 } 1500 1501 }, 1502 1503 1504 /** 1505 * @method _onMenuItemMouseOut 1506 * @description "mouseout" event handler for a menuitem. 1507 * @protected 1508 * @param {Node} menuItem Node instance representing a menuitem. 1509 * @param {Object} event Object representing the DOM event. 1510 */ 1511 _onMenuItemMouseOut: function (menuItem, event) { 1512 1513 this._clearActiveItem(); 1514 1515 }, 1516 1517 1518 /** 1519 * @method _onVerticalMenuKeyDown 1520 * @description "keydown" event handler for vertical menus. 1521 * @protected 1522 * @param {Object} event Object representing the DOM event. 1523 */ 1524 _onVerticalMenuKeyDown: function (event) { 1525 1526 var menuNav = this, 1527 oActiveMenu = menuNav._activeMenu, 1528 oRootMenu = menuNav._rootMenu, 1529 oTarget = event.target, 1530 bPreventDefault = false, 1531 nKeyCode = event.keyCode, 1532 oSubmenu, 1533 oParentMenu, 1534 oLI, 1535 oItem; 1536 1537 1538 switch (nKeyCode) { 1539 1540 case 37: // left arrow 1541 1542 oParentMenu = getParentMenu(oActiveMenu); 1543 1544 if (oParentMenu && isHorizontalMenu(oParentMenu)) { 1545 1546 menuNav._hideMenu(oActiveMenu); 1547 oLI = getPreviousSibling(oActiveMenu.get(PARENT_NODE)); 1548 oItem = getItem(oLI); 1549 1550 if (oItem) { 1551 1552 if (isMenuLabel(oItem)) { // Menu label 1553 1554 oSubmenu = oItem.next(); 1555 1556 1557 if (oSubmenu) { 1558 1559 menuNav._showMenu(oSubmenu); 1560 menuNav._focusItem(getFirstItem(oSubmenu)); 1561 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1562 1563 } 1564 else { 1565 1566 menuNav._focusItem(oItem); 1567 menuNav._setActiveItem(oItem); 1568 1569 } 1570 1571 } 1572 else { // MenuItem 1573 1574 menuNav._focusItem(oItem); 1575 menuNav._setActiveItem(oItem); 1576 1577 } 1578 1579 } 1580 1581 } 1582 else if (!menuNav._isRoot(oActiveMenu)) { 1583 menuNav._hideMenu(oActiveMenu, true); 1584 } 1585 1586 1587 bPreventDefault = true; 1588 1589 break; 1590 1591 case 39: // right arrow 1592 1593 if (isMenuLabel(oTarget)) { 1594 1595 oSubmenu = oTarget.next(); 1596 1597 if (oSubmenu) { 1598 1599 menuNav._showMenu(oSubmenu); 1600 menuNav._focusItem(getFirstItem(oSubmenu)); 1601 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1602 1603 } 1604 1605 } 1606 else if (isHorizontalMenu(oRootMenu)) { 1607 1608 oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu); 1609 oLI = getNextSibling(oSubmenu.get(PARENT_NODE)); 1610 oItem = getItem(oLI); 1611 1612 menuNav._hideAllSubmenus(oRootMenu); 1613 1614 if (oItem) { 1615 1616 if (isMenuLabel(oItem)) { // Menu label 1617 1618 oSubmenu = oItem.next(); 1619 1620 if (oSubmenu) { 1621 1622 menuNav._showMenu(oSubmenu); 1623 menuNav._focusItem(getFirstItem(oSubmenu)); 1624 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1625 1626 } 1627 else { 1628 1629 menuNav._focusItem(oItem); 1630 menuNav._setActiveItem(oItem); 1631 1632 } 1633 1634 } 1635 else { // MenuItem 1636 1637 menuNav._focusItem(oItem); 1638 menuNav._setActiveItem(oItem); 1639 1640 } 1641 1642 } 1643 1644 } 1645 1646 bPreventDefault = true; 1647 1648 break; 1649 1650 } 1651 1652 1653 if (bPreventDefault) { 1654 1655 // Prevent the browser from scrolling the window 1656 1657 event.preventDefault(); 1658 1659 } 1660 1661 }, 1662 1663 1664 /** 1665 * @method _onHorizontalMenuKeyDown 1666 * @description "keydown" event handler for horizontal menus. 1667 * @protected 1668 * @param {Object} event Object representing the DOM event. 1669 */ 1670 _onHorizontalMenuKeyDown: function (event) { 1671 1672 var menuNav = this, 1673 oActiveMenu = menuNav._activeMenu, 1674 oTarget = event.target, 1675 oFocusedItem = getItem(oTarget, true), 1676 bPreventDefault = false, 1677 nKeyCode = event.keyCode, 1678 oSubmenu; 1679 1680 1681 if (nKeyCode === 40) { 1682 1683 menuNav._hideAllSubmenus(oActiveMenu); 1684 1685 if (isMenuLabel(oFocusedItem)) { 1686 1687 oSubmenu = oFocusedItem.next(); 1688 1689 if (oSubmenu) { 1690 1691 menuNav._showMenu(oSubmenu); 1692 menuNav._focusItem(getFirstItem(oSubmenu)); 1693 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1694 1695 } 1696 1697 bPreventDefault = true; 1698 1699 } 1700 1701 } 1702 1703 1704 if (bPreventDefault) { 1705 1706 // Prevent the browser from scrolling the window 1707 1708 event.preventDefault(); 1709 1710 } 1711 1712 }, 1713 1714 1715 // Generic DOM Event handlers 1716 1717 1718 /** 1719 * @method _onMouseMove 1720 * @description "mousemove" event handler for the menu. 1721 * @protected 1722 * @param {Object} event Object representing the DOM event. 1723 */ 1724 _onMouseMove: function (event) { 1725 1726 var menuNav = this; 1727 1728 // Using a timer to set the value of the "_currentMouseX" property 1729 // helps improve the reliability of the calculation used to set the 1730 // value of the "_movingToSubmenu" property - especially in Opera. 1731 1732 later(10, menuNav, function () { 1733 1734 menuNav._currentMouseX = event.pageX; 1735 1736 }); 1737 1738 }, 1739 1740 1741 /** 1742 * @method _onMouseOver 1743 * @description "mouseover" event handler for the menu. 1744 * @protected 1745 * @param {Object} event Object representing the DOM event. 1746 */ 1747 _onMouseOver: function (event) { 1748 1749 var menuNav = this, 1750 oTarget, 1751 oMenu, 1752 oMenuLabel, 1753 oParentMenu, 1754 oMenuItem; 1755 1756 1757 if (menuNav._blockMouseEvent) { 1758 menuNav._blockMouseEvent = false; 1759 } 1760 else { 1761 1762 oTarget = event.target; 1763 oMenu = getMenu(oTarget, true); 1764 oMenuLabel = getMenuLabel(oTarget, true); 1765 oMenuItem = getMenuItem(oTarget, true); 1766 1767 1768 if (handleMouseOverForNode(oMenu, oTarget)) { 1769 1770 menuNav._onMenuMouseOver(oMenu, event); 1771 1772 oMenu[HANDLED_MOUSEOVER] = true; 1773 oMenu[HANDLED_MOUSEOUT] = false; 1774 1775 oParentMenu = getParentMenu(oMenu); 1776 1777 if (oParentMenu) { 1778 1779 oParentMenu[HANDLED_MOUSEOUT] = true; 1780 oParentMenu[HANDLED_MOUSEOVER] = false; 1781 1782 } 1783 1784 } 1785 1786 if (handleMouseOverForNode(oMenuLabel, oTarget)) { 1787 1788 menuNav._onMenuLabelMouseOver(oMenuLabel, event); 1789 1790 oMenuLabel[HANDLED_MOUSEOVER] = true; 1791 oMenuLabel[HANDLED_MOUSEOUT] = false; 1792 1793 } 1794 1795 if (handleMouseOverForNode(oMenuItem, oTarget)) { 1796 1797 menuNav._onMenuItemMouseOver(oMenuItem, event); 1798 1799 oMenuItem[HANDLED_MOUSEOVER] = true; 1800 oMenuItem[HANDLED_MOUSEOUT] = false; 1801 1802 } 1803 1804 } 1805 1806 }, 1807 1808 1809 /** 1810 * @method _onMouseOut 1811 * @description "mouseout" event handler for the menu. 1812 * @protected 1813 * @param {Object} event Object representing the DOM event. 1814 */ 1815 _onMouseOut: function (event) { 1816 1817 var menuNav = this, 1818 oActiveMenu = menuNav._activeMenu, 1819 bMovingToSubmenu = false, 1820 oTarget, 1821 oRelatedTarget, 1822 oMenu, 1823 oMenuLabel, 1824 oSubmenu, 1825 oMenuItem; 1826 1827 1828 menuNav._movingToSubmenu = 1829 (oActiveMenu && !isHorizontalMenu(oActiveMenu) && 1830 ((event.pageX - 5) > menuNav._currentMouseX)); 1831 1832 oTarget = event.target; 1833 oRelatedTarget = event.relatedTarget; 1834 oMenu = getMenu(oTarget, true); 1835 oMenuLabel = getMenuLabel(oTarget, true); 1836 oMenuItem = getMenuItem(oTarget, true); 1837 1838 1839 if (handleMouseOutForNode(oMenuLabel, oRelatedTarget)) { 1840 1841 menuNav._onMenuLabelMouseOut(oMenuLabel, event); 1842 1843 oMenuLabel[HANDLED_MOUSEOUT] = true; 1844 oMenuLabel[HANDLED_MOUSEOVER] = false; 1845 1846 } 1847 1848 if (handleMouseOutForNode(oMenuItem, oRelatedTarget)) { 1849 1850 menuNav._onMenuItemMouseOut(oMenuItem, event); 1851 1852 oMenuItem[HANDLED_MOUSEOUT] = true; 1853 oMenuItem[HANDLED_MOUSEOVER] = false; 1854 1855 } 1856 1857 1858 if (oMenuLabel) { 1859 1860 oSubmenu = oMenuLabel.next(); 1861 1862 if (oSubmenu && oRelatedTarget && 1863 (oRelatedTarget.compareTo(oSubmenu) || 1864 oSubmenu.contains(oRelatedTarget))) { 1865 1866 bMovingToSubmenu = true; 1867 1868 } 1869 1870 } 1871 1872 1873 if (handleMouseOutForNode(oMenu, oRelatedTarget) || bMovingToSubmenu) { 1874 1875 menuNav._onMenuMouseOut(oMenu, event); 1876 1877 oMenu[HANDLED_MOUSEOUT] = true; 1878 oMenu[HANDLED_MOUSEOVER] = false; 1879 1880 } 1881 1882 }, 1883 1884 1885 /** 1886 * @method _toggleSubmenuDisplay 1887 * @description "mousedown," "keydown," and "click" event handler for the 1888 * menu used to toggle the display of a submenu. 1889 * @protected 1890 * @param {Object} event Object representing the DOM event. 1891 */ 1892 _toggleSubmenuDisplay: function (event) { 1893 1894 var menuNav = this, 1895 oTarget = event.target, 1896 oMenuLabel = getMenuLabel(oTarget, true), 1897 sType = event.type, 1898 oAnchor, 1899 oSubmenu, 1900 sHref, 1901 nHashPos, 1902 nLen, 1903 sId; 1904 1905 1906 if (oMenuLabel) { 1907 1908 oAnchor = isAnchor(oTarget) ? oTarget : oTarget.ancestor(isAnchor); 1909 1910 1911 if (oAnchor) { 1912 1913 // Need to pass "2" as a second argument to "getAttribute" for 1914 // IE otherwise IE will return a fully qualified URL for the 1915 // value of the "href" attribute. 1916 // http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx 1917 1918 sHref = oAnchor.getAttribute("href", 2); 1919 nHashPos = sHref.indexOf("#"); 1920 nLen = sHref.length; 1921 1922 if (nHashPos === 0 && nLen > 1) { 1923 1924 sId = sHref.substr(1, nLen); 1925 oSubmenu = oMenuLabel.next(); 1926 1927 if (oSubmenu && (oSubmenu.get(ID) === sId)) { 1928 1929 if (sType === MOUSEDOWN || sType === KEYDOWN) { 1930 1931 if ((UA.opera || UA.gecko || UA.ie) && sType === KEYDOWN && !menuNav._preventClickHandle) { 1932 1933 // Prevent the browser from following the URL of 1934 // the anchor element 1935 1936 menuNav._preventClickHandle = menuNav._rootMenu.on("click", function (event) { 1937 1938 event.preventDefault(); 1939 1940 menuNav._preventClickHandle.detach(); 1941 menuNav._preventClickHandle = null; 1942 1943 }); 1944 1945 } 1946 1947 if (sType == MOUSEDOWN) { 1948 1949 // Prevent the target from getting focused by 1950 // default, since the element to be focused will 1951 // be determined by weather or not the submenu 1952 // is visible. 1953 event.preventDefault(); 1954 1955 // FocusManager will attempt to focus any 1956 // descendant that is the target of the mousedown 1957 // event. Since we want to explicitly control 1958 // where focus is going, we need to call 1959 // "stopImmediatePropagation" to stop the 1960 // FocusManager from doing its thing. 1961 event.stopImmediatePropagation(); 1962 1963 // The "_focusItem" method relies on the 1964 // "_hasFocus" property being set to true. The 1965 // "_hasFocus" property is normally set via a 1966 // "focus" event listener, but since we've 1967 // blocked focus from happening, we need to set 1968 // this property manually. 1969 menuNav._hasFocus = true; 1970 1971 } 1972 1973 1974 if (menuNav._isRoot(getParentMenu(oTarget))) { // Event target is a submenu label in the root menu 1975 1976 // Menu label toggle functionality 1977 1978 if (hasVisibleSubmenu(oMenuLabel)) { 1979 1980 menuNav._hideMenu(oSubmenu); 1981 menuNav._focusItem(oMenuLabel); 1982 menuNav._setActiveItem(oMenuLabel); 1983 1984 } 1985 else { 1986 1987 menuNav._hideAllSubmenus(menuNav._rootMenu); 1988 menuNav._showMenu(oSubmenu); 1989 1990 menuNav._focusItem(getFirstItem(oSubmenu)); 1991 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1992 1993 } 1994 1995 } 1996 else { // Event target is a submenu label within a submenu 1997 1998 if (menuNav._activeItem == oMenuLabel) { 1999 2000 menuNav._showMenu(oSubmenu); 2001 menuNav._focusItem(getFirstItem(oSubmenu)); 2002 menuNav._setActiveItem(getFirstItem(oSubmenu)); 2003 2004 } 2005 else { 2006 2007 if (!oMenuLabel._clickHandle) { 2008 2009 oMenuLabel._clickHandle = oMenuLabel.on("click", function () { 2010 2011 menuNav._hideAllSubmenus(menuNav._rootMenu); 2012 2013 menuNav._hasFocus = false; 2014 menuNav._clearActiveItem(); 2015 2016 2017 oMenuLabel._clickHandle.detach(); 2018 2019 oMenuLabel._clickHandle = null; 2020 2021 }); 2022 2023 } 2024 2025 } 2026 2027 } 2028 2029 } 2030 2031 2032 if (sType === CLICK) { 2033 2034 // Prevent the browser from following the URL of 2035 // the anchor element 2036 2037 event.preventDefault(); 2038 2039 } 2040 2041 } 2042 2043 } 2044 2045 2046 } 2047 2048 } 2049 2050 }, 2051 2052 2053 /** 2054 * @method _onKeyPress 2055 * @description "keypress" event handler for the menu. 2056 * @protected 2057 * @param {Object} event Object representing the DOM event. 2058 */ 2059 _onKeyPress: function (event) { 2060 2061 switch (event.keyCode) { 2062 2063 case 37: // left arrow 2064 case 38: // up arrow 2065 case 39: // right arrow 2066 case 40: // down arrow 2067 2068 // Prevent the browser from scrolling the window 2069 2070 event.preventDefault(); 2071 2072 break; 2073 2074 } 2075 2076 }, 2077 2078 2079 /** 2080 * @method _onKeyDown 2081 * @description "keydown" event handler for the menu. 2082 * @protected 2083 * @param {Object} event Object representing the DOM event. 2084 */ 2085 _onKeyDown: function (event) { 2086 2087 var menuNav = this, 2088 oActiveItem = menuNav._activeItem, 2089 oTarget = event.target, 2090 oActiveMenu = getParentMenu(oTarget), 2091 oSubmenu; 2092 2093 if (oActiveMenu) { 2094 2095 menuNav._activeMenu = oActiveMenu; 2096 2097 if (isHorizontalMenu(oActiveMenu)) { 2098 menuNav._onHorizontalMenuKeyDown(event); 2099 } 2100 else { 2101 menuNav._onVerticalMenuKeyDown(event); 2102 } 2103 2104 2105 if (event.keyCode === 27) { 2106 2107 if (!menuNav._isRoot(oActiveMenu)) { 2108 2109 if (UA.opera) { 2110 later(0, menuNav, function () { 2111 menuNav._hideMenu(oActiveMenu, true); 2112 }); 2113 } 2114 else { 2115 menuNav._hideMenu(oActiveMenu, true); 2116 } 2117 2118 event.stopPropagation(); 2119 menuNav._blockMouseEvent = UA.gecko ? true : false; 2120 2121 } 2122 else if (oActiveItem) { 2123 2124 if (isMenuLabel(oActiveItem) && 2125 hasVisibleSubmenu(oActiveItem)) { 2126 2127 oSubmenu = oActiveItem.next(); 2128 2129 if (oSubmenu) { 2130 menuNav._hideMenu(oSubmenu); 2131 } 2132 2133 } 2134 else { 2135 2136 menuNav._focusManager.blur(); 2137 2138 // This is necessary for Webkit since blurring the 2139 // active menuitem won't result in the document 2140 // gaining focus, meaning the that _onDocFocus 2141 // listener won't clear the active menuitem. 2142 2143 menuNav._clearActiveItem(); 2144 2145 menuNav._hasFocus = false; 2146 2147 } 2148 2149 } 2150 2151 } 2152 2153 } 2154 2155 }, 2156 2157 /** 2158 * @method _onDocMouseDown 2159 * @description "mousedown" event handler for the owner document of 2160 * the menu. 2161 * @protected 2162 * @param {Object} event Object representing the DOM event. 2163 */ 2164 _onDocMouseDown: function (event) { 2165 2166 var menuNav = this, 2167 oRoot = menuNav._rootMenu, 2168 oTarget = event.target; 2169 2170 2171 if (!(oRoot.compareTo(oTarget) || oRoot.contains(oTarget))) { 2172 2173 menuNav._hideAllSubmenus(oRoot); 2174 2175 // Document doesn't receive focus in Webkit when the user mouses 2176 // down on it, so the "_hasFocus" property won't get set to the 2177 // correct value. The following line corrects the problem. 2178 2179 if (UA.webkit) { 2180 menuNav._hasFocus = false; 2181 menuNav._clearActiveItem(); 2182 } 2183 2184 } 2185 2186 } 2187 2188 }); 2189 2190 2191 Y.namespace('Plugin'); 2192 2193 Y.Plugin.NodeMenuNav = NodeMenuNav; 2194 2195 2196 }, '3.17.2', {"requires": ["node", "classnamemanager", "plugin", "node-focusmanager"], "skinnable": true});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |