[ 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 710 if (oRootMenu) { 711 712 menuNav._rootMenu = oRootMenu; 713 714 oRootMenu.all("ul:first-child").addClass(FIRST_OF_TYPE); 715 716 // Hide all visible submenus 717 718 oRootMenu.all(MENU_SELECTOR).addClass(CSS_MENU_HIDDEN); 719 720 721 // Wire up all event handlers 722 723 aHandlers.push(oRootMenu.on("mouseover", menuNav._onMouseOver, menuNav)); 724 aHandlers.push(oRootMenu.on("mouseout", menuNav._onMouseOut, menuNav)); 725 aHandlers.push(oRootMenu.on("mousemove", menuNav._onMouseMove, menuNav)); 726 aHandlers.push(oRootMenu.on(MOUSEDOWN, menuNav._toggleSubmenuDisplay, menuNav)); 727 aHandlers.push(Y.on("key", menuNav._toggleSubmenuDisplay, oRootMenu, "down:13", menuNav)); 728 aHandlers.push(oRootMenu.on(CLICK, menuNav._toggleSubmenuDisplay, menuNav)); 729 aHandlers.push(oRootMenu.on("keypress", menuNav._onKeyPress, menuNav)); 730 aHandlers.push(oRootMenu.on(KEYDOWN, menuNav._onKeyDown, menuNav)); 731 732 oDoc = oRootMenu.get("ownerDocument"); 733 734 aHandlers.push(oDoc.on(MOUSEDOWN, menuNav._onDocMouseDown, menuNav)); 735 aHandlers.push(oDoc.on("focus", menuNav._onDocFocus, menuNav)); 736 737 this._eventHandlers = aHandlers; 738 739 menuNav._initFocusManager(); 740 741 } 742 743 744 }, 745 746 destructor: function () { 747 748 var aHandlers = this._eventHandlers; 749 750 if (aHandlers) { 751 752 Y.Array.each(aHandlers, function (handle) { 753 handle.detach(); 754 }); 755 756 this._eventHandlers = null; 757 758 } 759 760 this.get(HOST).unplug("focusManager"); 761 762 }, 763 764 765 766 // Protected methods 767 768 /** 769 * @method _isRoot 770 * @description Returns a boolean indicating if the specified menu is the 771 * root menu in the menu. 772 * @protected 773 * @param {Node} menu Node instance representing a menu. 774 * @return {Boolean} Boolean indicating if the specified menu is the root 775 * menu in the menu. 776 */ 777 _isRoot: function (menu) { 778 779 return this._rootMenu.compareTo(menu); 780 781 }, 782 783 784 /** 785 * @method _getTopmostSubmenu 786 * @description Returns the topmost submenu of a submenu hierarchy. 787 * @protected 788 * @param {Node} menu Node instance representing a menu. 789 * @return {Node} Node instance representing a menu. 790 */ 791 _getTopmostSubmenu: function (menu) { 792 793 var menuNav = this, 794 oMenu = getParentMenu(menu), 795 returnVal; 796 797 798 if (!oMenu) { 799 returnVal = menu; 800 } 801 else if (menuNav._isRoot(oMenu)) { 802 returnVal = menu; 803 } 804 else { 805 returnVal = menuNav._getTopmostSubmenu(oMenu); 806 } 807 808 return returnVal; 809 810 }, 811 812 813 /** 814 * @method _clearActiveItem 815 * @description Clears the menu's active descendent. 816 * @protected 817 */ 818 _clearActiveItem: function () { 819 820 var menuNav = this, 821 oActiveItem = menuNav._activeItem; 822 823 if (oActiveItem) { 824 oActiveItem.removeClass(getActiveClass(oActiveItem)); 825 } 826 827 menuNav._activeItem = null; 828 829 }, 830 831 832 /** 833 * @method _setActiveItem 834 * @description Sets the specified menuitem or menu label as the menu's 835 * active descendent. 836 * @protected 837 * @param {Node} item Node instance representing a menuitem or menu label. 838 */ 839 _setActiveItem: function (item) { 840 841 var menuNav = this; 842 843 if (item) { 844 845 menuNav._clearActiveItem(); 846 847 item.addClass(getActiveClass(item)); 848 849 menuNav._activeItem = item; 850 851 } 852 853 }, 854 855 856 /** 857 * @method _focusItem 858 * @description Focuses the specified menuitem or menu label. 859 * @protected 860 * @param {Node} item Node instance representing a menuitem or menu label. 861 */ 862 _focusItem: function (item) { 863 864 var menuNav = this, 865 oMenu, 866 oItem; 867 868 if (item && menuNav._hasFocus) { 869 870 oMenu = getParentMenu(item); 871 oItem = getItemAnchor(item); 872 873 if (oMenu && !oMenu.compareTo(menuNav._activeMenu)) { 874 menuNav._activeMenu = oMenu; 875 menuNav._initFocusManager(); 876 } 877 878 menuNav._focusManager.focus(oItem); 879 880 } 881 882 }, 883 884 885 /** 886 * @method _showMenu 887 * @description Shows the specified menu. 888 * @protected 889 * @param {Node} menu Node instance representing a menu. 890 */ 891 _showMenu: function (menu) { 892 893 var oParentMenu = getParentMenu(menu), 894 oLI = menu.get(PARENT_NODE), 895 aXY = oLI.getXY(); 896 897 898 if (this.get(USE_ARIA)) { 899 menu.set(ARIA_HIDDEN, false); 900 } 901 902 903 if (isHorizontalMenu(oParentMenu)) { 904 aXY[1] = aXY[1] + oLI.get(OFFSET_HEIGHT); 905 } 906 else { 907 aXY[0] = aXY[0] + oLI.get(OFFSET_WIDTH); 908 } 909 910 menu.setXY(aXY); 911 912 if (UA.ie && UA.ie < 8) { 913 914 if (UA.ie === 6 && !menu.hasIFrameShim) { 915 916 menu.appendChild(Y.Node.create(NodeMenuNav.SHIM_TEMPLATE)); 917 menu.hasIFrameShim = true; 918 919 } 920 921 // Clear previous values for height and width 922 923 menu.setStyles({ height: EMPTY_STRING, width: EMPTY_STRING }); 924 925 // Set the width and height of the menu's bounding box - this is 926 // necessary for IE 6 so that the CSS for the <iframe> shim can 927 // simply set the <iframe>'s width and height to 100% to ensure 928 // that dimensions of an <iframe> shim are always sync'd to the 929 // that of its parent menu. Specifying a width and height also 930 // helps when positioning decorator elements (for creating effects 931 // like rounded corners) inside a menu's bounding box in IE 7. 932 933 menu.setStyles({ 934 height: (menu.get(OFFSET_HEIGHT) + PX), 935 width: (menu.get(OFFSET_WIDTH) + PX) }); 936 937 } 938 939 menu.previous().addClass(CSS_MENU_LABEL_MENUVISIBLE); 940 menu.removeClass(CSS_MENU_HIDDEN); 941 942 }, 943 944 945 /** 946 * @method _hideMenu 947 * @description Hides the specified menu. 948 * @protected 949 * @param {Node} menu Node instance representing a menu. 950 * @param {Boolean} activateAndFocusLabel Boolean indicating if the label 951 * for the specified 952 * menu should be focused and set as active. 953 */ 954 _hideMenu: function (menu, activateAndFocusLabel) { 955 956 var menuNav = this, 957 oLabel = menu.previous(), 958 oActiveItem; 959 960 oLabel.removeClass(CSS_MENU_LABEL_MENUVISIBLE); 961 962 963 if (activateAndFocusLabel) { 964 menuNav._focusItem(oLabel); 965 menuNav._setActiveItem(oLabel); 966 } 967 968 oActiveItem = menu.one((PERIOD + CSS_MENUITEM_ACTIVE)); 969 970 if (oActiveItem) { 971 oActiveItem.removeClass(CSS_MENUITEM_ACTIVE); 972 } 973 974 // Clear the values for top and left that were set by the call to 975 // "setXY" when the menu was shown so that the hidden position 976 // specified in the core CSS file will take affect. 977 978 menu.setStyles({ left: EMPTY_STRING, top: EMPTY_STRING }); 979 980 menu.addClass(CSS_MENU_HIDDEN); 981 982 if (menuNav.get(USE_ARIA)) { 983 menu.set(ARIA_HIDDEN, true); 984 } 985 986 }, 987 988 989 /** 990 * @method _hideAllSubmenus 991 * @description Hides all submenus of the specified menu. 992 * @protected 993 * @param {Node} menu Node instance representing a menu. 994 */ 995 _hideAllSubmenus: function (menu) { 996 997 var menuNav = this; 998 999 menu.all(MENU_SELECTOR).each(Y.bind(function (submenuNode) { 1000 1001 menuNav._hideMenu(submenuNode); 1002 1003 }, menuNav)); 1004 1005 }, 1006 1007 1008 /** 1009 * @method _cancelShowSubmenuTimer 1010 * @description Cancels the timer used to show a submenu. 1011 * @protected 1012 */ 1013 _cancelShowSubmenuTimer: function () { 1014 1015 var menuNav = this, 1016 oShowSubmenuTimer = menuNav._showSubmenuTimer; 1017 1018 if (oShowSubmenuTimer) { 1019 oShowSubmenuTimer.cancel(); 1020 menuNav._showSubmenuTimer = null; 1021 } 1022 1023 }, 1024 1025 1026 /** 1027 * @method _cancelHideSubmenuTimer 1028 * @description Cancels the timer used to hide a submenu. 1029 * @protected 1030 */ 1031 _cancelHideSubmenuTimer: function () { 1032 1033 var menuNav = this, 1034 oHideSubmenuTimer = menuNav._hideSubmenuTimer; 1035 1036 1037 if (oHideSubmenuTimer) { 1038 oHideSubmenuTimer.cancel(); 1039 menuNav._hideSubmenuTimer = null; 1040 } 1041 1042 }, 1043 1044 1045 /** 1046 * @method _initFocusManager 1047 * @description Initializes and updates the Focus Manager so that is is 1048 * always managing descendants of the active menu. 1049 * @protected 1050 */ 1051 _initFocusManager: function () { 1052 1053 var menuNav = this, 1054 oRootMenu = menuNav._rootMenu, 1055 oMenu = menuNav._activeMenu || oRootMenu, 1056 sSelectorBase = 1057 menuNav._isRoot(oMenu) ? EMPTY_STRING : ("#" + oMenu.get("id")), 1058 oFocusManager = menuNav._focusManager, 1059 sKeysVal, 1060 sDescendantSelector, 1061 sQuery; 1062 1063 if (isHorizontalMenu(oMenu)) { 1064 1065 sDescendantSelector = sSelectorBase + STANDARD_QUERY + "," + 1066 sSelectorBase + EXTENDED_QUERY; 1067 1068 sKeysVal = { next: "down:39", previous: "down:37" }; 1069 1070 } 1071 else { 1072 1073 sDescendantSelector = sSelectorBase + STANDARD_QUERY; 1074 sKeysVal = { next: "down:40", previous: "down:38" }; 1075 1076 } 1077 1078 1079 if (!oFocusManager) { 1080 1081 oRootMenu.plug(Y.Plugin.NodeFocusManager, { 1082 descendants: sDescendantSelector, 1083 keys: sKeysVal, 1084 circular: true 1085 }); 1086 1087 oFocusManager = oRootMenu.focusManager; 1088 1089 sQuery = "#" + oRootMenu.get("id") + MENU_SELECTOR + " a," + 1090 MENU_TOGGLE_SELECTOR; 1091 1092 oRootMenu.all(sQuery).set("tabIndex", -1); 1093 1094 oFocusManager.on(ACTIVE_DESCENDANT_CHANGE, 1095 this._onActiveDescendantChange, oFocusManager, this); 1096 1097 oFocusManager.after(ACTIVE_DESCENDANT_CHANGE, 1098 this._afterActiveDescendantChange, oFocusManager, this); 1099 1100 menuNav._focusManager = oFocusManager; 1101 1102 } 1103 else { 1104 1105 oFocusManager.set(ACTIVE_DESCENDANT, -1); 1106 oFocusManager.set(DESCENDANTS, sDescendantSelector); 1107 oFocusManager.set("keys", sKeysVal); 1108 1109 } 1110 1111 }, 1112 1113 1114 // Event handlers for discrete pieces of pieces of the menu 1115 1116 1117 /** 1118 * @method _onActiveDescendantChange 1119 * @description "activeDescendantChange" event handler for menu's 1120 * Focus Manager. 1121 * @protected 1122 * @param {Object} event Object representing the Attribute change event. 1123 * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance. 1124 */ 1125 _onActiveDescendantChange: function (event, menuNav) { 1126 1127 if (event.src === UI && menuNav._activeMenu && 1128 !menuNav._movingToSubmenu) { 1129 1130 menuNav._hideAllSubmenus(menuNav._activeMenu); 1131 1132 } 1133 1134 }, 1135 1136 1137 /** 1138 * @method _afterActiveDescendantChange 1139 * @description "activeDescendantChange" event handler for menu's 1140 * Focus Manager. 1141 * @protected 1142 * @param {Object} event Object representing the Attribute change event. 1143 * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance. 1144 */ 1145 _afterActiveDescendantChange: function (event, menuNav) { 1146 1147 var oItem; 1148 1149 if (event.src === UI) { 1150 oItem = getItem(this.get(DESCENDANTS).item(event.newVal), true); 1151 menuNav._setActiveItem(oItem); 1152 } 1153 1154 }, 1155 1156 1157 /** 1158 * @method _onDocFocus 1159 * @description "focus" event handler for the owner document of the MenuNav. 1160 * @protected 1161 * @param {Object} event Object representing the DOM event. 1162 */ 1163 _onDocFocus: function (event) { 1164 1165 var menuNav = this, 1166 oActiveItem = menuNav._activeItem, 1167 oTarget = event.target, 1168 oMenu; 1169 1170 1171 if (menuNav._rootMenu.contains(oTarget)) { // The menu has focus 1172 1173 if (menuNav._hasFocus) { 1174 1175 oMenu = getParentMenu(oTarget); 1176 1177 // If the element that was focused is a descendant of the 1178 // root menu, but is in a submenu not currently being 1179 // managed by the Focus Manager, update the Focus Manager so 1180 // that it is now managing the submenu that is the parent of 1181 // the element that was focused. 1182 1183 if (!menuNav._activeMenu.compareTo(oMenu)) { 1184 1185 menuNav._activeMenu = oMenu; 1186 menuNav._initFocusManager(); 1187 menuNav._focusManager.set(ACTIVE_DESCENDANT, oTarget); 1188 menuNav._setActiveItem(getItem(oTarget, true)); 1189 1190 } 1191 1192 } 1193 else { // Initial focus 1194 1195 // First time the menu has been focused, need to setup focused 1196 // state and established active active descendant 1197 1198 menuNav._hasFocus = true; 1199 1200 oActiveItem = getItem(oTarget, true); 1201 1202 if (oActiveItem) { 1203 menuNav._setActiveItem(oActiveItem); 1204 } 1205 1206 } 1207 1208 } 1209 else { // The menu has lost focus 1210 1211 menuNav._clearActiveItem(); 1212 1213 menuNav._cancelShowSubmenuTimer(); 1214 menuNav._hideAllSubmenus(menuNav._rootMenu); 1215 1216 menuNav._activeMenu = menuNav._rootMenu; 1217 menuNav._initFocusManager(); 1218 1219 menuNav._focusManager.set(ACTIVE_DESCENDANT, 0); 1220 1221 menuNav._hasFocus = false; 1222 1223 } 1224 1225 }, 1226 1227 1228 /** 1229 * @method _onMenuMouseOver 1230 * @description "mouseover" event handler for a menu. 1231 * @protected 1232 * @param {Node} menu Node instance representing a menu. 1233 * @param {Object} event Object representing the DOM event. 1234 */ 1235 _onMenuMouseOver: function (menu, event) { 1236 1237 var menuNav = this, 1238 oHideAllSubmenusTimer = menuNav._hideAllSubmenusTimer; 1239 1240 if (oHideAllSubmenusTimer) { 1241 oHideAllSubmenusTimer.cancel(); 1242 menuNav._hideAllSubmenusTimer = null; 1243 } 1244 1245 menuNav._cancelHideSubmenuTimer(); 1246 1247 // Need to update the FocusManager in advance of focus a new 1248 // Menu in order to avoid the FocusManager thinking that 1249 // it has lost focus 1250 1251 if (menu && !menu.compareTo(menuNav._activeMenu)) { 1252 menuNav._activeMenu = menu; 1253 1254 if (menuNav._hasFocus) { 1255 menuNav._initFocusManager(); 1256 } 1257 1258 } 1259 1260 if (menuNav._movingToSubmenu && isHorizontalMenu(menu)) { 1261 menuNav._movingToSubmenu = false; 1262 } 1263 1264 }, 1265 1266 1267 /** 1268 * @method _hideAndFocusLabel 1269 * @description Hides all of the submenus of the root menu and focuses the 1270 * label of the topmost submenu 1271 * @protected 1272 */ 1273 _hideAndFocusLabel: function () { 1274 1275 var menuNav = this, 1276 oActiveMenu = menuNav._activeMenu, 1277 oSubmenu; 1278 1279 menuNav._hideAllSubmenus(menuNav._rootMenu); 1280 1281 if (oActiveMenu) { 1282 1283 // Focus the label element for the topmost submenu 1284 oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu); 1285 menuNav._focusItem(oSubmenu.previous()); 1286 1287 } 1288 1289 }, 1290 1291 1292 /** 1293 * @method _onMenuMouseOut 1294 * @description "mouseout" event handler for a menu. 1295 * @protected 1296 * @param {Node} menu Node instance representing a menu. 1297 * @param {Object} event Object representing the DOM event. 1298 */ 1299 _onMenuMouseOut: function (menu, event) { 1300 1301 var menuNav = this, 1302 oActiveMenu = menuNav._activeMenu, 1303 oRelatedTarget = event.relatedTarget, 1304 oActiveItem = menuNav._activeItem, 1305 oParentMenu, 1306 oMenu; 1307 1308 1309 if (oActiveMenu && !oActiveMenu.contains(oRelatedTarget)) { 1310 1311 oParentMenu = getParentMenu(oActiveMenu); 1312 1313 1314 if (oParentMenu && !oParentMenu.contains(oRelatedTarget)) { 1315 1316 if (menuNav.get(MOUSEOUT_HIDE_DELAY) > 0) { 1317 1318 menuNav._cancelShowSubmenuTimer(); 1319 1320 menuNav._hideAllSubmenusTimer = 1321 1322 later(menuNav.get(MOUSEOUT_HIDE_DELAY), 1323 menuNav, menuNav._hideAndFocusLabel); 1324 1325 } 1326 1327 } 1328 else { 1329 1330 if (oActiveItem) { 1331 1332 oMenu = getParentMenu(oActiveItem); 1333 1334 if (!menuNav._isRoot(oMenu)) { 1335 menuNav._focusItem(oMenu.previous()); 1336 } 1337 1338 } 1339 1340 } 1341 1342 } 1343 1344 }, 1345 1346 1347 /** 1348 * @method _onMenuLabelMouseOver 1349 * @description "mouseover" event handler for a menu label. 1350 * @protected 1351 * @param {Node} menuLabel Node instance representing a menu label. 1352 * @param {Object} event Object representing the DOM event. 1353 */ 1354 _onMenuLabelMouseOver: function (menuLabel, event) { 1355 1356 var menuNav = this, 1357 oActiveMenu = menuNav._activeMenu, 1358 bIsRoot = menuNav._isRoot(oActiveMenu), 1359 bUseAutoSubmenuDisplay = 1360 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot), 1361 submenuShowDelay = menuNav.get("submenuShowDelay"), 1362 oSubmenu; 1363 1364 1365 var showSubmenu = function (delay) { 1366 1367 menuNav._cancelHideSubmenuTimer(); 1368 menuNav._cancelShowSubmenuTimer(); 1369 1370 if (!hasVisibleSubmenu(menuLabel)) { 1371 1372 oSubmenu = menuLabel.next(); 1373 1374 if (oSubmenu) { 1375 menuNav._hideAllSubmenus(oActiveMenu); 1376 menuNav._showSubmenuTimer = later(delay, menuNav, menuNav._showMenu, oSubmenu); 1377 } 1378 1379 } 1380 1381 }; 1382 1383 1384 menuNav._focusItem(menuLabel); 1385 menuNav._setActiveItem(menuLabel); 1386 1387 1388 if (bUseAutoSubmenuDisplay) { 1389 1390 if (menuNav._movingToSubmenu) { 1391 1392 // If the user is moving diagonally from a submenu to 1393 // another submenu and they then stop and pause on a 1394 // menu label for an amount of time equal to the amount of 1395 // time defined for the display of a submenu then show the 1396 // submenu immediately. 1397 // http://yuilibrary.com/projects/yui3/ticket/2528316 1398 1399 //Y.message("Pause path"); 1400 1401 menuNav._hoverTimer = later(submenuShowDelay, menuNav, function () { 1402 showSubmenu(0); 1403 }); 1404 1405 } 1406 else { 1407 showSubmenu(submenuShowDelay); 1408 } 1409 1410 } 1411 1412 }, 1413 1414 1415 /** 1416 * @method _onMenuLabelMouseOut 1417 * @description "mouseout" event handler for a menu label. 1418 * @protected 1419 * @param {Node} menuLabel Node instance representing a menu label. 1420 * @param {Object} event Object representing the DOM event. 1421 */ 1422 _onMenuLabelMouseOut: function (menuLabel, event) { 1423 1424 var menuNav = this, 1425 bIsRoot = menuNav._isRoot(menuNav._activeMenu), 1426 bUseAutoSubmenuDisplay = 1427 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot), 1428 1429 oRelatedTarget = event.relatedTarget, 1430 oSubmenu = menuLabel.next(), 1431 hoverTimer = menuNav._hoverTimer; 1432 1433 if (hoverTimer) { 1434 hoverTimer.cancel(); 1435 } 1436 1437 menuNav._clearActiveItem(); 1438 1439 if (bUseAutoSubmenuDisplay) { 1440 1441 if (menuNav._movingToSubmenu && 1442 !menuNav._showSubmenuTimer && oSubmenu) { 1443 1444 // If the mouse is moving diagonally toward the submenu and 1445 // another submenu isn't in the process of being displayed 1446 // (via a timer), then hide the submenu via a timer to give 1447 // the user some time to reach the submenu. 1448 1449 menuNav._hideSubmenuTimer = 1450 later(menuNav.get("submenuHideDelay"), menuNav, 1451 menuNav._hideMenu, oSubmenu); 1452 1453 } 1454 else if (!menuNav._movingToSubmenu && oSubmenu && (!oRelatedTarget || 1455 (oRelatedTarget && 1456 !oSubmenu.contains(oRelatedTarget) && 1457 !oRelatedTarget.compareTo(oSubmenu)))) { 1458 1459 // If the mouse is not moving toward the submenu, cancel any 1460 // submenus that might be in the process of being displayed 1461 // (via a timer) and hide this submenu immediately. 1462 1463 menuNav._cancelShowSubmenuTimer(); 1464 1465 menuNav._hideMenu(oSubmenu); 1466 1467 } 1468 1469 } 1470 1471 }, 1472 1473 1474 /** 1475 * @method _onMenuItemMouseOver 1476 * @description "mouseover" event handler for a menuitem. 1477 * @protected 1478 * @param {Node} menuItem Node instance representing a menuitem. 1479 * @param {Object} event Object representing the DOM event. 1480 */ 1481 _onMenuItemMouseOver: function (menuItem, event) { 1482 1483 var menuNav = this, 1484 oActiveMenu = menuNav._activeMenu, 1485 bIsRoot = menuNav._isRoot(oActiveMenu), 1486 bUseAutoSubmenuDisplay = 1487 (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot); 1488 1489 1490 menuNav._focusItem(menuItem); 1491 menuNav._setActiveItem(menuItem); 1492 1493 1494 if (bUseAutoSubmenuDisplay && !menuNav._movingToSubmenu) { 1495 1496 menuNav._hideAllSubmenus(oActiveMenu); 1497 1498 } 1499 1500 }, 1501 1502 1503 /** 1504 * @method _onMenuItemMouseOut 1505 * @description "mouseout" event handler for a menuitem. 1506 * @protected 1507 * @param {Node} menuItem Node instance representing a menuitem. 1508 * @param {Object} event Object representing the DOM event. 1509 */ 1510 _onMenuItemMouseOut: function (menuItem, event) { 1511 1512 this._clearActiveItem(); 1513 1514 }, 1515 1516 1517 /** 1518 * @method _onVerticalMenuKeyDown 1519 * @description "keydown" event handler for vertical menus. 1520 * @protected 1521 * @param {Object} event Object representing the DOM event. 1522 */ 1523 _onVerticalMenuKeyDown: function (event) { 1524 1525 var menuNav = this, 1526 oActiveMenu = menuNav._activeMenu, 1527 oRootMenu = menuNav._rootMenu, 1528 oTarget = event.target, 1529 bPreventDefault = false, 1530 nKeyCode = event.keyCode, 1531 oSubmenu, 1532 oParentMenu, 1533 oLI, 1534 oItem; 1535 1536 1537 switch (nKeyCode) { 1538 1539 case 37: // left arrow 1540 1541 oParentMenu = getParentMenu(oActiveMenu); 1542 1543 if (oParentMenu && isHorizontalMenu(oParentMenu)) { 1544 1545 menuNav._hideMenu(oActiveMenu); 1546 oLI = getPreviousSibling(oActiveMenu.get(PARENT_NODE)); 1547 oItem = getItem(oLI); 1548 1549 if (oItem) { 1550 1551 if (isMenuLabel(oItem)) { // Menu label 1552 1553 oSubmenu = oItem.next(); 1554 1555 1556 if (oSubmenu) { 1557 1558 menuNav._showMenu(oSubmenu); 1559 menuNav._focusItem(getFirstItem(oSubmenu)); 1560 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1561 1562 } 1563 else { 1564 1565 menuNav._focusItem(oItem); 1566 menuNav._setActiveItem(oItem); 1567 1568 } 1569 1570 } 1571 else { // MenuItem 1572 1573 menuNav._focusItem(oItem); 1574 menuNav._setActiveItem(oItem); 1575 1576 } 1577 1578 } 1579 1580 } 1581 else if (!menuNav._isRoot(oActiveMenu)) { 1582 menuNav._hideMenu(oActiveMenu, true); 1583 } 1584 1585 1586 bPreventDefault = true; 1587 1588 break; 1589 1590 case 39: // right arrow 1591 1592 if (isMenuLabel(oTarget)) { 1593 1594 oSubmenu = oTarget.next(); 1595 1596 if (oSubmenu) { 1597 1598 menuNav._showMenu(oSubmenu); 1599 menuNav._focusItem(getFirstItem(oSubmenu)); 1600 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1601 1602 } 1603 1604 } 1605 else if (isHorizontalMenu(oRootMenu)) { 1606 1607 oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu); 1608 oLI = getNextSibling(oSubmenu.get(PARENT_NODE)); 1609 oItem = getItem(oLI); 1610 1611 menuNav._hideAllSubmenus(oRootMenu); 1612 1613 if (oItem) { 1614 1615 if (isMenuLabel(oItem)) { // Menu label 1616 1617 oSubmenu = oItem.next(); 1618 1619 if (oSubmenu) { 1620 1621 menuNav._showMenu(oSubmenu); 1622 menuNav._focusItem(getFirstItem(oSubmenu)); 1623 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1624 1625 } 1626 else { 1627 1628 menuNav._focusItem(oItem); 1629 menuNav._setActiveItem(oItem); 1630 1631 } 1632 1633 } 1634 else { // MenuItem 1635 1636 menuNav._focusItem(oItem); 1637 menuNav._setActiveItem(oItem); 1638 1639 } 1640 1641 } 1642 1643 } 1644 1645 bPreventDefault = true; 1646 1647 break; 1648 1649 } 1650 1651 1652 if (bPreventDefault) { 1653 1654 // Prevent the browser from scrolling the window 1655 1656 event.preventDefault(); 1657 1658 } 1659 1660 }, 1661 1662 1663 /** 1664 * @method _onHorizontalMenuKeyDown 1665 * @description "keydown" event handler for horizontal menus. 1666 * @protected 1667 * @param {Object} event Object representing the DOM event. 1668 */ 1669 _onHorizontalMenuKeyDown: function (event) { 1670 1671 var menuNav = this, 1672 oActiveMenu = menuNav._activeMenu, 1673 oTarget = event.target, 1674 oFocusedItem = getItem(oTarget, true), 1675 bPreventDefault = false, 1676 nKeyCode = event.keyCode, 1677 oSubmenu; 1678 1679 1680 if (nKeyCode === 40) { 1681 1682 menuNav._hideAllSubmenus(oActiveMenu); 1683 1684 if (isMenuLabel(oFocusedItem)) { 1685 1686 oSubmenu = oFocusedItem.next(); 1687 1688 if (oSubmenu) { 1689 1690 menuNav._showMenu(oSubmenu); 1691 menuNav._focusItem(getFirstItem(oSubmenu)); 1692 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1693 1694 } 1695 1696 bPreventDefault = true; 1697 1698 } 1699 1700 } 1701 1702 1703 if (bPreventDefault) { 1704 1705 // Prevent the browser from scrolling the window 1706 1707 event.preventDefault(); 1708 1709 } 1710 1711 }, 1712 1713 1714 // Generic DOM Event handlers 1715 1716 1717 /** 1718 * @method _onMouseMove 1719 * @description "mousemove" event handler for the menu. 1720 * @protected 1721 * @param {Object} event Object representing the DOM event. 1722 */ 1723 _onMouseMove: function (event) { 1724 1725 var menuNav = this; 1726 1727 // Using a timer to set the value of the "_currentMouseX" property 1728 // helps improve the reliability of the calculation used to set the 1729 // value of the "_movingToSubmenu" property - especially in Opera. 1730 1731 later(10, menuNav, function () { 1732 1733 menuNav._currentMouseX = event.pageX; 1734 1735 }); 1736 1737 }, 1738 1739 1740 /** 1741 * @method _onMouseOver 1742 * @description "mouseover" event handler for the menu. 1743 * @protected 1744 * @param {Object} event Object representing the DOM event. 1745 */ 1746 _onMouseOver: function (event) { 1747 1748 var menuNav = this, 1749 oTarget, 1750 oMenu, 1751 oMenuLabel, 1752 oParentMenu, 1753 oMenuItem; 1754 1755 1756 if (menuNav._blockMouseEvent) { 1757 menuNav._blockMouseEvent = false; 1758 } 1759 else { 1760 1761 oTarget = event.target; 1762 oMenu = getMenu(oTarget, true); 1763 oMenuLabel = getMenuLabel(oTarget, true); 1764 oMenuItem = getMenuItem(oTarget, true); 1765 1766 1767 if (handleMouseOverForNode(oMenu, oTarget)) { 1768 1769 menuNav._onMenuMouseOver(oMenu, event); 1770 1771 oMenu[HANDLED_MOUSEOVER] = true; 1772 oMenu[HANDLED_MOUSEOUT] = false; 1773 1774 oParentMenu = getParentMenu(oMenu); 1775 1776 if (oParentMenu) { 1777 1778 oParentMenu[HANDLED_MOUSEOUT] = true; 1779 oParentMenu[HANDLED_MOUSEOVER] = false; 1780 1781 } 1782 1783 } 1784 1785 if (handleMouseOverForNode(oMenuLabel, oTarget)) { 1786 1787 menuNav._onMenuLabelMouseOver(oMenuLabel, event); 1788 1789 oMenuLabel[HANDLED_MOUSEOVER] = true; 1790 oMenuLabel[HANDLED_MOUSEOUT] = false; 1791 1792 } 1793 1794 if (handleMouseOverForNode(oMenuItem, oTarget)) { 1795 1796 menuNav._onMenuItemMouseOver(oMenuItem, event); 1797 1798 oMenuItem[HANDLED_MOUSEOVER] = true; 1799 oMenuItem[HANDLED_MOUSEOUT] = false; 1800 1801 } 1802 1803 } 1804 1805 }, 1806 1807 1808 /** 1809 * @method _onMouseOut 1810 * @description "mouseout" event handler for the menu. 1811 * @protected 1812 * @param {Object} event Object representing the DOM event. 1813 */ 1814 _onMouseOut: function (event) { 1815 1816 var menuNav = this, 1817 oActiveMenu = menuNav._activeMenu, 1818 bMovingToSubmenu = false, 1819 oTarget, 1820 oRelatedTarget, 1821 oMenu, 1822 oMenuLabel, 1823 oSubmenu, 1824 oMenuItem; 1825 1826 1827 menuNav._movingToSubmenu = 1828 (oActiveMenu && !isHorizontalMenu(oActiveMenu) && 1829 ((event.pageX - 5) > menuNav._currentMouseX)); 1830 1831 oTarget = event.target; 1832 oRelatedTarget = event.relatedTarget; 1833 oMenu = getMenu(oTarget, true); 1834 oMenuLabel = getMenuLabel(oTarget, true); 1835 oMenuItem = getMenuItem(oTarget, true); 1836 1837 1838 if (handleMouseOutForNode(oMenuLabel, oRelatedTarget)) { 1839 1840 menuNav._onMenuLabelMouseOut(oMenuLabel, event); 1841 1842 oMenuLabel[HANDLED_MOUSEOUT] = true; 1843 oMenuLabel[HANDLED_MOUSEOVER] = false; 1844 1845 } 1846 1847 if (handleMouseOutForNode(oMenuItem, oRelatedTarget)) { 1848 1849 menuNav._onMenuItemMouseOut(oMenuItem, event); 1850 1851 oMenuItem[HANDLED_MOUSEOUT] = true; 1852 oMenuItem[HANDLED_MOUSEOVER] = false; 1853 1854 } 1855 1856 1857 if (oMenuLabel) { 1858 1859 oSubmenu = oMenuLabel.next(); 1860 1861 if (oSubmenu && oRelatedTarget && 1862 (oRelatedTarget.compareTo(oSubmenu) || 1863 oSubmenu.contains(oRelatedTarget))) { 1864 1865 bMovingToSubmenu = true; 1866 1867 } 1868 1869 } 1870 1871 1872 if (handleMouseOutForNode(oMenu, oRelatedTarget) || bMovingToSubmenu) { 1873 1874 menuNav._onMenuMouseOut(oMenu, event); 1875 1876 oMenu[HANDLED_MOUSEOUT] = true; 1877 oMenu[HANDLED_MOUSEOVER] = false; 1878 1879 } 1880 1881 }, 1882 1883 1884 /** 1885 * @method _toggleSubmenuDisplay 1886 * @description "mousedown," "keydown," and "click" event handler for the 1887 * menu used to toggle the display of a submenu. 1888 * @protected 1889 * @param {Object} event Object representing the DOM event. 1890 */ 1891 _toggleSubmenuDisplay: function (event) { 1892 1893 var menuNav = this, 1894 oTarget = event.target, 1895 oMenuLabel = getMenuLabel(oTarget, true), 1896 sType = event.type, 1897 oAnchor, 1898 oSubmenu, 1899 sHref, 1900 nHashPos, 1901 nLen, 1902 sId; 1903 1904 1905 if (oMenuLabel) { 1906 1907 oAnchor = isAnchor(oTarget) ? oTarget : oTarget.ancestor(isAnchor); 1908 1909 1910 if (oAnchor) { 1911 1912 // Need to pass "2" as a second argument to "getAttribute" for 1913 // IE otherwise IE will return a fully qualified URL for the 1914 // value of the "href" attribute. 1915 // http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx 1916 1917 sHref = oAnchor.getAttribute("href", 2); 1918 nHashPos = sHref.indexOf("#"); 1919 nLen = sHref.length; 1920 1921 if (nHashPos === 0 && nLen > 1) { 1922 1923 sId = sHref.substr(1, nLen); 1924 oSubmenu = oMenuLabel.next(); 1925 1926 if (oSubmenu && (oSubmenu.get(ID) === sId)) { 1927 1928 if (sType === MOUSEDOWN || sType === KEYDOWN) { 1929 1930 if ((UA.opera || UA.gecko || UA.ie) && sType === KEYDOWN && !menuNav._preventClickHandle) { 1931 1932 // Prevent the browser from following the URL of 1933 // the anchor element 1934 1935 menuNav._preventClickHandle = menuNav._rootMenu.on("click", function (event) { 1936 1937 event.preventDefault(); 1938 1939 menuNav._preventClickHandle.detach(); 1940 menuNav._preventClickHandle = null; 1941 1942 }); 1943 1944 } 1945 1946 if (sType == MOUSEDOWN) { 1947 1948 // Prevent the target from getting focused by 1949 // default, since the element to be focused will 1950 // be determined by weather or not the submenu 1951 // is visible. 1952 event.preventDefault(); 1953 1954 // FocusManager will attempt to focus any 1955 // descendant that is the target of the mousedown 1956 // event. Since we want to explicitly control 1957 // where focus is going, we need to call 1958 // "stopImmediatePropagation" to stop the 1959 // FocusManager from doing its thing. 1960 event.stopImmediatePropagation(); 1961 1962 // The "_focusItem" method relies on the 1963 // "_hasFocus" property being set to true. The 1964 // "_hasFocus" property is normally set via a 1965 // "focus" event listener, but since we've 1966 // blocked focus from happening, we need to set 1967 // this property manually. 1968 menuNav._hasFocus = true; 1969 1970 } 1971 1972 1973 if (menuNav._isRoot(getParentMenu(oTarget))) { // Event target is a submenu label in the root menu 1974 1975 // Menu label toggle functionality 1976 1977 if (hasVisibleSubmenu(oMenuLabel)) { 1978 1979 menuNav._hideMenu(oSubmenu); 1980 menuNav._focusItem(oMenuLabel); 1981 menuNav._setActiveItem(oMenuLabel); 1982 1983 } 1984 else { 1985 1986 menuNav._hideAllSubmenus(menuNav._rootMenu); 1987 menuNav._showMenu(oSubmenu); 1988 1989 menuNav._focusItem(getFirstItem(oSubmenu)); 1990 menuNav._setActiveItem(getFirstItem(oSubmenu)); 1991 1992 } 1993 1994 } 1995 else { // Event target is a submenu label within a submenu 1996 1997 if (menuNav._activeItem == oMenuLabel) { 1998 1999 menuNav._showMenu(oSubmenu); 2000 menuNav._focusItem(getFirstItem(oSubmenu)); 2001 menuNav._setActiveItem(getFirstItem(oSubmenu)); 2002 2003 } 2004 else { 2005 2006 if (!oMenuLabel._clickHandle) { 2007 2008 oMenuLabel._clickHandle = oMenuLabel.on("click", function () { 2009 2010 menuNav._hideAllSubmenus(menuNav._rootMenu); 2011 2012 menuNav._hasFocus = false; 2013 menuNav._clearActiveItem(); 2014 2015 2016 oMenuLabel._clickHandle.detach(); 2017 2018 oMenuLabel._clickHandle = null; 2019 2020 }); 2021 2022 } 2023 2024 } 2025 2026 } 2027 2028 } 2029 2030 2031 if (sType === CLICK) { 2032 2033 // Prevent the browser from following the URL of 2034 // the anchor element 2035 2036 event.preventDefault(); 2037 2038 } 2039 2040 } 2041 2042 } 2043 2044 2045 } 2046 2047 } 2048 2049 }, 2050 2051 2052 /** 2053 * @method _onKeyPress 2054 * @description "keypress" event handler for the menu. 2055 * @protected 2056 * @param {Object} event Object representing the DOM event. 2057 */ 2058 _onKeyPress: function (event) { 2059 2060 switch (event.keyCode) { 2061 2062 case 37: // left arrow 2063 case 38: // up arrow 2064 case 39: // right arrow 2065 case 40: // down arrow 2066 2067 // Prevent the browser from scrolling the window 2068 2069 event.preventDefault(); 2070 2071 break; 2072 2073 } 2074 2075 }, 2076 2077 2078 /** 2079 * @method _onKeyDown 2080 * @description "keydown" event handler for the menu. 2081 * @protected 2082 * @param {Object} event Object representing the DOM event. 2083 */ 2084 _onKeyDown: function (event) { 2085 2086 var menuNav = this, 2087 oActiveItem = menuNav._activeItem, 2088 oTarget = event.target, 2089 oActiveMenu = getParentMenu(oTarget), 2090 oSubmenu; 2091 2092 if (oActiveMenu) { 2093 2094 menuNav._activeMenu = oActiveMenu; 2095 2096 if (isHorizontalMenu(oActiveMenu)) { 2097 menuNav._onHorizontalMenuKeyDown(event); 2098 } 2099 else { 2100 menuNav._onVerticalMenuKeyDown(event); 2101 } 2102 2103 2104 if (event.keyCode === 27) { 2105 2106 if (!menuNav._isRoot(oActiveMenu)) { 2107 2108 if (UA.opera) { 2109 later(0, menuNav, function () { 2110 menuNav._hideMenu(oActiveMenu, true); 2111 }); 2112 } 2113 else { 2114 menuNav._hideMenu(oActiveMenu, true); 2115 } 2116 2117 event.stopPropagation(); 2118 menuNav._blockMouseEvent = UA.gecko ? true : false; 2119 2120 } 2121 else if (oActiveItem) { 2122 2123 if (isMenuLabel(oActiveItem) && 2124 hasVisibleSubmenu(oActiveItem)) { 2125 2126 oSubmenu = oActiveItem.next(); 2127 2128 if (oSubmenu) { 2129 menuNav._hideMenu(oSubmenu); 2130 } 2131 2132 } 2133 else { 2134 2135 menuNav._focusManager.blur(); 2136 2137 // This is necessary for Webkit since blurring the 2138 // active menuitem won't result in the document 2139 // gaining focus, meaning the that _onDocFocus 2140 // listener won't clear the active menuitem. 2141 2142 menuNav._clearActiveItem(); 2143 2144 menuNav._hasFocus = false; 2145 2146 } 2147 2148 } 2149 2150 } 2151 2152 } 2153 2154 }, 2155 2156 /** 2157 * @method _onDocMouseDown 2158 * @description "mousedown" event handler for the owner document of 2159 * the menu. 2160 * @protected 2161 * @param {Object} event Object representing the DOM event. 2162 */ 2163 _onDocMouseDown: function (event) { 2164 2165 var menuNav = this, 2166 oRoot = menuNav._rootMenu, 2167 oTarget = event.target; 2168 2169 2170 if (!(oRoot.compareTo(oTarget) || oRoot.contains(oTarget))) { 2171 2172 menuNav._hideAllSubmenus(oRoot); 2173 2174 // Document doesn't receive focus in Webkit when the user mouses 2175 // down on it, so the "_hasFocus" property won't get set to the 2176 // correct value. The following line corrects the problem. 2177 2178 if (UA.webkit) { 2179 menuNav._hasFocus = false; 2180 menuNav._clearActiveItem(); 2181 } 2182 2183 } 2184 2185 } 2186 2187 }); 2188 2189 2190 Y.namespace('Plugin'); 2191 2192 Y.Plugin.NodeMenuNav = NodeMenuNav; 2193 2194 2195 }, '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 |