Show:
  1. /**
  2. * <p>The MenuNav Node Plugin makes it easy to transform existing list-based
  3. * markup into traditional, drop down navigational menus that are both accessible
  4. * and easy to customize, and only require a small set of dependencies.</p>
  5. *
  6. *
  7. * <p>To use the MenuNav Node Plugin, simply pass a reference to the plugin to a
  8. * Node instance's <code>plug</code> method.</p>
  9. *
  10. * <p>
  11. * <code>
  12. * &#60;script type="text/javascript"&#62; <br>
  13. * <br>
  14. * // Call the "use" method, passing in "node-menunav". This will <br>
  15. * // load the script and CSS for the MenuNav Node Plugin and all of <br>
  16. * // the required dependencies. <br>
  17. * <br>
  18. * YUI().use("node-menunav", function(Y) { <br>
  19. * <br>
  20. * // Use the "contentready" event to initialize the menu when <br>
  21. * // the subtree of element representing the root menu <br>
  22. * // (&#60;div id="menu-1"&#62;) is ready to be scripted. <br>
  23. * <br>
  24. * Y.on("contentready", function () { <br>
  25. * <br>
  26. * // The scope of the callback will be a Node instance <br>
  27. * // representing the root menu (&#60;div id="menu-1"&#62;). <br>
  28. * // Therefore, since "this" represents a Node instance, it <br>
  29. * // is possible to just call "this.plug" passing in a <br>
  30. * // reference to the MenuNav Node Plugin. <br>
  31. * <br>
  32. * this.plug(Y.Plugin.NodeMenuNav); <br>
  33. * <br>
  34. * }, "#menu-1"); <br>
  35. * <br>
  36. * }); <br>
  37. * <br>
  38. * &#60;/script&#62; <br>
  39. * </code>
  40. * </p>
  41. *
  42. * <p>The MenuNav Node Plugin has several configuration properties that can be
  43. * set via an object literal that is passed as a second argument to a Node
  44. * instance's <code>plug</code> method.
  45. * </p>
  46. *
  47. * <p>
  48. * <code>
  49. * &#60;script type="text/javascript"&#62; <br>
  50. * <br>
  51. * // Call the "use" method, passing in "node-menunav". This will <br>
  52. * // load the script and CSS for the MenuNav Node Plugin and all of <br>
  53. * // the required dependencies. <br>
  54. * <br>
  55. * YUI().use("node-menunav", function(Y) { <br>
  56. * <br>
  57. * // Use the "contentready" event to initialize the menu when <br>
  58. * // the subtree of element representing the root menu <br>
  59. * // (&#60;div id="menu-1"&#62;) is ready to be scripted. <br>
  60. * <br>
  61. * Y.on("contentready", function () { <br>
  62. * <br>
  63. * // The scope of the callback will be a Node instance <br>
  64. * // representing the root menu (&#60;div id="menu-1"&#62;). <br>
  65. * // Therefore, since "this" represents a Node instance, it <br>
  66. * // is possible to just call "this.plug" passing in a <br>
  67. * // reference to the MenuNav Node Plugin. <br>
  68. * <br>
  69. * this.plug(Y.Plugin.NodeMenuNav, { mouseOutHideDelay: 1000 });
  70. * <br><br>
  71. * }, "#menu-1"); <br>
  72. * <br>
  73. * }); <br>
  74. * <br>
  75. * &#60;/script&#62; <br>
  76. * </code>
  77. * </p>
  78. *
  79. 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/>.
  80. @module node-menunav
  81. @deprecated 3.9.0
  82. */
  83. // Util shortcuts
  84. var UA = Y.UA,
  85. later = Y.later,
  86. getClassName = Y.ClassNameManager.getClassName,
  87. // Frequently used strings
  88. MENU = "menu",
  89. MENUITEM = "menuitem",
  90. HIDDEN = "hidden",
  91. PARENT_NODE = "parentNode",
  92. CHILDREN = "children",
  93. OFFSET_HEIGHT = "offsetHeight",
  94. OFFSET_WIDTH = "offsetWidth",
  95. PX = "px",
  96. ID = "id",
  97. PERIOD = ".",
  98. HANDLED_MOUSEOUT = "handledMouseOut",
  99. HANDLED_MOUSEOVER = "handledMouseOver",
  100. ACTIVE = "active",
  101. LABEL = "label",
  102. LOWERCASE_A = "a",
  103. MOUSEDOWN = "mousedown",
  104. KEYDOWN = "keydown",
  105. CLICK = "click",
  106. EMPTY_STRING = "",
  107. FIRST_OF_TYPE = "first-of-type",
  108. ROLE = "role",
  109. PRESENTATION = "presentation",
  110. DESCENDANTS = "descendants",
  111. UI = "UI",
  112. ACTIVE_DESCENDANT = "activeDescendant",
  113. USE_ARIA = "useARIA",
  114. ARIA_HIDDEN = "aria-hidden",
  115. CONTENT = "content",
  116. HOST = "host",
  117. ACTIVE_DESCENDANT_CHANGE = ACTIVE_DESCENDANT + "Change",
  118. // Attribute keys
  119. AUTO_SUBMENU_DISPLAY = "autoSubmenuDisplay",
  120. MOUSEOUT_HIDE_DELAY = "mouseOutHideDelay",
  121. // CSS class names
  122. CSS_MENU = getClassName(MENU),
  123. CSS_MENU_HIDDEN = getClassName(MENU, HIDDEN),
  124. CSS_MENU_HORIZONTAL = getClassName(MENU, "horizontal"),
  125. CSS_MENU_LABEL = getClassName(MENU, LABEL),
  126. CSS_MENU_LABEL_ACTIVE = getClassName(MENU, LABEL, ACTIVE),
  127. CSS_MENU_LABEL_MENUVISIBLE = getClassName(MENU, LABEL, (MENU + "visible")),
  128. CSS_MENUITEM = getClassName(MENUITEM),
  129. CSS_MENUITEM_ACTIVE = getClassName(MENUITEM, ACTIVE),
  130. // CSS selectors
  131. MENU_SELECTOR = PERIOD + CSS_MENU,
  132. MENU_TOGGLE_SELECTOR = (PERIOD + getClassName(MENU, "toggle")),
  133. MENU_CONTENT_SELECTOR = PERIOD + getClassName(MENU, CONTENT),
  134. MENU_LABEL_SELECTOR = PERIOD + CSS_MENU_LABEL,
  135. STANDARD_QUERY = ">" + MENU_CONTENT_SELECTOR + ">ul>li>a",
  136. EXTENDED_QUERY = ">" + MENU_CONTENT_SELECTOR + ">ul>li>" + MENU_LABEL_SELECTOR + ">a:first-child";
  137. // Utility functions
  138. var getPreviousSibling = function (node) {
  139. var oPrevious = node.previous(),
  140. oChildren;
  141. if (!oPrevious) {
  142. oChildren = node.get(PARENT_NODE).get(CHILDREN);
  143. oPrevious = oChildren.item(oChildren.size() - 1);
  144. }
  145. return oPrevious;
  146. };
  147. var getNextSibling = function (node) {
  148. var oNext = node.next();
  149. if (!oNext) {
  150. oNext = node.get(PARENT_NODE).get(CHILDREN).item(0);
  151. }
  152. return oNext;
  153. };
  154. var isAnchor = function (node) {
  155. var bReturnVal = false;
  156. if (node) {
  157. bReturnVal = node.get("nodeName").toLowerCase() === LOWERCASE_A;
  158. }
  159. return bReturnVal;
  160. };
  161. var isMenuItem = function (node) {
  162. return node.hasClass(CSS_MENUITEM);
  163. };
  164. var isMenuLabel = function (node) {
  165. return node.hasClass(CSS_MENU_LABEL);
  166. };
  167. var isHorizontalMenu = function (menu) {
  168. return menu.hasClass(CSS_MENU_HORIZONTAL);
  169. };
  170. var hasVisibleSubmenu = function (menuLabel) {
  171. return menuLabel.hasClass(CSS_MENU_LABEL_MENUVISIBLE);
  172. };
  173. var getItemAnchor = function (node) {
  174. return isAnchor(node) ? node : node.one(LOWERCASE_A);
  175. };
  176. var getNodeWithClass = function (node, className, searchAncestors) {
  177. var oItem;
  178. if (node) {
  179. if (node.hasClass(className)) {
  180. oItem = node;
  181. }
  182. if (!oItem && searchAncestors) {
  183. oItem = node.ancestor((PERIOD + className));
  184. }
  185. }
  186. return oItem;
  187. };
  188. var getParentMenu = function (node) {
  189. return node.ancestor(MENU_SELECTOR);
  190. };
  191. var getMenu = function (node, searchAncestors) {
  192. return getNodeWithClass(node, CSS_MENU, searchAncestors);
  193. };
  194. var getMenuItem = function (node, searchAncestors) {
  195. var oItem;
  196. if (node) {
  197. oItem = getNodeWithClass(node, CSS_MENUITEM, searchAncestors);
  198. }
  199. return oItem;
  200. };
  201. var getMenuLabel = function (node, searchAncestors) {
  202. var oItem;
  203. if (node) {
  204. if (searchAncestors) {
  205. oItem = getNodeWithClass(node, CSS_MENU_LABEL, searchAncestors);
  206. }
  207. else {
  208. oItem = getNodeWithClass(node, CSS_MENU_LABEL) ||
  209. node.one((PERIOD + CSS_MENU_LABEL));
  210. }
  211. }
  212. return oItem;
  213. };
  214. var getItem = function (node, searchAncestors) {
  215. var oItem;
  216. if (node) {
  217. oItem = getMenuItem(node, searchAncestors) ||
  218. getMenuLabel(node, searchAncestors);
  219. }
  220. return oItem;
  221. };
  222. var getFirstItem = function (menu) {
  223. return getItem(menu.one("li"));
  224. };
  225. var getActiveClass = function (node) {
  226. return isMenuItem(node) ? CSS_MENUITEM_ACTIVE : CSS_MENU_LABEL_ACTIVE;
  227. };
  228. var handleMouseOverForNode = function (node, target) {
  229. return node && !node[HANDLED_MOUSEOVER] &&
  230. (node.compareTo(target) || node.contains(target));
  231. };
  232. var handleMouseOutForNode = function (node, relatedTarget) {
  233. return node && !node[HANDLED_MOUSEOUT] &&
  234. (!node.compareTo(relatedTarget) && !node.contains(relatedTarget));
  235. };
  236. /**
  237. * The NodeMenuNav class is a plugin for a Node instance. The class is used via
  238. * the <a href="Node.html#method_plug"><code>plug</code></a> method of Node and
  239. * should not be instantiated directly.
  240. * @namespace plugin
  241. * @class NodeMenuNav
  242. */
  243. var NodeMenuNav = function () {
  244. NodeMenuNav.superclass.constructor.apply(this, arguments);
  245. };
  246. NodeMenuNav.NAME = "nodeMenuNav";
  247. NodeMenuNav.NS = "menuNav";
  248. /**
  249. * @property SHIM_TEMPLATE_TITLE
  250. * @description String representing the value for the <code>title</code>
  251. * attribute for the shim used to prevent <code>&#60;select&#62;</code> elements
  252. * from poking through menus in IE 6.
  253. * @default "Menu Stacking Shim"
  254. * @type String
  255. */
  256. NodeMenuNav.SHIM_TEMPLATE_TITLE = "Menu Stacking Shim";
  257. /**
  258. * @property SHIM_TEMPLATE
  259. * @description String representing the HTML used to create the
  260. * <code>&#60;iframe&#62;</code> shim used to prevent
  261. * <code>&#60;select&#62;</code> elements from poking through menus in IE 6.
  262. * @default &#34;&#60;iframe frameborder=&#34;0&#34; tabindex=&#34;-1&#34;
  263. * class=&#34;yui-shim&#34; title=&#34;Menu Stacking Shim&#34;
  264. * src=&#34;javascript:false;&#34;&#62;&#60;/iframe&#62;&#34;
  265. * @type String
  266. */
  267. // <iframe> shim notes:
  268. //
  269. // 1) Need to set the "frameBorder" property to 0 to suppress the default
  270. // <iframe> border in IE. (Setting the CSS "border" property alone doesn't
  271. // suppress it.)
  272. //
  273. // 2) The "src" attribute of the <iframe> is set to "javascript:false;" so
  274. // that it won't load a page inside it, preventing the secure/nonsecure
  275. // warning in IE when using HTTPS.
  276. //
  277. // 3) Since the role of the <iframe> shim is completely presentational, its
  278. // "tabindex" attribute is set to "-1" and its title attribute is set to
  279. // "Menu Stacking Shim". Both strategies help users of screen readers to
  280. // avoid mistakenly interacting with the <iframe> shim.
  281. NodeMenuNav.SHIM_TEMPLATE = '<iframe frameborder="0" tabindex="-1" class="' +
  282. getClassName("shim") +
  283. '" title="' + NodeMenuNav.SHIM_TEMPLATE_TITLE +
  284. '" src="javascript:false;"></iframe>';
  285. NodeMenuNav.ATTRS = {
  286. /**
  287. * Boolean indicating if use of the WAI-ARIA Roles and States should be
  288. * enabled for the menu.
  289. *
  290. * @attribute useARIA
  291. * @readOnly
  292. * @writeOnce
  293. * @default true
  294. * @type boolean
  295. */
  296. useARIA: {
  297. value: true,
  298. writeOnce: true,
  299. lazyAdd: false,
  300. setter: function (value) {
  301. var oMenu = this.get(HOST),
  302. oMenuLabel,
  303. oMenuToggle,
  304. oSubmenu,
  305. sID;
  306. if (value) {
  307. oMenu.set(ROLE, MENU);
  308. oMenu.all("ul,li," + MENU_CONTENT_SELECTOR).set(ROLE, PRESENTATION);
  309. oMenu.all((PERIOD + getClassName(MENUITEM, CONTENT))).set(ROLE, MENUITEM);
  310. oMenu.all((PERIOD + CSS_MENU_LABEL)).each(function (node) {
  311. oMenuLabel = node;
  312. oMenuToggle = node.one(MENU_TOGGLE_SELECTOR);
  313. if (oMenuToggle) {
  314. oMenuToggle.set(ROLE, PRESENTATION);
  315. oMenuLabel = oMenuToggle.previous();
  316. }
  317. oMenuLabel.set(ROLE, MENUITEM);
  318. oMenuLabel.set("aria-haspopup", true);
  319. oSubmenu = node.next();
  320. if (oSubmenu) {
  321. oSubmenu.set(ROLE, MENU);
  322. oMenuLabel = oSubmenu.previous();
  323. oMenuToggle = oMenuLabel.one(MENU_TOGGLE_SELECTOR);
  324. if (oMenuToggle) {
  325. oMenuLabel = oMenuToggle;
  326. }
  327. sID = Y.stamp(oMenuLabel);
  328. if (!oMenuLabel.get(ID)) {
  329. oMenuLabel.set(ID, sID);
  330. }
  331. oSubmenu.set("aria-labelledby", sID);
  332. oSubmenu.set(ARIA_HIDDEN, true);
  333. }
  334. });
  335. }
  336. }
  337. },
  338. /**
  339. * Boolean indicating if submenus are automatically made visible when the
  340. * user mouses over the menu's items.
  341. *
  342. * @attribute autoSubmenuDisplay
  343. * @readOnly
  344. * @writeOnce
  345. * @default true
  346. * @type boolean
  347. */
  348. autoSubmenuDisplay: {
  349. value: true,
  350. writeOnce: true
  351. },
  352. /**
  353. * Number indicating the time (in milliseconds) that should expire before a
  354. * submenu is made visible when the user mouses over the menu's label.
  355. *
  356. * @attribute submenuShowDelay
  357. * @readOnly
  358. * @writeOnce
  359. * @default 250
  360. * @type Number
  361. */
  362. submenuShowDelay: {
  363. value: 250,
  364. writeOnce: true
  365. },
  366. /**
  367. * Number indicating the time (in milliseconds) that should expire before a
  368. * submenu is hidden when the user mouses out of a menu label heading in the
  369. * direction of a submenu.
  370. *
  371. * @attribute submenuHideDelay
  372. * @readOnly
  373. * @writeOnce
  374. * @default 250
  375. * @type Number
  376. */
  377. submenuHideDelay: {
  378. value: 250,
  379. writeOnce: true
  380. },
  381. /**
  382. * Number indicating the time (in milliseconds) that should expire before a
  383. * submenu is hidden when the user mouses out of it.
  384. *
  385. * @attribute mouseOutHideDelay
  386. * @readOnly
  387. * @writeOnce
  388. * @default 750
  389. * @type Number
  390. */
  391. mouseOutHideDelay: {
  392. value: 750,
  393. writeOnce: true
  394. }
  395. };
  396. Y.extend(NodeMenuNav, Y.Plugin.Base, {
  397. // Protected properties
  398. /**
  399. * @property _rootMenu
  400. * @description Node instance representing the root menu in the menu.
  401. * @default null
  402. * @protected
  403. * @type Node
  404. */
  405. _rootMenu: null,
  406. /**
  407. * @property _activeItem
  408. * @description Node instance representing the menu's active descendent:
  409. * the menuitem or menu label the user is currently interacting with.
  410. * @default null
  411. * @protected
  412. * @type Node
  413. */
  414. _activeItem: null,
  415. /**
  416. * @property _activeMenu
  417. * @description Node instance representing the menu that is the parent of
  418. * the menu's active descendent.
  419. * @default null
  420. * @protected
  421. * @type Node
  422. */
  423. _activeMenu: null,
  424. /**
  425. * @property _hasFocus
  426. * @description Boolean indicating if the menu has focus.
  427. * @default false
  428. * @protected
  429. * @type Boolean
  430. */
  431. _hasFocus: false,
  432. // In gecko-based browsers a mouseover and mouseout event will fire even
  433. // if a DOM element moves out from under the mouse without the user
  434. // actually moving the mouse. This bug affects NodeMenuNav because the
  435. // user can hit the Esc key to hide a menu, and if the mouse is over the
  436. // menu when the user presses Esc, the _onMenuMouseOut handler will be
  437. // called. To fix this bug the following flag (_blockMouseEvent) is used
  438. // to block the code in the _onMenuMouseOut handler from executing.
  439. /**
  440. * @property _blockMouseEvent
  441. * @description Boolean indicating whether or not to handle the
  442. * "mouseover" event.
  443. * @default false
  444. * @protected
  445. * @type Boolean
  446. */
  447. _blockMouseEvent: false,
  448. /**
  449. * @property _currentMouseX
  450. * @description Number representing the current x coordinate of the mouse
  451. * inside the menu.
  452. * @default 0
  453. * @protected
  454. * @type Number
  455. */
  456. _currentMouseX: 0,
  457. /**
  458. * @property _movingToSubmenu
  459. * @description Boolean indicating if the mouse is moving from a menu
  460. * label to its corresponding submenu.
  461. * @default false
  462. * @protected
  463. * @type Boolean
  464. */
  465. _movingToSubmenu: false,
  466. /**
  467. * @property _showSubmenuTimer
  468. * @description Timer used to show a submenu.
  469. * @default null
  470. * @protected
  471. * @type Object
  472. */
  473. _showSubmenuTimer: null,
  474. /**
  475. * @property _hideSubmenuTimer
  476. * @description Timer used to hide a submenu.
  477. * @default null
  478. * @protected
  479. * @type Object
  480. */
  481. _hideSubmenuTimer: null,
  482. /**
  483. * @property _hideAllSubmenusTimer
  484. * @description Timer used to hide a all submenus.
  485. * @default null
  486. * @protected
  487. * @type Object
  488. */
  489. _hideAllSubmenusTimer: null,
  490. /**
  491. * @property _firstItem
  492. * @description Node instance representing the first item (menuitem or menu
  493. * label) in the root menu of a menu.
  494. * @default null
  495. * @protected
  496. * @type Node
  497. */
  498. _firstItem: null,
  499. // Public methods
  500. initializer: function (config) {
  501. var menuNav = this,
  502. oRootMenu = this.get(HOST),
  503. aHandlers = [],
  504. oDoc;
  505. 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");
  506. if (oRootMenu) {
  507. menuNav._rootMenu = oRootMenu;
  508. oRootMenu.all("ul:first-child").addClass(FIRST_OF_TYPE);
  509. // Hide all visible submenus
  510. oRootMenu.all(MENU_SELECTOR).addClass(CSS_MENU_HIDDEN);
  511. // Wire up all event handlers
  512. aHandlers.push(oRootMenu.on("mouseover", menuNav._onMouseOver, menuNav));
  513. aHandlers.push(oRootMenu.on("mouseout", menuNav._onMouseOut, menuNav));
  514. aHandlers.push(oRootMenu.on("mousemove", menuNav._onMouseMove, menuNav));
  515. aHandlers.push(oRootMenu.on(MOUSEDOWN, menuNav._toggleSubmenuDisplay, menuNav));
  516. aHandlers.push(Y.on("key", menuNav._toggleSubmenuDisplay, oRootMenu, "down:13", menuNav));
  517. aHandlers.push(oRootMenu.on(CLICK, menuNav._toggleSubmenuDisplay, menuNav));
  518. aHandlers.push(oRootMenu.on("keypress", menuNav._onKeyPress, menuNav));
  519. aHandlers.push(oRootMenu.on(KEYDOWN, menuNav._onKeyDown, menuNav));
  520. oDoc = oRootMenu.get("ownerDocument");
  521. aHandlers.push(oDoc.on(MOUSEDOWN, menuNav._onDocMouseDown, menuNav));
  522. aHandlers.push(oDoc.on("focus", menuNav._onDocFocus, menuNav));
  523. this._eventHandlers = aHandlers;
  524. menuNav._initFocusManager();
  525. }
  526. },
  527. destructor: function () {
  528. var aHandlers = this._eventHandlers;
  529. if (aHandlers) {
  530. Y.Array.each(aHandlers, function (handle) {
  531. handle.detach();
  532. });
  533. this._eventHandlers = null;
  534. }
  535. this.get(HOST).unplug("focusManager");
  536. },
  537. // Protected methods
  538. /**
  539. * @method _isRoot
  540. * @description Returns a boolean indicating if the specified menu is the
  541. * root menu in the menu.
  542. * @protected
  543. * @param {Node} menu Node instance representing a menu.
  544. * @return {Boolean} Boolean indicating if the specified menu is the root
  545. * menu in the menu.
  546. */
  547. _isRoot: function (menu) {
  548. return this._rootMenu.compareTo(menu);
  549. },
  550. /**
  551. * @method _getTopmostSubmenu
  552. * @description Returns the topmost submenu of a submenu hierarchy.
  553. * @protected
  554. * @param {Node} menu Node instance representing a menu.
  555. * @return {Node} Node instance representing a menu.
  556. */
  557. _getTopmostSubmenu: function (menu) {
  558. var menuNav = this,
  559. oMenu = getParentMenu(menu),
  560. returnVal;
  561. if (!oMenu) {
  562. returnVal = menu;
  563. }
  564. else if (menuNav._isRoot(oMenu)) {
  565. returnVal = menu;
  566. }
  567. else {
  568. returnVal = menuNav._getTopmostSubmenu(oMenu);
  569. }
  570. return returnVal;
  571. },
  572. /**
  573. * @method _clearActiveItem
  574. * @description Clears the menu's active descendent.
  575. * @protected
  576. */
  577. _clearActiveItem: function () {
  578. var menuNav = this,
  579. oActiveItem = menuNav._activeItem;
  580. if (oActiveItem) {
  581. oActiveItem.removeClass(getActiveClass(oActiveItem));
  582. }
  583. menuNav._activeItem = null;
  584. },
  585. /**
  586. * @method _setActiveItem
  587. * @description Sets the specified menuitem or menu label as the menu's
  588. * active descendent.
  589. * @protected
  590. * @param {Node} item Node instance representing a menuitem or menu label.
  591. */
  592. _setActiveItem: function (item) {
  593. var menuNav = this;
  594. if (item) {
  595. menuNav._clearActiveItem();
  596. item.addClass(getActiveClass(item));
  597. menuNav._activeItem = item;
  598. }
  599. },
  600. /**
  601. * @method _focusItem
  602. * @description Focuses the specified menuitem or menu label.
  603. * @protected
  604. * @param {Node} item Node instance representing a menuitem or menu label.
  605. */
  606. _focusItem: function (item) {
  607. var menuNav = this,
  608. oMenu,
  609. oItem;
  610. if (item && menuNav._hasFocus) {
  611. oMenu = getParentMenu(item);
  612. oItem = getItemAnchor(item);
  613. if (oMenu && !oMenu.compareTo(menuNav._activeMenu)) {
  614. menuNav._activeMenu = oMenu;
  615. menuNav._initFocusManager();
  616. }
  617. menuNav._focusManager.focus(oItem);
  618. }
  619. },
  620. /**
  621. * @method _showMenu
  622. * @description Shows the specified menu.
  623. * @protected
  624. * @param {Node} menu Node instance representing a menu.
  625. */
  626. _showMenu: function (menu) {
  627. var oParentMenu = getParentMenu(menu),
  628. oLI = menu.get(PARENT_NODE),
  629. aXY = oLI.getXY();
  630. if (this.get(USE_ARIA)) {
  631. menu.set(ARIA_HIDDEN, false);
  632. }
  633. if (isHorizontalMenu(oParentMenu)) {
  634. aXY[1] = aXY[1] + oLI.get(OFFSET_HEIGHT);
  635. }
  636. else {
  637. aXY[0] = aXY[0] + oLI.get(OFFSET_WIDTH);
  638. }
  639. menu.setXY(aXY);
  640. if (UA.ie && UA.ie < 8) {
  641. if (UA.ie === 6 && !menu.hasIFrameShim) {
  642. menu.appendChild(Y.Node.create(NodeMenuNav.SHIM_TEMPLATE));
  643. menu.hasIFrameShim = true;
  644. }
  645. // Clear previous values for height and width
  646. menu.setStyles({ height: EMPTY_STRING, width: EMPTY_STRING });
  647. // Set the width and height of the menu's bounding box - this is
  648. // necessary for IE 6 so that the CSS for the <iframe> shim can
  649. // simply set the <iframe>'s width and height to 100% to ensure
  650. // that dimensions of an <iframe> shim are always sync'd to the
  651. // that of its parent menu. Specifying a width and height also
  652. // helps when positioning decorator elements (for creating effects
  653. // like rounded corners) inside a menu's bounding box in IE 7.
  654. menu.setStyles({
  655. height: (menu.get(OFFSET_HEIGHT) + PX),
  656. width: (menu.get(OFFSET_WIDTH) + PX) });
  657. }
  658. menu.previous().addClass(CSS_MENU_LABEL_MENUVISIBLE);
  659. menu.removeClass(CSS_MENU_HIDDEN);
  660. },
  661. /**
  662. * @method _hideMenu
  663. * @description Hides the specified menu.
  664. * @protected
  665. * @param {Node} menu Node instance representing a menu.
  666. * @param {Boolean} activateAndFocusLabel Boolean indicating if the label
  667. * for the specified
  668. * menu should be focused and set as active.
  669. */
  670. _hideMenu: function (menu, activateAndFocusLabel) {
  671. var menuNav = this,
  672. oLabel = menu.previous(),
  673. oActiveItem;
  674. oLabel.removeClass(CSS_MENU_LABEL_MENUVISIBLE);
  675. if (activateAndFocusLabel) {
  676. menuNav._focusItem(oLabel);
  677. menuNav._setActiveItem(oLabel);
  678. }
  679. oActiveItem = menu.one((PERIOD + CSS_MENUITEM_ACTIVE));
  680. if (oActiveItem) {
  681. oActiveItem.removeClass(CSS_MENUITEM_ACTIVE);
  682. }
  683. // Clear the values for top and left that were set by the call to
  684. // "setXY" when the menu was shown so that the hidden position
  685. // specified in the core CSS file will take affect.
  686. menu.setStyles({ left: EMPTY_STRING, top: EMPTY_STRING });
  687. menu.addClass(CSS_MENU_HIDDEN);
  688. if (menuNav.get(USE_ARIA)) {
  689. menu.set(ARIA_HIDDEN, true);
  690. }
  691. },
  692. /**
  693. * @method _hideAllSubmenus
  694. * @description Hides all submenus of the specified menu.
  695. * @protected
  696. * @param {Node} menu Node instance representing a menu.
  697. */
  698. _hideAllSubmenus: function (menu) {
  699. var menuNav = this;
  700. menu.all(MENU_SELECTOR).each(Y.bind(function (submenuNode) {
  701. menuNav._hideMenu(submenuNode);
  702. }, menuNav));
  703. },
  704. /**
  705. * @method _cancelShowSubmenuTimer
  706. * @description Cancels the timer used to show a submenu.
  707. * @protected
  708. */
  709. _cancelShowSubmenuTimer: function () {
  710. var menuNav = this,
  711. oShowSubmenuTimer = menuNav._showSubmenuTimer;
  712. if (oShowSubmenuTimer) {
  713. oShowSubmenuTimer.cancel();
  714. menuNav._showSubmenuTimer = null;
  715. }
  716. },
  717. /**
  718. * @method _cancelHideSubmenuTimer
  719. * @description Cancels the timer used to hide a submenu.
  720. * @protected
  721. */
  722. _cancelHideSubmenuTimer: function () {
  723. var menuNav = this,
  724. oHideSubmenuTimer = menuNav._hideSubmenuTimer;
  725. if (oHideSubmenuTimer) {
  726. oHideSubmenuTimer.cancel();
  727. menuNav._hideSubmenuTimer = null;
  728. }
  729. },
  730. /**
  731. * @method _initFocusManager
  732. * @description Initializes and updates the Focus Manager so that is is
  733. * always managing descendants of the active menu.
  734. * @protected
  735. */
  736. _initFocusManager: function () {
  737. var menuNav = this,
  738. oRootMenu = menuNav._rootMenu,
  739. oMenu = menuNav._activeMenu || oRootMenu,
  740. sSelectorBase =
  741. menuNav._isRoot(oMenu) ? EMPTY_STRING : ("#" + oMenu.get("id")),
  742. oFocusManager = menuNav._focusManager,
  743. sKeysVal,
  744. sDescendantSelector,
  745. sQuery;
  746. if (isHorizontalMenu(oMenu)) {
  747. sDescendantSelector = sSelectorBase + STANDARD_QUERY + "," +
  748. sSelectorBase + EXTENDED_QUERY;
  749. sKeysVal = { next: "down:39", previous: "down:37" };
  750. }
  751. else {
  752. sDescendantSelector = sSelectorBase + STANDARD_QUERY;
  753. sKeysVal = { next: "down:40", previous: "down:38" };
  754. }
  755. if (!oFocusManager) {
  756. oRootMenu.plug(Y.Plugin.NodeFocusManager, {
  757. descendants: sDescendantSelector,
  758. keys: sKeysVal,
  759. circular: true
  760. });
  761. oFocusManager = oRootMenu.focusManager;
  762. sQuery = "#" + oRootMenu.get("id") + MENU_SELECTOR + " a," +
  763. MENU_TOGGLE_SELECTOR;
  764. oRootMenu.all(sQuery).set("tabIndex", -1);
  765. oFocusManager.on(ACTIVE_DESCENDANT_CHANGE,
  766. this._onActiveDescendantChange, oFocusManager, this);
  767. oFocusManager.after(ACTIVE_DESCENDANT_CHANGE,
  768. this._afterActiveDescendantChange, oFocusManager, this);
  769. menuNav._focusManager = oFocusManager;
  770. }
  771. else {
  772. oFocusManager.set(ACTIVE_DESCENDANT, -1);
  773. oFocusManager.set(DESCENDANTS, sDescendantSelector);
  774. oFocusManager.set("keys", sKeysVal);
  775. }
  776. },
  777. // Event handlers for discrete pieces of pieces of the menu
  778. /**
  779. * @method _onActiveDescendantChange
  780. * @description "activeDescendantChange" event handler for menu's
  781. * Focus Manager.
  782. * @protected
  783. * @param {Object} event Object representing the Attribute change event.
  784. * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance.
  785. */
  786. _onActiveDescendantChange: function (event, menuNav) {
  787. if (event.src === UI && menuNav._activeMenu &&
  788. !menuNav._movingToSubmenu) {
  789. menuNav._hideAllSubmenus(menuNav._activeMenu);
  790. }
  791. },
  792. /**
  793. * @method _afterActiveDescendantChange
  794. * @description "activeDescendantChange" event handler for menu's
  795. * Focus Manager.
  796. * @protected
  797. * @param {Object} event Object representing the Attribute change event.
  798. * @param {NodeMenuNav} menuNav Object representing the NodeMenuNav instance.
  799. */
  800. _afterActiveDescendantChange: function (event, menuNav) {
  801. var oItem;
  802. if (event.src === UI) {
  803. oItem = getItem(this.get(DESCENDANTS).item(event.newVal), true);
  804. menuNav._setActiveItem(oItem);
  805. }
  806. },
  807. /**
  808. * @method _onDocFocus
  809. * @description "focus" event handler for the owner document of the MenuNav.
  810. * @protected
  811. * @param {Object} event Object representing the DOM event.
  812. */
  813. _onDocFocus: function (event) {
  814. var menuNav = this,
  815. oActiveItem = menuNav._activeItem,
  816. oTarget = event.target,
  817. oMenu;
  818. if (menuNav._rootMenu.contains(oTarget)) { // The menu has focus
  819. if (menuNav._hasFocus) {
  820. oMenu = getParentMenu(oTarget);
  821. // If the element that was focused is a descendant of the
  822. // root menu, but is in a submenu not currently being
  823. // managed by the Focus Manager, update the Focus Manager so
  824. // that it is now managing the submenu that is the parent of
  825. // the element that was focused.
  826. if (!menuNav._activeMenu.compareTo(oMenu)) {
  827. menuNav._activeMenu = oMenu;
  828. menuNav._initFocusManager();
  829. menuNav._focusManager.set(ACTIVE_DESCENDANT, oTarget);
  830. menuNav._setActiveItem(getItem(oTarget, true));
  831. }
  832. }
  833. else { // Initial focus
  834. // First time the menu has been focused, need to setup focused
  835. // state and established active active descendant
  836. menuNav._hasFocus = true;
  837. oActiveItem = getItem(oTarget, true);
  838. if (oActiveItem) {
  839. menuNav._setActiveItem(oActiveItem);
  840. }
  841. }
  842. }
  843. else { // The menu has lost focus
  844. menuNav._clearActiveItem();
  845. menuNav._cancelShowSubmenuTimer();
  846. menuNav._hideAllSubmenus(menuNav._rootMenu);
  847. menuNav._activeMenu = menuNav._rootMenu;
  848. menuNav._initFocusManager();
  849. menuNav._focusManager.set(ACTIVE_DESCENDANT, 0);
  850. menuNav._hasFocus = false;
  851. }
  852. },
  853. /**
  854. * @method _onMenuMouseOver
  855. * @description "mouseover" event handler for a menu.
  856. * @protected
  857. * @param {Node} menu Node instance representing a menu.
  858. * @param {Object} event Object representing the DOM event.
  859. */
  860. _onMenuMouseOver: function (menu, event) {
  861. var menuNav = this,
  862. oHideAllSubmenusTimer = menuNav._hideAllSubmenusTimer;
  863. if (oHideAllSubmenusTimer) {
  864. oHideAllSubmenusTimer.cancel();
  865. menuNav._hideAllSubmenusTimer = null;
  866. }
  867. menuNav._cancelHideSubmenuTimer();
  868. // Need to update the FocusManager in advance of focus a new
  869. // Menu in order to avoid the FocusManager thinking that
  870. // it has lost focus
  871. if (menu && !menu.compareTo(menuNav._activeMenu)) {
  872. menuNav._activeMenu = menu;
  873. if (menuNav._hasFocus) {
  874. menuNav._initFocusManager();
  875. }
  876. }
  877. if (menuNav._movingToSubmenu && isHorizontalMenu(menu)) {
  878. menuNav._movingToSubmenu = false;
  879. }
  880. },
  881. /**
  882. * @method _hideAndFocusLabel
  883. * @description Hides all of the submenus of the root menu and focuses the
  884. * label of the topmost submenu
  885. * @protected
  886. */
  887. _hideAndFocusLabel: function () {
  888. var menuNav = this,
  889. oActiveMenu = menuNav._activeMenu,
  890. oSubmenu;
  891. menuNav._hideAllSubmenus(menuNav._rootMenu);
  892. if (oActiveMenu) {
  893. // Focus the label element for the topmost submenu
  894. oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu);
  895. menuNav._focusItem(oSubmenu.previous());
  896. }
  897. },
  898. /**
  899. * @method _onMenuMouseOut
  900. * @description "mouseout" event handler for a menu.
  901. * @protected
  902. * @param {Node} menu Node instance representing a menu.
  903. * @param {Object} event Object representing the DOM event.
  904. */
  905. _onMenuMouseOut: function (menu, event) {
  906. var menuNav = this,
  907. oActiveMenu = menuNav._activeMenu,
  908. oRelatedTarget = event.relatedTarget,
  909. oActiveItem = menuNav._activeItem,
  910. oParentMenu,
  911. oMenu;
  912. if (oActiveMenu && !oActiveMenu.contains(oRelatedTarget)) {
  913. oParentMenu = getParentMenu(oActiveMenu);
  914. if (oParentMenu && !oParentMenu.contains(oRelatedTarget)) {
  915. if (menuNav.get(MOUSEOUT_HIDE_DELAY) > 0) {
  916. menuNav._cancelShowSubmenuTimer();
  917. menuNav._hideAllSubmenusTimer =
  918. later(menuNav.get(MOUSEOUT_HIDE_DELAY),
  919. menuNav, menuNav._hideAndFocusLabel);
  920. }
  921. }
  922. else {
  923. if (oActiveItem) {
  924. oMenu = getParentMenu(oActiveItem);
  925. if (!menuNav._isRoot(oMenu)) {
  926. menuNav._focusItem(oMenu.previous());
  927. }
  928. }
  929. }
  930. }
  931. },
  932. /**
  933. * @method _onMenuLabelMouseOver
  934. * @description "mouseover" event handler for a menu label.
  935. * @protected
  936. * @param {Node} menuLabel Node instance representing a menu label.
  937. * @param {Object} event Object representing the DOM event.
  938. */
  939. _onMenuLabelMouseOver: function (menuLabel, event) {
  940. var menuNav = this,
  941. oActiveMenu = menuNav._activeMenu,
  942. bIsRoot = menuNav._isRoot(oActiveMenu),
  943. bUseAutoSubmenuDisplay =
  944. (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot),
  945. submenuShowDelay = menuNav.get("submenuShowDelay"),
  946. oSubmenu;
  947. var showSubmenu = function (delay) {
  948. menuNav._cancelHideSubmenuTimer();
  949. menuNav._cancelShowSubmenuTimer();
  950. if (!hasVisibleSubmenu(menuLabel)) {
  951. oSubmenu = menuLabel.next();
  952. if (oSubmenu) {
  953. menuNav._hideAllSubmenus(oActiveMenu);
  954. menuNav._showSubmenuTimer = later(delay, menuNav, menuNav._showMenu, oSubmenu);
  955. }
  956. }
  957. };
  958. menuNav._focusItem(menuLabel);
  959. menuNav._setActiveItem(menuLabel);
  960. if (bUseAutoSubmenuDisplay) {
  961. if (menuNav._movingToSubmenu) {
  962. // If the user is moving diagonally from a submenu to
  963. // another submenu and they then stop and pause on a
  964. // menu label for an amount of time equal to the amount of
  965. // time defined for the display of a submenu then show the
  966. // submenu immediately.
  967. // http://yuilibrary.com/projects/yui3/ticket/2528316
  968. //Y.message("Pause path");
  969. menuNav._hoverTimer = later(submenuShowDelay, menuNav, function () {
  970. showSubmenu(0);
  971. });
  972. }
  973. else {
  974. showSubmenu(submenuShowDelay);
  975. }
  976. }
  977. },
  978. /**
  979. * @method _onMenuLabelMouseOut
  980. * @description "mouseout" event handler for a menu label.
  981. * @protected
  982. * @param {Node} menuLabel Node instance representing a menu label.
  983. * @param {Object} event Object representing the DOM event.
  984. */
  985. _onMenuLabelMouseOut: function (menuLabel, event) {
  986. var menuNav = this,
  987. bIsRoot = menuNav._isRoot(menuNav._activeMenu),
  988. bUseAutoSubmenuDisplay =
  989. (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot),
  990. oRelatedTarget = event.relatedTarget,
  991. oSubmenu = menuLabel.next(),
  992. hoverTimer = menuNav._hoverTimer;
  993. if (hoverTimer) {
  994. hoverTimer.cancel();
  995. }
  996. menuNav._clearActiveItem();
  997. if (bUseAutoSubmenuDisplay) {
  998. if (menuNav._movingToSubmenu &&
  999. !menuNav._showSubmenuTimer && oSubmenu) {
  1000. // If the mouse is moving diagonally toward the submenu and
  1001. // another submenu isn't in the process of being displayed
  1002. // (via a timer), then hide the submenu via a timer to give
  1003. // the user some time to reach the submenu.
  1004. menuNav._hideSubmenuTimer =
  1005. later(menuNav.get("submenuHideDelay"), menuNav,
  1006. menuNav._hideMenu, oSubmenu);
  1007. }
  1008. else if (!menuNav._movingToSubmenu && oSubmenu && (!oRelatedTarget ||
  1009. (oRelatedTarget &&
  1010. !oSubmenu.contains(oRelatedTarget) &&
  1011. !oRelatedTarget.compareTo(oSubmenu)))) {
  1012. // If the mouse is not moving toward the submenu, cancel any
  1013. // submenus that might be in the process of being displayed
  1014. // (via a timer) and hide this submenu immediately.
  1015. menuNav._cancelShowSubmenuTimer();
  1016. menuNav._hideMenu(oSubmenu);
  1017. }
  1018. }
  1019. },
  1020. /**
  1021. * @method _onMenuItemMouseOver
  1022. * @description "mouseover" event handler for a menuitem.
  1023. * @protected
  1024. * @param {Node} menuItem Node instance representing a menuitem.
  1025. * @param {Object} event Object representing the DOM event.
  1026. */
  1027. _onMenuItemMouseOver: function (menuItem, event) {
  1028. var menuNav = this,
  1029. oActiveMenu = menuNav._activeMenu,
  1030. bIsRoot = menuNav._isRoot(oActiveMenu),
  1031. bUseAutoSubmenuDisplay =
  1032. (menuNav.get(AUTO_SUBMENU_DISPLAY) && bIsRoot || !bIsRoot);
  1033. menuNav._focusItem(menuItem);
  1034. menuNav._setActiveItem(menuItem);
  1035. if (bUseAutoSubmenuDisplay && !menuNav._movingToSubmenu) {
  1036. menuNav._hideAllSubmenus(oActiveMenu);
  1037. }
  1038. },
  1039. /**
  1040. * @method _onMenuItemMouseOut
  1041. * @description "mouseout" event handler for a menuitem.
  1042. * @protected
  1043. * @param {Node} menuItem Node instance representing a menuitem.
  1044. * @param {Object} event Object representing the DOM event.
  1045. */
  1046. _onMenuItemMouseOut: function (menuItem, event) {
  1047. this._clearActiveItem();
  1048. },
  1049. /**
  1050. * @method _onVerticalMenuKeyDown
  1051. * @description "keydown" event handler for vertical menus.
  1052. * @protected
  1053. * @param {Object} event Object representing the DOM event.
  1054. */
  1055. _onVerticalMenuKeyDown: function (event) {
  1056. var menuNav = this,
  1057. oActiveMenu = menuNav._activeMenu,
  1058. oRootMenu = menuNav._rootMenu,
  1059. oTarget = event.target,
  1060. bPreventDefault = false,
  1061. nKeyCode = event.keyCode,
  1062. oSubmenu,
  1063. oParentMenu,
  1064. oLI,
  1065. oItem;
  1066. switch (nKeyCode) {
  1067. case 37: // left arrow
  1068. oParentMenu = getParentMenu(oActiveMenu);
  1069. if (oParentMenu && isHorizontalMenu(oParentMenu)) {
  1070. menuNav._hideMenu(oActiveMenu);
  1071. oLI = getPreviousSibling(oActiveMenu.get(PARENT_NODE));
  1072. oItem = getItem(oLI);
  1073. if (oItem) {
  1074. if (isMenuLabel(oItem)) { // Menu label
  1075. oSubmenu = oItem.next();
  1076. if (oSubmenu) {
  1077. menuNav._showMenu(oSubmenu);
  1078. menuNav._focusItem(getFirstItem(oSubmenu));
  1079. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1080. }
  1081. else {
  1082. menuNav._focusItem(oItem);
  1083. menuNav._setActiveItem(oItem);
  1084. }
  1085. }
  1086. else { // MenuItem
  1087. menuNav._focusItem(oItem);
  1088. menuNav._setActiveItem(oItem);
  1089. }
  1090. }
  1091. }
  1092. else if (!menuNav._isRoot(oActiveMenu)) {
  1093. menuNav._hideMenu(oActiveMenu, true);
  1094. }
  1095. bPreventDefault = true;
  1096. break;
  1097. case 39: // right arrow
  1098. if (isMenuLabel(oTarget)) {
  1099. oSubmenu = oTarget.next();
  1100. if (oSubmenu) {
  1101. menuNav._showMenu(oSubmenu);
  1102. menuNav._focusItem(getFirstItem(oSubmenu));
  1103. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1104. }
  1105. }
  1106. else if (isHorizontalMenu(oRootMenu)) {
  1107. oSubmenu = menuNav._getTopmostSubmenu(oActiveMenu);
  1108. oLI = getNextSibling(oSubmenu.get(PARENT_NODE));
  1109. oItem = getItem(oLI);
  1110. menuNav._hideAllSubmenus(oRootMenu);
  1111. if (oItem) {
  1112. if (isMenuLabel(oItem)) { // Menu label
  1113. oSubmenu = oItem.next();
  1114. if (oSubmenu) {
  1115. menuNav._showMenu(oSubmenu);
  1116. menuNav._focusItem(getFirstItem(oSubmenu));
  1117. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1118. }
  1119. else {
  1120. menuNav._focusItem(oItem);
  1121. menuNav._setActiveItem(oItem);
  1122. }
  1123. }
  1124. else { // MenuItem
  1125. menuNav._focusItem(oItem);
  1126. menuNav._setActiveItem(oItem);
  1127. }
  1128. }
  1129. }
  1130. bPreventDefault = true;
  1131. break;
  1132. }
  1133. if (bPreventDefault) {
  1134. // Prevent the browser from scrolling the window
  1135. event.preventDefault();
  1136. }
  1137. },
  1138. /**
  1139. * @method _onHorizontalMenuKeyDown
  1140. * @description "keydown" event handler for horizontal menus.
  1141. * @protected
  1142. * @param {Object} event Object representing the DOM event.
  1143. */
  1144. _onHorizontalMenuKeyDown: function (event) {
  1145. var menuNav = this,
  1146. oActiveMenu = menuNav._activeMenu,
  1147. oTarget = event.target,
  1148. oFocusedItem = getItem(oTarget, true),
  1149. bPreventDefault = false,
  1150. nKeyCode = event.keyCode,
  1151. oSubmenu;
  1152. if (nKeyCode === 40) {
  1153. menuNav._hideAllSubmenus(oActiveMenu);
  1154. if (isMenuLabel(oFocusedItem)) {
  1155. oSubmenu = oFocusedItem.next();
  1156. if (oSubmenu) {
  1157. menuNav._showMenu(oSubmenu);
  1158. menuNav._focusItem(getFirstItem(oSubmenu));
  1159. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1160. }
  1161. bPreventDefault = true;
  1162. }
  1163. }
  1164. if (bPreventDefault) {
  1165. // Prevent the browser from scrolling the window
  1166. event.preventDefault();
  1167. }
  1168. },
  1169. // Generic DOM Event handlers
  1170. /**
  1171. * @method _onMouseMove
  1172. * @description "mousemove" event handler for the menu.
  1173. * @protected
  1174. * @param {Object} event Object representing the DOM event.
  1175. */
  1176. _onMouseMove: function (event) {
  1177. var menuNav = this;
  1178. // Using a timer to set the value of the "_currentMouseX" property
  1179. // helps improve the reliability of the calculation used to set the
  1180. // value of the "_movingToSubmenu" property - especially in Opera.
  1181. later(10, menuNav, function () {
  1182. menuNav._currentMouseX = event.pageX;
  1183. });
  1184. },
  1185. /**
  1186. * @method _onMouseOver
  1187. * @description "mouseover" event handler for the menu.
  1188. * @protected
  1189. * @param {Object} event Object representing the DOM event.
  1190. */
  1191. _onMouseOver: function (event) {
  1192. var menuNav = this,
  1193. oTarget,
  1194. oMenu,
  1195. oMenuLabel,
  1196. oParentMenu,
  1197. oMenuItem;
  1198. if (menuNav._blockMouseEvent) {
  1199. menuNav._blockMouseEvent = false;
  1200. }
  1201. else {
  1202. oTarget = event.target;
  1203. oMenu = getMenu(oTarget, true);
  1204. oMenuLabel = getMenuLabel(oTarget, true);
  1205. oMenuItem = getMenuItem(oTarget, true);
  1206. if (handleMouseOverForNode(oMenu, oTarget)) {
  1207. menuNav._onMenuMouseOver(oMenu, event);
  1208. oMenu[HANDLED_MOUSEOVER] = true;
  1209. oMenu[HANDLED_MOUSEOUT] = false;
  1210. oParentMenu = getParentMenu(oMenu);
  1211. if (oParentMenu) {
  1212. oParentMenu[HANDLED_MOUSEOUT] = true;
  1213. oParentMenu[HANDLED_MOUSEOVER] = false;
  1214. }
  1215. }
  1216. if (handleMouseOverForNode(oMenuLabel, oTarget)) {
  1217. menuNav._onMenuLabelMouseOver(oMenuLabel, event);
  1218. oMenuLabel[HANDLED_MOUSEOVER] = true;
  1219. oMenuLabel[HANDLED_MOUSEOUT] = false;
  1220. }
  1221. if (handleMouseOverForNode(oMenuItem, oTarget)) {
  1222. menuNav._onMenuItemMouseOver(oMenuItem, event);
  1223. oMenuItem[HANDLED_MOUSEOVER] = true;
  1224. oMenuItem[HANDLED_MOUSEOUT] = false;
  1225. }
  1226. }
  1227. },
  1228. /**
  1229. * @method _onMouseOut
  1230. * @description "mouseout" event handler for the menu.
  1231. * @protected
  1232. * @param {Object} event Object representing the DOM event.
  1233. */
  1234. _onMouseOut: function (event) {
  1235. var menuNav = this,
  1236. oActiveMenu = menuNav._activeMenu,
  1237. bMovingToSubmenu = false,
  1238. oTarget,
  1239. oRelatedTarget,
  1240. oMenu,
  1241. oMenuLabel,
  1242. oSubmenu,
  1243. oMenuItem;
  1244. menuNav._movingToSubmenu =
  1245. (oActiveMenu && !isHorizontalMenu(oActiveMenu) &&
  1246. ((event.pageX - 5) > menuNav._currentMouseX));
  1247. oTarget = event.target;
  1248. oRelatedTarget = event.relatedTarget;
  1249. oMenu = getMenu(oTarget, true);
  1250. oMenuLabel = getMenuLabel(oTarget, true);
  1251. oMenuItem = getMenuItem(oTarget, true);
  1252. if (handleMouseOutForNode(oMenuLabel, oRelatedTarget)) {
  1253. menuNav._onMenuLabelMouseOut(oMenuLabel, event);
  1254. oMenuLabel[HANDLED_MOUSEOUT] = true;
  1255. oMenuLabel[HANDLED_MOUSEOVER] = false;
  1256. }
  1257. if (handleMouseOutForNode(oMenuItem, oRelatedTarget)) {
  1258. menuNav._onMenuItemMouseOut(oMenuItem, event);
  1259. oMenuItem[HANDLED_MOUSEOUT] = true;
  1260. oMenuItem[HANDLED_MOUSEOVER] = false;
  1261. }
  1262. if (oMenuLabel) {
  1263. oSubmenu = oMenuLabel.next();
  1264. if (oSubmenu && oRelatedTarget &&
  1265. (oRelatedTarget.compareTo(oSubmenu) ||
  1266. oSubmenu.contains(oRelatedTarget))) {
  1267. bMovingToSubmenu = true;
  1268. }
  1269. }
  1270. if (handleMouseOutForNode(oMenu, oRelatedTarget) || bMovingToSubmenu) {
  1271. menuNav._onMenuMouseOut(oMenu, event);
  1272. oMenu[HANDLED_MOUSEOUT] = true;
  1273. oMenu[HANDLED_MOUSEOVER] = false;
  1274. }
  1275. },
  1276. /**
  1277. * @method _toggleSubmenuDisplay
  1278. * @description "mousedown," "keydown," and "click" event handler for the
  1279. * menu used to toggle the display of a submenu.
  1280. * @protected
  1281. * @param {Object} event Object representing the DOM event.
  1282. */
  1283. _toggleSubmenuDisplay: function (event) {
  1284. var menuNav = this,
  1285. oTarget = event.target,
  1286. oMenuLabel = getMenuLabel(oTarget, true),
  1287. sType = event.type,
  1288. oAnchor,
  1289. oSubmenu,
  1290. sHref,
  1291. nHashPos,
  1292. nLen,
  1293. sId;
  1294. if (oMenuLabel) {
  1295. oAnchor = isAnchor(oTarget) ? oTarget : oTarget.ancestor(isAnchor);
  1296. if (oAnchor) {
  1297. // Need to pass "2" as a second argument to "getAttribute" for
  1298. // IE otherwise IE will return a fully qualified URL for the
  1299. // value of the "href" attribute.
  1300. // http://msdn.microsoft.com/en-us/library/ms536429(VS.85).aspx
  1301. sHref = oAnchor.getAttribute("href", 2);
  1302. nHashPos = sHref.indexOf("#");
  1303. nLen = sHref.length;
  1304. if (nHashPos === 0 && nLen > 1) {
  1305. sId = sHref.substr(1, nLen);
  1306. oSubmenu = oMenuLabel.next();
  1307. if (oSubmenu && (oSubmenu.get(ID) === sId)) {
  1308. if (sType === MOUSEDOWN || sType === KEYDOWN) {
  1309. if ((UA.opera || UA.gecko || UA.ie) && sType === KEYDOWN && !menuNav._preventClickHandle) {
  1310. // Prevent the browser from following the URL of
  1311. // the anchor element
  1312. menuNav._preventClickHandle = menuNav._rootMenu.on("click", function (event) {
  1313. event.preventDefault();
  1314. menuNav._preventClickHandle.detach();
  1315. menuNav._preventClickHandle = null;
  1316. });
  1317. }
  1318. if (sType == MOUSEDOWN) {
  1319. // Prevent the target from getting focused by
  1320. // default, since the element to be focused will
  1321. // be determined by weather or not the submenu
  1322. // is visible.
  1323. event.preventDefault();
  1324. // FocusManager will attempt to focus any
  1325. // descendant that is the target of the mousedown
  1326. // event. Since we want to explicitly control
  1327. // where focus is going, we need to call
  1328. // "stopImmediatePropagation" to stop the
  1329. // FocusManager from doing its thing.
  1330. event.stopImmediatePropagation();
  1331. // The "_focusItem" method relies on the
  1332. // "_hasFocus" property being set to true. The
  1333. // "_hasFocus" property is normally set via a
  1334. // "focus" event listener, but since we've
  1335. // blocked focus from happening, we need to set
  1336. // this property manually.
  1337. menuNav._hasFocus = true;
  1338. }
  1339. if (menuNav._isRoot(getParentMenu(oTarget))) { // Event target is a submenu label in the root menu
  1340. // Menu label toggle functionality
  1341. if (hasVisibleSubmenu(oMenuLabel)) {
  1342. menuNav._hideMenu(oSubmenu);
  1343. menuNav._focusItem(oMenuLabel);
  1344. menuNav._setActiveItem(oMenuLabel);
  1345. }
  1346. else {
  1347. menuNav._hideAllSubmenus(menuNav._rootMenu);
  1348. menuNav._showMenu(oSubmenu);
  1349. menuNav._focusItem(getFirstItem(oSubmenu));
  1350. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1351. }
  1352. }
  1353. else { // Event target is a submenu label within a submenu
  1354. if (menuNav._activeItem == oMenuLabel) {
  1355. menuNav._showMenu(oSubmenu);
  1356. menuNav._focusItem(getFirstItem(oSubmenu));
  1357. menuNav._setActiveItem(getFirstItem(oSubmenu));
  1358. }
  1359. else {
  1360. if (!oMenuLabel._clickHandle) {
  1361. oMenuLabel._clickHandle = oMenuLabel.on("click", function () {
  1362. menuNav._hideAllSubmenus(menuNav._rootMenu);
  1363. menuNav._hasFocus = false;
  1364. menuNav._clearActiveItem();
  1365. oMenuLabel._clickHandle.detach();
  1366. oMenuLabel._clickHandle = null;
  1367. });
  1368. }
  1369. }
  1370. }
  1371. }
  1372. if (sType === CLICK) {
  1373. // Prevent the browser from following the URL of
  1374. // the anchor element
  1375. event.preventDefault();
  1376. }
  1377. }
  1378. }
  1379. }
  1380. }
  1381. },
  1382. /**
  1383. * @method _onKeyPress
  1384. * @description "keypress" event handler for the menu.
  1385. * @protected
  1386. * @param {Object} event Object representing the DOM event.
  1387. */
  1388. _onKeyPress: function (event) {
  1389. switch (event.keyCode) {
  1390. case 37: // left arrow
  1391. case 38: // up arrow
  1392. case 39: // right arrow
  1393. case 40: // down arrow
  1394. // Prevent the browser from scrolling the window
  1395. event.preventDefault();
  1396. break;
  1397. }
  1398. },
  1399. /**
  1400. * @method _onKeyDown
  1401. * @description "keydown" event handler for the menu.
  1402. * @protected
  1403. * @param {Object} event Object representing the DOM event.
  1404. */
  1405. _onKeyDown: function (event) {
  1406. var menuNav = this,
  1407. oActiveItem = menuNav._activeItem,
  1408. oTarget = event.target,
  1409. oActiveMenu = getParentMenu(oTarget),
  1410. oSubmenu;
  1411. if (oActiveMenu) {
  1412. menuNav._activeMenu = oActiveMenu;
  1413. if (isHorizontalMenu(oActiveMenu)) {
  1414. menuNav._onHorizontalMenuKeyDown(event);
  1415. }
  1416. else {
  1417. menuNav._onVerticalMenuKeyDown(event);
  1418. }
  1419. if (event.keyCode === 27) {
  1420. if (!menuNav._isRoot(oActiveMenu)) {
  1421. if (UA.opera) {
  1422. later(0, menuNav, function () {
  1423. menuNav._hideMenu(oActiveMenu, true);
  1424. });
  1425. }
  1426. else {
  1427. menuNav._hideMenu(oActiveMenu, true);
  1428. }
  1429. event.stopPropagation();
  1430. menuNav._blockMouseEvent = UA.gecko ? true : false;
  1431. }
  1432. else if (oActiveItem) {
  1433. if (isMenuLabel(oActiveItem) &&
  1434. hasVisibleSubmenu(oActiveItem)) {
  1435. oSubmenu = oActiveItem.next();
  1436. if (oSubmenu) {
  1437. menuNav._hideMenu(oSubmenu);
  1438. }
  1439. }
  1440. else {
  1441. menuNav._focusManager.blur();
  1442. // This is necessary for Webkit since blurring the
  1443. // active menuitem won't result in the document
  1444. // gaining focus, meaning the that _onDocFocus
  1445. // listener won't clear the active menuitem.
  1446. menuNav._clearActiveItem();
  1447. menuNav._hasFocus = false;
  1448. }
  1449. }
  1450. }
  1451. }
  1452. },
  1453. /**
  1454. * @method _onDocMouseDown
  1455. * @description "mousedown" event handler for the owner document of
  1456. * the menu.
  1457. * @protected
  1458. * @param {Object} event Object representing the DOM event.
  1459. */
  1460. _onDocMouseDown: function (event) {
  1461. var menuNav = this,
  1462. oRoot = menuNav._rootMenu,
  1463. oTarget = event.target;
  1464. if (!(oRoot.compareTo(oTarget) || oRoot.contains(oTarget))) {
  1465. menuNav._hideAllSubmenus(oRoot);
  1466. // Document doesn't receive focus in Webkit when the user mouses
  1467. // down on it, so the "_hasFocus" property won't get set to the
  1468. // correct value. The following line corrects the problem.
  1469. if (UA.webkit) {
  1470. menuNav._hasFocus = false;
  1471. menuNav._clearActiveItem();
  1472. }
  1473. }
  1474. }
  1475. });
  1476. Y.namespace('Plugin');
  1477. Y.Plugin.NodeMenuNav = NodeMenuNav;