Yahoo! Developer Network Home - Help

YUI Library Examples: Menu Family: Implementing ARIA Roles and States with Menu

Menu Family: Implementing ARIA Roles and States with Menu

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.

Using ARIA roles and states in the 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 
6Event.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 
6var oCurrentItemLabel; 
7 
8 
9/*
10     Initialize and render the MenuBar when the page's DOM is ready 
11     to be scripted.
12*/ 
13 
14Event.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 
246oMenuBar.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.

Menu Family Examples:

More Menu Family Resources:

Copyright © 2008 Yahoo! Inc. All rights reserved.

Privacy Policy - Terms of Service - Copyright Policy - Job Openings