|
1 /* |
|
2 YUI 3.10.3 (build 2fb5187) |
|
3 Copyright 2013 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 < 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.10.3', {"requires": ["node", "classnamemanager", "plugin", "node-focusmanager"], "skinnable": true}); |