source: Dev/trunk/src/client/dijit/a11y.js @ 527

Last change on this file since 527 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 6.0 KB
Line 
1define([
2        "dojo/_base/array", // array.forEach array.map
3        "dojo/dom",                     // dom.byId
4        "dojo/dom-attr", // domAttr.attr domAttr.has
5        "dojo/dom-style", // domStyle.style
6        "dojo/_base/lang", // lang.mixin()
7        "dojo/sniff", // has("ie") has("extend-dojo")
8        "./main"        // for exporting methods to dijit namespace
9], function(array, dom, domAttr, domStyle, lang, has, dijit){
10
11        // module:
12        //              dijit/a11y
13
14        var undefined;
15
16        var a11y = {
17                // summary:
18                //              Accessibility utility functions (keyboard, tab stops, etc.)
19
20                _isElementShown: function(/*Element*/ elem){
21                        var s = domStyle.get(elem);
22                        return (s.visibility != "hidden")
23                                && (s.visibility != "collapsed")
24                                && (s.display != "none")
25                                && (domAttr.get(elem, "type") != "hidden");
26                },
27
28                hasDefaultTabStop: function(/*Element*/ elem){
29                        // summary:
30                        //              Tests if element is tab-navigable even without an explicit tabIndex setting
31
32                        // No explicit tabIndex setting, need to investigate node type
33                        switch(elem.nodeName.toLowerCase()){
34                                case "a":
35                                        // An <a> w/out a tabindex is only navigable if it has an href
36                                        return domAttr.has(elem, "href");
37                                case "area":
38                                case "button":
39                                case "input":
40                                case "object":
41                                case "select":
42                                case "textarea":
43                                        // These are navigable by default
44                                        return true;
45                                case "iframe":
46                                        // If it's an editor <iframe> then it's tab navigable.
47                                        var body;
48                                        try{
49                                                // non-IE
50                                                var contentDocument = elem.contentDocument;
51                                                if("designMode" in contentDocument && contentDocument.designMode == "on"){
52                                                        return true;
53                                                }
54                                                body = contentDocument.body;
55                                        }catch(e1){
56                                                // contentWindow.document isn't accessible within IE7/8
57                                                // if the iframe.src points to a foreign url and this
58                                                // page contains an element, that could get focus
59                                                try{
60                                                        body = elem.contentWindow.document.body;
61                                                }catch(e2){
62                                                        return false;
63                                                }
64                                        }
65                                        return body && (body.contentEditable == 'true' ||
66                                                (body.firstChild && body.firstChild.contentEditable == 'true'));
67                                default:
68                                        return elem.contentEditable == 'true';
69                        }
70                },
71
72                effectiveTabIndex: function(/*Element*/ elem){
73                        // summary:
74                        //              Returns effective tabIndex of an element, either a number, or undefined if element isn't focusable.
75
76                        if(domAttr.get(elem, "disabled")){
77                                return undefined;
78                        }else if(domAttr.has(elem, "tabIndex")){
79                                // Explicit tab index setting
80                                return +domAttr.get(elem, "tabIndex");// + to convert string --> number
81                        }else{
82                                // No explicit tabIndex setting, so depends on node type
83                                return a11y.hasDefaultTabStop(elem) ? 0 : undefined;
84                        }
85                },
86
87                isTabNavigable: function(/*Element*/ elem){
88                        // summary:
89                        //              Tests if an element is tab-navigable
90
91                        return a11y.effectiveTabIndex(elem) >= 0;
92                },
93
94                isFocusable: function(/*Element*/ elem){
95                        // summary:
96                        //              Tests if an element is focusable by tabbing to it, or clicking it with the mouse.
97
98                        return a11y.effectiveTabIndex(elem) >= -1;
99                },
100
101                _getTabNavigable: function(/*DOMNode*/ root){
102                        // summary:
103                        //              Finds descendants of the specified root node.
104                        // description:
105                        //              Finds the following descendants of the specified root node:
106                        //
107                        //              - the first tab-navigable element in document order
108                        //                without a tabIndex or with tabIndex="0"
109                        //              - the last tab-navigable element in document order
110                        //                without a tabIndex or with tabIndex="0"
111                        //              - the first element in document order with the lowest
112                        //                positive tabIndex value
113                        //              - the last element in document order with the highest
114                        //                positive tabIndex value
115                        var first, last, lowest, lowestTabindex, highest, highestTabindex, radioSelected = {};
116
117                        function radioName(node){
118                                // If this element is part of a radio button group, return the name for that group.
119                                return node && node.tagName.toLowerCase() == "input" &&
120                                        node.type && node.type.toLowerCase() == "radio" &&
121                                        node.name && node.name.toLowerCase();
122                        }
123
124                        var shown = a11y._isElementShown, effectiveTabIndex = a11y.effectiveTabIndex;
125                        var walkTree = function(/*DOMNode*/ parent){
126                                for(var child = parent.firstChild; child; child = child.nextSibling){
127                                        // Skip text elements, hidden elements, and also non-HTML elements (those in custom namespaces) in IE,
128                                        // since show() invokes getAttribute("type"), which crash on VML nodes in IE.
129                                        if(child.nodeType != 1 || (has("ie") <= 9 && child.scopeName !== "HTML") || !shown(child)){
130                                                continue;
131                                        }
132
133                                        var tabindex = effectiveTabIndex(child);
134                                        if(tabindex >= 0){
135                                                if(tabindex == 0){
136                                                        if(!first){
137                                                                first = child;
138                                                        }
139                                                        last = child;
140                                                }else if(tabindex > 0){
141                                                        if(!lowest || tabindex < lowestTabindex){
142                                                                lowestTabindex = tabindex;
143                                                                lowest = child;
144                                                        }
145                                                        if(!highest || tabindex >= highestTabindex){
146                                                                highestTabindex = tabindex;
147                                                                highest = child;
148                                                        }
149                                                }
150                                                var rn = radioName(child);
151                                                if(domAttr.get(child, "checked") && rn){
152                                                        radioSelected[rn] = child;
153                                                }
154                                        }
155                                        if(child.nodeName.toUpperCase() != 'SELECT'){
156                                                walkTree(child);
157                                        }
158                                }
159                        };
160                        if(shown(root)){
161                                walkTree(root);
162                        }
163                        function rs(node){
164                                // substitute checked radio button for unchecked one, if there is a checked one with the same name.
165                                return radioSelected[radioName(node)] || node;
166                        }
167
168                        return { first: rs(first), last: rs(last), lowest: rs(lowest), highest: rs(highest) };
169                },
170
171                getFirstInTabbingOrder: function(/*String|DOMNode*/ root, /*Document?*/ doc){
172                        // summary:
173                        //              Finds the descendant of the specified root node
174                        //              that is first in the tabbing order
175                        var elems = a11y._getTabNavigable(dom.byId(root, doc));
176                        return elems.lowest ? elems.lowest : elems.first; // DomNode
177                },
178
179                getLastInTabbingOrder: function(/*String|DOMNode*/ root, /*Document?*/ doc){
180                        // summary:
181                        //              Finds the descendant of the specified root node
182                        //              that is last in the tabbing order
183                        var elems = a11y._getTabNavigable(dom.byId(root, doc));
184                        return elems.last ? elems.last : elems.highest; // DomNode
185                }
186        };
187
188        has("extend-dojo") && lang.mixin(dijit, a11y);
189
190        return a11y;
191});
Note: See TracBrowser for help on using the repository browser.