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