This example demonstrates how use the WAI-ARIA Roles and States as implemented for Firefox 3 and Internet Explorer 8 with the YUI Menu Control.
Begin by defining an anonymous function in order to keep all variables out of the global scope. Inside the anonymous function, define some shortcuts to widgets and utils that will be used frequently as well as an array of MenuItem configuration properties that describe each item in the MenuBar.
1 | (function () { |
2 | |
3 | var Event = YAHOO.util.Event, |
4 | Dom = YAHOO.util.Dom, |
5 | MenuBar = YAHOO.widget.MenuBar, |
6 | |
7 | |
8 | /* |
9 | Define an array of object literals, each containing |
10 | the data necessary to create the items for a MenuBar. |
11 | */ |
12 | |
13 | aItemData = [ |
14 | |
15 | { text: "File", submenu: { id: "filemenu", itemdata: [ |
16 | |
17 | { text: "New File", helptext: "Ctrl + N" }, |
18 | "New Folder", |
19 | { text: "Open", helptext: "Ctrl + O" }, |
20 | { text: "Open With...", submenu: { id: "applications", itemdata: [ |
21 | "Application 1", |
22 | "Application 2", |
23 | "Application 3", |
24 | "Application 4" |
25 | ] } |
26 | }, |
27 | { text: "Print", helptext: "Ctrl + P" } |
28 | |
29 | ] } |
30 | |
31 | }, |
32 | |
33 | { text: "Edit", submenu: { id: "editmenu", itemdata: [ |
34 | |
35 | [ |
36 | { text: "Undo", helptext: "Ctrl + Z" }, |
37 | { text: "Redo", helptext: "Ctrl + Y" } |
38 | ], |
39 | |
40 | [ |
41 | { text: "Cut", helptext: "Ctrl + X" }, |
42 | { text: "Copy", helptext: "Ctrl + C" }, |
43 | { text: "Paste", helptext: "Ctrl + V" }, |
44 | { text: "Delete", helptext: "Del" } |
45 | ], |
46 | |
47 | [ { text: "Select All", helptext: "Ctrl + A" } ], |
48 | |
49 | [ |
50 | { text: "Find", helptext: "Ctrl + F" }, |
51 | { text: "Find Again", helptext: "Ctrl + G" } |
52 | ] |
53 | |
54 | ] } |
55 | |
56 | } |
57 | |
58 | ]; |
59 | |
60 | }()); |
view plain | print | ? |
Next, use the onDOMReady
method of the YUI Event Utility
to instantiate the MenuBar as soon as the
page's DOM is available for scripting.
1 | /* |
2 | Initialize and render the MenuBar when the page's DOM is ready |
3 | to be scripted. |
4 | */ |
5 | |
6 | Event.onDOMReady(function () { |
7 | |
8 | /* |
9 | Instantiate a MenuBar: The first argument passed to the constructor is |
10 | the id of the HTML to be created that will represent the MenuBar; the |
11 | second is an object literal of configuration properties. |
12 | */ |
13 | |
14 | var oMenuBar = new MenuBar("mymenubar", { lazyload: true, itemdata: aItemData }); |
15 | |
16 | |
17 | /* |
18 | Add a "show" event listener that keeps the left-most |
19 | submenu against the left edge of the browser viewport. |
20 | */ |
21 | |
22 | function onSubmenuShow() { |
23 | |
24 | var oIFrame; |
25 | |
26 | |
27 | if (this.id == "filemenu") { |
28 | |
29 | Dom.setX(this.element, 0); |
30 | |
31 | oIFrame = this.iframe; |
32 | |
33 | |
34 | if (oIFrame) { |
35 | |
36 | Dom.setX(oIFrame, 0); |
37 | |
38 | } |
39 | |
40 | this.cfg.setProperty("x", 0, true); |
41 | |
42 | } |
43 | |
44 | } |
45 | |
46 | |
47 | // Subscribe to the "show" event for each submenu |
48 | |
49 | oMenuBar.subscribe("show", onSubmenuShow); |
50 | |
51 | }); |
view plain | print | ? |
Lastly, apply the WAI-ARIA Roles and States to a Menu widget via a "render" event listener. Waiting for a Menu's "render" event to fire ensures that all of its DOM elements have been appended to the document and are available to be scripted. Roles and states are added to a Menu's DOM elements via the DOM setAttribute method.
1 | /* |
2 | Create a variable to store a reference to the currently focused MenuItem |
3 | label, or the MenuItem label that CAN be focused by the user. |
4 | */ |
5 | |
6 | var oCurrentItemLabel; |
7 | |
8 | |
9 | /* |
10 | Initialize and render the MenuBar when the page's DOM is ready |
11 | to be scripted. |
12 | */ |
13 | |
14 | Event.onDOMReady(function () { |
15 | |
16 | /* |
17 | Instantiate a MenuBar: The first argument passed to the constructor is |
18 | the id of the HTML to be created that will represent the MenuBar; the |
19 | second is an object literal of configuration properties. |
20 | */ |
21 | |
22 | var oMenuBar = new MenuBar("mymenubar", { lazyload: true, itemdata: aItemData }); |
23 | |
24 | |
25 | /* |
26 | Add a "show" event listener that keeps the left-most |
27 | submenu against the left edge of the browser viewport. |
28 | */ |
29 | |
30 | function onSubmenuShow() { |
31 | |
32 | var oIFrame; |
33 | |
34 | |
35 | if (this.id == "filemenu") { |
36 | |
37 | Dom.setX(this.element, 0); |
38 | |
39 | oIFrame = this.iframe; |
40 | |
41 | |
42 | if (oIFrame) { |
43 | |
44 | Dom.setX(oIFrame, 0); |
45 | |
46 | } |
47 | |
48 | this.cfg.setProperty("x", 0, true); |
49 | |
50 | } |
51 | |
52 | } |
53 | |
54 | |
55 | // Subscribe to the "show" event for each submenu |
56 | |
57 | oMenuBar.subscribe("show", onSubmenuShow); |
58 | |
59 | |
60 | /* |
61 | Create a variable to store a reference to the currently focused MenuItem |
62 | label, or the MenuItem label that CAN be focused by the user. |
63 | */ |
64 | |
65 | var oCurrentItemLabel; |
66 | |
67 | |
68 | /* |
69 | Only apply the WAI-ARIA Roles and States for FF 3 and IE 8 since those |
70 | are the only browsers that support them. |
71 | */ |
72 | |
73 | if ((UA.gecko && UA.gecko >= 1.9) || (UA.ie && UA.ie >= 8)) { |
74 | |
75 | /* |
76 | Add the WAI-ARIA Roles and States to the MenuBar's DOM elements once it |
77 | is rendered. |
78 | */ |
79 | |
80 | oMenuBar.subscribe("render", function () { |
81 | |
82 | /* |
83 | Apply the "role" attribute of "menu" or "menubar" depending on the |
84 | type of the Menu control being rendered. |
85 | */ |
86 | |
87 | this.element.setAttribute("role", |
88 | (this instanceof MenuBar ? "menubar" : "menu")); |
89 | |
90 | |
91 | /* |
92 | Apply the appropriate "role" and "aria-[state]" attributes to the |
93 | label of each MenuItem instance. |
94 | */ |
95 | |
96 | var aMenuItems = this.getItems(), |
97 | i = aMenuItems.length - 1, |
98 | oMenuItem, |
99 | oMenuItemLabel; |
100 | |
101 | |
102 | do { |
103 | |
104 | oMenuItem = aMenuItems[i]; |
105 | |
106 | |
107 | /* |
108 | Retrieve a reference to the anchor element that serves as the |
109 | label for each MenuItem. |
110 | */ |
111 | |
112 | oMenuItemLabel = oMenuItem.element.firstChild; |
113 | |
114 | |
115 | // Set the "role" attribute of the label to "menuitem" |
116 | |
117 | oMenuItemLabel.setAttribute("role", "menuitem"); |
118 | |
119 | |
120 | // Remove the label from the browser's default tab order |
121 | |
122 | oMenuItemLabel.tabIndex = -1; |
123 | |
124 | |
125 | /* |
126 | Optional: JAWS announces the value of each anchor element's |
127 | "href" attribute when it recieves focus. If the MenuItem |
128 | instance's "url" attribute is set to the default, remove the |
129 | attribute so that JAWS doesn't announce its value. |
130 | */ |
131 | |
132 | if (oMenuItem.cfg.getProperty("url") == "#") { |
133 | |
134 | oMenuItemLabel.removeAttribute("href"); |
135 | |
136 | } |
137 | |
138 | |
139 | /* |
140 | If the MenuItem has a submenu, set the "aria-haspopup" |
141 | attribute to true so that the screen reader can announce |
142 | */ |
143 | |
144 | if (oMenuItem.cfg.getProperty("submenu")) { |
145 | |
146 | oMenuItemLabel.setAttribute("aria-haspopup", true); |
147 | |
148 | } |
149 | |
150 | } |
151 | while (i--); |
152 | |
153 | |
154 | /* |
155 | Set the "tabindex" of the first MenuItem's label to 0 so the user |
156 | can easily tab into and out of the control. |
157 | */ |
158 | |
159 | if (this.getRoot() == this) { |
160 | |
161 | oCurrentItemLabel = this.getItem(0).element.firstChild; |
162 | |
163 | oCurrentItemLabel.tabIndex = 0; |
164 | |
165 | } |
166 | |
167 | }); |
168 | |
169 | |
170 | function onMenuItemFocus(p_oEvent) { |
171 | |
172 | // The currently focused element |
173 | |
174 | var oTarget = Event.getTarget(p_oEvent); |
175 | |
176 | |
177 | if (Dom.isAncestor(oMenuBar.element, oTarget)) { |
178 | |
179 | /* |
180 | Modify value of the tabIndex attribute so that the currently |
181 | focused MenuItem label is in the browser's default tab order. |
182 | */ |
183 | |
184 | if (oCurrentItemLabel) { |
185 | |
186 | oCurrentItemLabel.tabIndex = -1; |
187 | |
188 | } |
189 | |
190 | oCurrentItemLabel = oTarget; |
191 | oCurrentItemLabel.tabIndex = 0; |
192 | |
193 | } |
194 | else { |
195 | |
196 | /* |
197 | If the focus has moved to an element on the page that is not a |
198 | part of the MenuBar, restore the MenuBar to its original state |
199 | so that the first item is in the browser's default tab index. |
200 | */ |
201 | |
202 | oCurrentItemLabel.tabIndex = -1; |
203 | |
204 | oCurrentItemLabel = oMenuBar.getItem(0).element.firstChild; |
205 | oCurrentItemLabel.tabIndex = 0; |
206 | |
207 | } |
208 | |
209 | } |
210 | |
211 | |
212 | // Subscribe to the focus event. |
213 | |
214 | if (UA.ie) { |
215 | |
216 | /* |
217 | Since the "focus" event doesn't bubble, and IE doesn't support |
218 | listening for the capture phase of events, subscribe to the |
219 | "focusin" event, which DOES bubble. |
220 | */ |
221 | |
222 | Event.on(document, "focusin", onMenuItemFocus); |
223 | |
224 | } |
225 | else { |
226 | |
227 | /* |
228 | Since the "focus" event doesn't bubble, we'll use |
229 | event capture for Gecko, Webkit and Opera. |
230 | */ |
231 | |
232 | document.addEventListener("focus", onMenuItemFocus, true); |
233 | |
234 | } |
235 | |
236 | } |
237 | |
238 | |
239 | /* |
240 | Since this MenuBar instance is built completely from |
241 | script, call the "render" method passing in a node |
242 | reference for the DOM element that its should be |
243 | appended to. |
244 | */ |
245 | |
246 | oMenuBar.render(document.body); |
view plain | print | ? |
Once the WAI-ARIA Roles and States are applied, there are a few tweaks that can be made to the
Menu's DOM elements to further improve the user experience. For Menu, the label of each MenuItem
instance is represented in HTML as an anchor element
(i.e. <a class="yuimenuitemlabel">
), and in IE and Firefox, anchor elements
are automatically part of the tab order. Having MenuItem labels in the tab order by default is
important when JavaScript is disabled to ensure that the user can navigate a Menu via the keyboard
with the tab key.
Since the Menu code provides its own, desktop-like keyboard functionality when JavaScript is enabled, having every MenuItem label in the browser's default tab order can be a nuisance to users of screen readers. When navigating the document with the tab key, users of screen readers have to tab through every single MenuItem label in a Menu, regardless of whether or not they want to use the Menu Control. This problem can be solved by setting the "tabindex" attribute of every MenuItem label but the first to a value of -1. Setting an element's "tabindex" attribute to a value of -1 removes it from the browser's default tab order, while maintaining its focusability via JavaScript. Since the YUI Menu keyboard functionality is activated when any MenuItem label has focus, with just one MenuItem label in the browser's default tab order the Menu's keyboard functionality will be preserved, while at the same time giving the user the ability to quickly tab into and out of the control.
Copyright © 2008 Yahoo! Inc. All rights reserved.
Privacy Policy - Terms of Service - Copyright Policy - Job Openings