a11y.js 6.0 KB

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