|
1 YUI().use("*", function (Y) { |
|
2 |
|
3 var menuButton = Y.one("#button-1"), |
|
4 menu; |
|
5 |
|
6 |
|
7 var initMenu = function () { |
|
8 |
|
9 menu = new Y.Overlay({ |
|
10 contentBox: "#menu-1", |
|
11 visible: false, |
|
12 tabIndex: null |
|
13 }); |
|
14 |
|
15 menu.render(); |
|
16 |
|
17 |
|
18 Y.one("#menu-1").setStyle("display", ""); |
|
19 |
|
20 var boundingBox = menu.get("boundingBox"), |
|
21 contentBox = menu.get("contentBox"); |
|
22 |
|
23 boundingBox.addClass("yui-buttonmenu"); |
|
24 contentBox.addClass("yui-buttonmenu-content"); |
|
25 |
|
26 |
|
27 // Append a decorator element to the bounding box to render the shadow. |
|
28 |
|
29 boundingBox.append('<div class="yui-menu-shadow"></div>'); |
|
30 |
|
31 |
|
32 // Apply the ARIA roles, states and properties to the menu. |
|
33 |
|
34 boundingBox.setAttrs({ |
|
35 role: "menu", |
|
36 "aria-labelledby": menuButton.get("id") |
|
37 }); |
|
38 |
|
39 boundingBox.all("input").set("role", "menuitem"); |
|
40 |
|
41 |
|
42 // For NVDA: Add the role of "presentation" to each LI |
|
43 // element to prevent NVDA from announcing the |
|
44 // "listitem" role. |
|
45 |
|
46 boundingBox.all("div,ul,li").set("role", "presentation"); |
|
47 |
|
48 |
|
49 // Use the FocusManager Node Plugin to manage the focusability |
|
50 // of each menuitem in the menu. |
|
51 |
|
52 contentBox.plug(Y.Plugin.NodeFocusManager, { |
|
53 |
|
54 descendants: "input", |
|
55 keys: { next: "down:40", // Down arrow |
|
56 previous: "down:38" }, // Up arrow |
|
57 focusClass: { |
|
58 className: "yui-menuitem-active", |
|
59 fn: function (node) { |
|
60 return node.get("parentNode"); |
|
61 } |
|
62 }, |
|
63 circular: true |
|
64 |
|
65 }); |
|
66 |
|
67 |
|
68 // Subscribe to the change event for the "focused" attribute |
|
69 // to listen for when the menu initially gains focus, and |
|
70 // when the menu has lost focus completely. |
|
71 |
|
72 contentBox.focusManager.after("focusedChange", function (event) { |
|
73 |
|
74 if (!event.newVal) { // The menu has lost focus |
|
75 |
|
76 // Set the "activeDescendant" attribute to 0 when the |
|
77 // menu is hidden so that the user can tab from the |
|
78 // button to the first item in the menu the next time |
|
79 // the menu is made visible. |
|
80 |
|
81 this.set("activeDescendant", 0); |
|
82 |
|
83 } |
|
84 |
|
85 }); |
|
86 |
|
87 |
|
88 // Hide the button's menu if the user presses the escape key |
|
89 // while focused either on the button or its menu. |
|
90 |
|
91 Y.on("key", function () { |
|
92 |
|
93 menu.hide(); |
|
94 menuButton.focus(); |
|
95 |
|
96 }, [menuButton, boundingBox] ,"down:27"); |
|
97 |
|
98 |
|
99 if (Y.UA.ie === 6) { |
|
100 |
|
101 // Set the width and height of the menu's bounding box - |
|
102 // this is necessary for IE 6 so that the CSS for the |
|
103 // shadow element can simply set the shadow's width and |
|
104 // height to 100% to ensure that dimensions of the shadow |
|
105 // are always sync'd to the that of its parent menu. |
|
106 |
|
107 menu.on("visibleChange", function (event) { |
|
108 |
|
109 if (event.newVal) { |
|
110 |
|
111 boundingBox.setStyles({ height: "", width: "" }); |
|
112 |
|
113 boundingBox.setStyles({ |
|
114 height: (boundingBox.get("offsetHeight") + "px"), |
|
115 width: (boundingBox.get("offsetWidth") + "px") }); |
|
116 |
|
117 } |
|
118 |
|
119 }); |
|
120 |
|
121 } |
|
122 |
|
123 |
|
124 menu.after("visibleChange", function (event) { |
|
125 |
|
126 var bVisible = event.newVal; |
|
127 |
|
128 // Focus the first item when the menu is made visible |
|
129 // to allow users to navigate the menu via the keyboard |
|
130 |
|
131 if (bVisible) { |
|
132 |
|
133 // Need to set focus via a timer for Webkit and Opera |
|
134 Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus); |
|
135 |
|
136 } |
|
137 |
|
138 boundingBox.set("aria-hidden", (!bVisible)); |
|
139 |
|
140 }); |
|
141 |
|
142 |
|
143 // Hide the menu when one of menu items is clicked. |
|
144 |
|
145 boundingBox.delegate("click", function (event) { |
|
146 |
|
147 alert("You clicked " + this.query("input").get("value")); |
|
148 |
|
149 contentBox.focusManager.blur(); |
|
150 menu.hide(); |
|
151 |
|
152 }, "li"); |
|
153 |
|
154 |
|
155 // Focus each menuitem as the user moves the mouse over |
|
156 // the menu. |
|
157 |
|
158 boundingBox.delegate("mouseenter", function (event) { |
|
159 |
|
160 var focusManager = contentBox.focusManager; |
|
161 |
|
162 if (focusManager.get("focused")) { |
|
163 focusManager.focus(this.query("input")); |
|
164 } |
|
165 |
|
166 }, "li"); |
|
167 |
|
168 |
|
169 // Hide the menu if the user clicks outside of it or if the |
|
170 // user doesn't click on the button |
|
171 |
|
172 boundingBox.get("ownerDocument").on("mousedown", function (event) { |
|
173 |
|
174 var oTarget = event.target; |
|
175 |
|
176 if (!oTarget.compareTo(menuButton) && |
|
177 !menuButton.contains(oTarget) && |
|
178 !oTarget.compareTo(boundingBox) && |
|
179 !boundingBox.contains(oTarget)) { |
|
180 |
|
181 menu.hide(); |
|
182 |
|
183 } |
|
184 |
|
185 }); |
|
186 |
|
187 }; |
|
188 |
|
189 |
|
190 menuButton.addClass("yui-menubutton"); |
|
191 |
|
192 |
|
193 // Hide the list until it is transformed into a menu |
|
194 |
|
195 Y.one("#menu-1").setStyle("display", "none"); |
|
196 |
|
197 |
|
198 // Remove the "yui-loading" class from the documentElement |
|
199 // now that the necessary YUI dependencies are loaded and the |
|
200 // menu button has been skinned. |
|
201 |
|
202 menuButton.get("ownerDocument").get("documentElement").removeClass("yui-loading"); |
|
203 |
|
204 |
|
205 // Apply the ARIA roles, states and properties to the anchor. |
|
206 |
|
207 menuButton.setAttrs({ |
|
208 role: "button", |
|
209 "aria-haspopup": true |
|
210 }); |
|
211 |
|
212 |
|
213 // Remove the "href" attribute from the anchor element to |
|
214 // prevent JAWS and NVDA from reading the value of the "href" |
|
215 // attribute when the anchor is focused. |
|
216 |
|
217 if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) { |
|
218 |
|
219 menuButton.removeAttribute("href"); |
|
220 |
|
221 // Since the anchor's "href" attribute has been removed, the |
|
222 // element will not fire the click event in Firefox when the |
|
223 // user presses the enter key. To fix this, dispatch the |
|
224 // "click" event to the anchor when the user presses the |
|
225 // enter key. |
|
226 |
|
227 Y.on("key", function (event) { |
|
228 |
|
229 menuButton.simulate("click"); |
|
230 |
|
231 }, menuButton, "down:13"); |
|
232 |
|
233 } |
|
234 |
|
235 |
|
236 // Set the "tabIndex" attribute of the anchor element to 0 to |
|
237 // place it in the browser's default tab flow. This is |
|
238 // necessary since 1) anchor elements are not in the default |
|
239 // tab flow in Opera and 2) removing the "href" attribute |
|
240 // prevents the anchor from firing its "click" event |
|
241 // in Firefox. |
|
242 |
|
243 menuButton.set("tabIndex", 0); |
|
244 |
|
245 |
|
246 var showMenu = function (event) { |
|
247 |
|
248 // For performance: Defer the creation of the menu until |
|
249 // the first time the button is clicked. |
|
250 |
|
251 if (!menu) { |
|
252 initMenu(); |
|
253 } |
|
254 |
|
255 |
|
256 if (!menu.get("visible")) { |
|
257 |
|
258 menu.set("align", { |
|
259 node: menuButton, |
|
260 points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL] |
|
261 }); |
|
262 |
|
263 menu.show(); |
|
264 |
|
265 } |
|
266 |
|
267 // Prevent the anchor element from being focused |
|
268 // when the users mouses down on it. |
|
269 event.preventDefault(); |
|
270 |
|
271 }; |
|
272 |
|
273 |
|
274 // Bind both a "mousedown" and "click" event listener to |
|
275 // ensure the button's menu can be invoked using both the |
|
276 // mouse and the keyboard. |
|
277 |
|
278 menuButton.on("mousedown", showMenu); |
|
279 menuButton.on("click", showMenu); |
|
280 |
|
281 }); |