_KeyNavContainer.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dijit._KeyNavContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dijit._KeyNavContainer"] = true;
  8. dojo.provide("dijit._KeyNavContainer");
  9. dojo.require("dijit._Container");
  10. dojo.declare("dijit._KeyNavContainer",
  11. dijit._Container,
  12. {
  13. // summary:
  14. // A _Container with keyboard navigation of its children.
  15. // description:
  16. // To use this mixin, call connectKeyNavHandlers() in
  17. // postCreate() and call startupKeyNavChildren() in startup().
  18. // It provides normalized keyboard and focusing code for Container
  19. // widgets.
  20. /*=====
  21. // focusedChild: [protected] Widget
  22. // The currently focused child widget, or null if there isn't one
  23. focusedChild: null,
  24. =====*/
  25. // tabIndex: Integer
  26. // Tab index of the container; same as HTML tabIndex attribute.
  27. // Note then when user tabs into the container, focus is immediately
  28. // moved to the first item in the container.
  29. tabIndex: "0",
  30. _keyNavCodes: {},
  31. connectKeyNavHandlers: function(/*dojo.keys[]*/ prevKeyCodes, /*dojo.keys[]*/ nextKeyCodes){
  32. // summary:
  33. // Call in postCreate() to attach the keyboard handlers
  34. // to the container.
  35. // preKeyCodes: dojo.keys[]
  36. // Key codes for navigating to the previous child.
  37. // nextKeyCodes: dojo.keys[]
  38. // Key codes for navigating to the next child.
  39. // tags:
  40. // protected
  41. var keyCodes = (this._keyNavCodes = {});
  42. var prev = dojo.hitch(this, this.focusPrev);
  43. var next = dojo.hitch(this, this.focusNext);
  44. dojo.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
  45. dojo.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
  46. keyCodes[dojo.keys.HOME] = dojo.hitch(this, "focusFirstChild");
  47. keyCodes[dojo.keys.END] = dojo.hitch(this, "focusLastChild");
  48. this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
  49. this.connect(this.domNode, "onfocus", "_onContainerFocus");
  50. },
  51. startupKeyNavChildren: function(){
  52. // summary:
  53. // Call in startup() to set child tabindexes to -1
  54. // tags:
  55. // protected
  56. dojo.forEach(this.getChildren(), dojo.hitch(this, "_startupChild"));
  57. },
  58. addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
  59. // summary:
  60. // Add a child to our _Container
  61. dijit._KeyNavContainer.superclass.addChild.apply(this, arguments);
  62. this._startupChild(widget);
  63. },
  64. focus: function(){
  65. // summary:
  66. // Default focus() implementation: focus the first child.
  67. this.focusFirstChild();
  68. },
  69. focusFirstChild: function(){
  70. // summary:
  71. // Focus the first focusable child in the container.
  72. // tags:
  73. // protected
  74. var child = this._getFirstFocusableChild();
  75. if(child){ // edge case: Menu could be empty or hidden
  76. this.focusChild(child);
  77. }
  78. },
  79. focusLastChild: function(){
  80. // summary:
  81. // Focus the last focusable child in the container.
  82. // tags:
  83. // protected
  84. var child = this._getLastFocusableChild();
  85. if(child){ // edge case: Menu could be empty or hidden
  86. this.focusChild(child);
  87. }
  88. },
  89. focusNext: function(){
  90. // summary:
  91. // Focus the next widget
  92. // tags:
  93. // protected
  94. var child = this._getNextFocusableChild(this.focusedChild, 1);
  95. this.focusChild(child);
  96. },
  97. focusPrev: function(){
  98. // summary:
  99. // Focus the last focusable node in the previous widget
  100. // (ex: go to the ComboButton icon section rather than button section)
  101. // tags:
  102. // protected
  103. var child = this._getNextFocusableChild(this.focusedChild, -1);
  104. this.focusChild(child, true);
  105. },
  106. focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
  107. // summary:
  108. // Focus widget.
  109. // widget:
  110. // Reference to container's child widget
  111. // last:
  112. // If true and if widget has multiple focusable nodes, focus the
  113. // last one instead of the first one
  114. // tags:
  115. // protected
  116. if(this.focusedChild && widget !== this.focusedChild){
  117. this._onChildBlur(this.focusedChild);
  118. }
  119. widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
  120. widget.focus(last ? "end" : "start");
  121. this._set("focusedChild", widget);
  122. },
  123. _startupChild: function(/*dijit._Widget*/ widget){
  124. // summary:
  125. // Setup for each child widget
  126. // description:
  127. // Sets tabIndex=-1 on each child, so that the tab key will
  128. // leave the container rather than visiting each child.
  129. // tags:
  130. // private
  131. widget.set("tabIndex", "-1");
  132. this.connect(widget, "_onFocus", function(){
  133. // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
  134. widget.set("tabIndex", this.tabIndex);
  135. });
  136. this.connect(widget, "_onBlur", function(){
  137. widget.set("tabIndex", "-1");
  138. });
  139. },
  140. _onContainerFocus: function(evt){
  141. // summary:
  142. // Handler for when the container gets focus
  143. // description:
  144. // Initially the container itself has a tabIndex, but when it gets
  145. // focus, switch focus to first child...
  146. // tags:
  147. // private
  148. // Note that we can't use _onFocus() because switching focus from the
  149. // _onFocus() handler confuses the focus.js code
  150. // (because it causes _onFocusNode() to be called recursively)
  151. // focus bubbles on Firefox,
  152. // so just make sure that focus has really gone to the container
  153. if(evt.target !== this.domNode){ return; }
  154. this.focusFirstChild();
  155. // and then set the container's tabIndex to -1,
  156. // (don't remove as that breaks Safari 4)
  157. // so that tab or shift-tab will go to the fields after/before
  158. // the container, rather than the container itself
  159. dojo.attr(this.domNode, "tabIndex", "-1");
  160. },
  161. _onBlur: function(evt){
  162. // When focus is moved away the container, and its descendant (popup) widgets,
  163. // then restore the container's tabIndex so that user can tab to it again.
  164. // Note that using _onBlur() so that this doesn't happen when focus is shifted
  165. // to one of my child widgets (typically a popup)
  166. if(this.tabIndex){
  167. dojo.attr(this.domNode, "tabIndex", this.tabIndex);
  168. }
  169. this.inherited(arguments);
  170. },
  171. _onContainerKeypress: function(evt){
  172. // summary:
  173. // When a key is pressed, if it's an arrow key etc. then
  174. // it's handled here.
  175. // tags:
  176. // private
  177. if(evt.ctrlKey || evt.altKey){ return; }
  178. var func = this._keyNavCodes[evt.charOrCode];
  179. if(func){
  180. func();
  181. dojo.stopEvent(evt);
  182. }
  183. },
  184. _onChildBlur: function(/*dijit._Widget*/ widget){
  185. // summary:
  186. // Called when focus leaves a child widget to go
  187. // to a sibling widget.
  188. // tags:
  189. // protected
  190. },
  191. _getFirstFocusableChild: function(){
  192. // summary:
  193. // Returns first child that can be focused
  194. return this._getNextFocusableChild(null, 1); // dijit._Widget
  195. },
  196. _getLastFocusableChild: function(){
  197. // summary:
  198. // Returns last child that can be focused
  199. return this._getNextFocusableChild(null, -1); // dijit._Widget
  200. },
  201. _getNextFocusableChild: function(child, dir){
  202. // summary:
  203. // Returns the next or previous focusable child, compared
  204. // to "child"
  205. // child: Widget
  206. // The current widget
  207. // dir: Integer
  208. // * 1 = after
  209. // * -1 = before
  210. if(child){
  211. child = this._getSiblingOfChild(child, dir);
  212. }
  213. var children = this.getChildren();
  214. for(var i=0; i < children.length; i++){
  215. if(!child){
  216. child = children[(dir>0) ? 0 : (children.length-1)];
  217. }
  218. if(child.isFocusable()){
  219. return child; // dijit._Widget
  220. }
  221. child = this._getSiblingOfChild(child, dir);
  222. }
  223. // no focusable child found
  224. return null; // dijit._Widget
  225. }
  226. }
  227. );
  228. }