1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<div class="intro">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove This example illustrates how to use the Focus Manager Node Plugin,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Event's <a href="{{apiDocs}}/classes/YUI.html#event_delegate">delegation support</a> and
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <a href="{{apiDocs}}/classes/YUI.html#event_mouseenter">mouseenter</a> event, along with
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove the <a href="../overlay/index.html">Overlay widget</a> and Node's support for the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <a href="http://www.w3.org/TR/wai-aria/">WAI-ARIA Roles and States</a> to
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove create an accessible menu button.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<div class="example">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <style scoped>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove /* The following two styles are necessary to override style rules in the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove YUI CSS file. */
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove .example ul {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove .example a:hover {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove text-decoration: none;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove /* Hide the button and list while it is being transformed into a menu button. */
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove .yui3-js-enabled .yui3-menubutton-loading #menu-1,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove .yui3-js-enabled .yui3-menubutton-loading #button-1 {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove display: none;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove {{>node-focusmanager-3-source}}
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<h2>Setting Up the HTML</h2>
64e210505dea375c7663112bb34ae4f378a27e29Derek GathrightFor a menu button, start with an `<a>` element whose
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright`href` attribute is set to the id of an `<div>`
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathrightthat wraps a list of `<input>` elements.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveTherefore, without JavaScript and CSS, the menu button is simply an in-page
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovelink to a set of additional buttons.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<div class="yui3-menubutton-loading">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <a id="button-1" href="#menu-1"><span><span>Move To</span></span></a>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <div id="menu-1">
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-1" value="Inbox"></li>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-2" value="Archive"></li>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove <li><input type="button" name="button-3" value="Trash"></li>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<h2>Progressive Enhancement</h2>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveTo account for the scenario where the user has CSS enabled in their browser but JavaScript
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Groveis disabled, the CSS used to style the menu button will be loaded via JavaScript
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Groveusing the YUI instance's <a href="../yui/index.html#loader">built-in Loader</a>.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove "menubuttoncss": {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove type: "css",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove fullpath: "{{componentAssets}}/menubutton.css"
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove "menubuttonjs": {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove fullpath: "{{componentAssets}}/menubutton.js",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove requires: ["menubuttoncss", "node-focusmanager", "node-event-simulate", "overlay"]
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove}).use("menubuttonjs");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveTo prevent the user from seeing a flash unstyled content when JavaScript is enabled,
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathrighta style rule is created using YUI's `yui3-js-enabled` class name that will temporarily
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovehide the markup while the JavaScript and CSS are in the process of loading. For more on using the
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright`yui3-js-enabled` class name, see the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<a href="../widget/index.html#progressive">Hiding Progressively Enhanced Markup</a> section of the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<a href="../widget/index.html">YUI Widget landing page</a>.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove/* Hide the button and list while it is being transformed into a menu button. */
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove.yui3-js-enabled .yui3-menubutton-loading #menu-1,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove.yui3-js-enabled .yui3-menubutton-loading #button-1 {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove display: none;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<h2>ARIA Support</h2>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveThrough the use of CSS and JavaScript the HTML for the menu button can be
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovetransformed into something that looks and behaves like a desktop menu button,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovebut users of screen readers won't perceive it as an atomic widget, but rather
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovesimply as a set of HTML elements. However, through the application
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<a href="http://www.w3.org/TR/wai-aria/">WAI-ARIA Roles and States</a>, it is
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovepossible to improve the semantics of the markup such that users of screen
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovereaders perceive it as a menu button control.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove<h2>Keyboard Functionality</h2>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveThe keyboard functionality for the button's menu will be provided by the Focus
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveManager Node Plugin. The Focus Manager's
f7da8d036724b1c753c75badbf8758f6ef48c0a0Derek Gathright<a href="{{apiDocs}}/classes/plugin.NodeFocusManager.html#attr_descendants">`descendants`</a>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Groveattribute is set to a value of "input", so that only one menuitem in the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovebutton's menu is in the browser's default tab flow. This allows users
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovenavigating via the keyboard to use the tab key to quickly move into and out of
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovethe menu. Once the menu has focus, the user can move focus among each menuitem
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Groveusing the up and down arrows keys, as defined by the value of the
f7da8d036724b1c753c75badbf8758f6ef48c0a0Derek Gathright<a href="{{apiDocs}}/classes/plugin.NodeFocusManager.html#attr_keys">`keys`</a>
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Groveattribute. The
f7da8d036724b1c753c75badbf8758f6ef48c0a0Derek Gathright<a href="{{apiDocs}}/classes/plugin.NodeFocusManager.html#attr_focusClass">`focusClass`</a>
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathrightattribute is used to apply a class of `yui-menuitem-active` to
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathrightthe parent `<li>` of each
64e210505dea375c7663112bb34ae4f378a27e29Derek Gathright`<input>` when it is focused, making it easy to style the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grovemenuitem's focused state in all browsers.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan GroveYUI().use("*", function (Y) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var menuButton = Y.one("#button-1"),
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var initMenu = function () {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove contentBox: "#menu-1",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove visible: false,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove tabIndex: null
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Y.one("#menu-1").setStyle("display", "");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var boundingBox = menu.get("boundingBox"),
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove contentBox = menu.get("contentBox");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.addClass("yui3-buttonmenu");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove contentBox.addClass("yui3-buttonmenu-content");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Append a decorator element to the bounding box to render the shadow.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.append('<div class="yui3-menu-shadow"></div>');
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Apply the ARIA roles, states and properties to the menu.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove role: "menu",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove "aria-labelledby": menuLabelID
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.all("input").set("role", "menuitem");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // For NVDA: Add the role of "presentation" to each LI
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // element to prevent NVDA from announcing the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // "listitem" role.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.all("div,ul,li").set("role", "presentation");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Use the FocusManager Node Plugin to manage the focusability
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // of each menuitem in the menu.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove descendants: "input",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove keys: { next: "down:40", // Down arrow
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove previous: "down:38" }, // Up arrow
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove focusClass: {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove className: "yui3-menuitem-active",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove fn: function (node) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove return node.get("parentNode");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove circular: true
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Subscribe to the change event for the "focused" attribute
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // to listen for when the menu initially gains focus, and
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // when the menu has lost focus completely.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove contentBox.focusManager.after("focusedChange", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (!event.newVal) { // The menu has lost focus
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Set the "activeDescendant" attribute to 0 when the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // menu is hidden so that the user can tab from the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // button to the first item in the menu the next time
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // the menu is made visible.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove this.set("activeDescendant", 0);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Hide the button's menu if the user presses the escape key
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // while focused either on the button or its menu.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Y.on("key", function () {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove }, [menuButton, boundingBox] ,"down:27");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Set the width and height of the menu's bounding box -
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // this is necessary for IE 6 so that the CSS for the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // shadow element can simply set the shadow's width and
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // height to 100% to ensure that dimensions of the shadow
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // are always sync'd to the that of its parent menu.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menu.on("visibleChange", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.setStyles({ height: "", width: "" });
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove height: (boundingBox.get("offsetHeight") + "px"),
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove width: (boundingBox.get("offsetWidth") + "px") });
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menu.after("visibleChange", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var bVisible = event.newVal;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Focus the first item when the menu is made visible
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // to allow users to navigate the menu via the keyboard
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (bVisible) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Need to set focus via a timer for Webkit and Opera
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.set("aria-hidden", (!bVisible));
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Hide the menu when one of menu items is clicked.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.delegate("click", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove alert("You clicked " + this.one("input").get("value"));
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Focus each menuitem as the user moves the mouse over
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // the menu.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.delegate("mouseenter", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var focusManager = contentBox.focusManager;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (focusManager.get("focused")) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Hide the menu if the user clicks outside of it or if the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // user doesn't click on the button
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove boundingBox.get("ownerDocument").on("mousedown", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var oTarget = event.target;
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (!oTarget.compareTo(menuButton) &&
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove !oTarget.compareTo(boundingBox) &&
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.addClass("yui3-menubutton");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Hide the list until it is transformed into a menu
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Y.one("#menu-1").setStyle("display", "none");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Remove the "yui3-menubutton-loading" class from the parent container
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // now that the necessary YUI dependencies are loaded and the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // menu button has been skinned.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.ancestor(".yui3-menubutton-loading").removeClass("yui3-menubutton-loading");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Apply the ARIA roles, states and properties to the anchor.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove role: "button",
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove "aria-haspopup": true
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Remove the "href" attribute from the anchor element to
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // prevent JAWS and NVDA from reading the value of the "href"
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // attribute when the anchor is focused.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Since the anchor's "href" attribute has been removed, the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // element will not fire the click event in Firefox when the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // user presses the enter key. To fix this, dispatch the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // "click" event to the anchor when the user presses the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // enter key.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove Y.on("key", function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove }, menuButton, "down:13");
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Set the "tabIndex" attribute of the anchor element to 0 to
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // place it in the browser's default tab flow. This is
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // necessary since 1) anchor elements are not in the default
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // tab flow in Opera and 2) removing the "href" attribute
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // prevents the anchor from firing its "click" event
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // in Firefox.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.set("tabIndex", 0);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Since there is some intermediary markup (<span>s) between the anchor element with the role
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // of "button" applied and the text label for the anchor - we need to use the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // "aria-labelledby" attribute to ensure that screen readers announce the text label for the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var menuLabel = menuButton.one("span span"),
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuLabelID = Y.stamp(menuLabel);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuLabel.set("id", menuLabelID);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.set("aria-labelledby", menuLabelID);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove var showMenu = function (event) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // For performance: Defer the creation of the menu until
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // the first time the button is clicked.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (!menu) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove if (!menu.get("visible")) {
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove node: menuButton,
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove points: [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.BL]
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Prevent the anchor element from being focused
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // when the users mouses down on it.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // Bind both a "mousedown" and "click" event listener to
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // ensure the button's menu can be invoked using both the
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove // mouse and the keyboard.
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.on("mousedown", showMenu);
5f9cae5c825d76bdc95b78301e460a46ec5fbdf4Ryan Grove menuButton.on("click", showMenu);