1 define("dijit/_KeyNavContainer", [
2 "dojo/_base/kernel", // kernel.deprecated
5 "dojo/_base/array", // array.forEach
6 "dojo/keys", // keys.END keys.HOME
7 "dojo/_base/declare", // declare
8 "dojo/_base/event", // event.stop
9 "dojo/dom-attr", // domAttr.set
10 "dojo/_base/lang" // lang.hitch
11 ], function(kernel, _Container, _FocusMixin, array, keys, declare, event, domAttr, lang){
15 // dijit/_KeyNavContainer
17 return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], {
19 // A _Container with keyboard navigation of its children.
21 // To use this mixin, call connectKeyNavHandlers() in
23 // It provides normalized keyboard and focusing code for Container
27 // focusedChild: [protected] Widget
28 // The currently focused child widget, or null if there isn't one
33 // Tab index of the container; same as HTML tabIndex attribute.
34 // Note then when user tabs into the container, focus is immediately
35 // moved to the first item in the container.
38 connectKeyNavHandlers: function(/*keys[]*/ prevKeyCodes, /*keys[]*/ nextKeyCodes){
40 // Call in postCreate() to attach the keyboard handlers
42 // preKeyCodes: keys[]
43 // Key codes for navigating to the previous child.
44 // nextKeyCodes: keys[]
45 // Key codes for navigating to the next child.
49 // TODO: call this automatically from my own postCreate()
51 var keyCodes = (this._keyNavCodes = {});
52 var prev = lang.hitch(this, "focusPrev");
53 var next = lang.hitch(this, "focusNext");
54 array.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
55 array.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
56 keyCodes[keys.HOME] = lang.hitch(this, "focusFirstChild");
57 keyCodes[keys.END] = lang.hitch(this, "focusLastChild");
58 this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
59 this.connect(this.domNode, "onfocus", "_onContainerFocus");
62 startupKeyNavChildren: function(){
63 kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0");
67 this.inherited(arguments);
68 array.forEach(this.getChildren(), lang.hitch(this, "_startupChild"));
71 addChild: function(/*dijit/_WidgetBase*/ widget, /*int?*/ insertIndex){
72 this.inherited(arguments);
73 this._startupChild(widget);
78 // Default focus() implementation: focus the first child.
79 this.focusFirstChild();
82 focusFirstChild: function(){
84 // Focus the first focusable child in the container.
87 this.focusChild(this._getFirstFocusableChild());
90 focusLastChild: function(){
92 // Focus the last focusable child in the container.
95 this.focusChild(this._getLastFocusableChild());
98 focusNext: function(){
100 // Focus the next widget
103 this.focusChild(this._getNextFocusableChild(this.focusedChild, 1));
106 focusPrev: function(){
108 // Focus the last focusable node in the previous widget
109 // (ex: go to the ComboButton icon section rather than button section)
112 this.focusChild(this._getNextFocusableChild(this.focusedChild, -1), true);
115 focusChild: function(/*dijit/_WidgetBase*/ widget, /*Boolean*/ last){
117 // Focus specified child widget.
119 // Reference to container's child widget
121 // If true and if widget has multiple focusable nodes, focus the
122 // last one instead of the first one
126 if(!widget){ return; }
128 if(this.focusedChild && widget !== this.focusedChild){
129 this._onChildBlur(this.focusedChild); // used by _MenuBase
131 widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
132 widget.focus(last ? "end" : "start");
133 this._set("focusedChild", widget);
136 _startupChild: function(/*dijit/_WidgetBase*/ widget){
138 // Setup for each child widget
140 // Sets tabIndex=-1 on each child, so that the tab key will
141 // leave the container rather than visiting each child.
145 widget.set("tabIndex", "-1");
147 this.connect(widget, "_onFocus", function(){
148 // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
149 widget.set("tabIndex", this.tabIndex);
151 this.connect(widget, "_onBlur", function(){
152 widget.set("tabIndex", "-1");
156 _onContainerFocus: function(evt){
158 // Handler for when the container gets focus
160 // Initially the container itself has a tabIndex, but when it gets
161 // focus, switch focus to first child...
165 // Note that we can't use _onFocus() because switching focus from the
166 // _onFocus() handler confuses the focus.js code
167 // (because it causes _onFocusNode() to be called recursively)
168 // Also, _onFocus() would fire when focus went directly to a child widget due to mouse click.
170 // Ignore spurious focus events:
171 // 1. focus on a child widget bubbles on FF
172 // 2. on IE, clicking the scrollbar of a select dropdown moves focus from the focused child item to me
173 if(evt.target !== this.domNode || this.focusedChild){ return; }
175 this.focusFirstChild();
177 // and then set the container's tabIndex to -1,
178 // (don't remove as that breaks Safari 4)
179 // so that tab or shift-tab will go to the fields after/before
180 // the container, rather than the container itself
181 domAttr.set(this.domNode, "tabIndex", "-1");
184 _onBlur: function(evt){
185 // When focus is moved away the container, and its descendant (popup) widgets,
186 // then restore the container's tabIndex so that user can tab to it again.
187 // Note that using _onBlur() so that this doesn't happen when focus is shifted
188 // to one of my child widgets (typically a popup)
190 domAttr.set(this.domNode, "tabIndex", this.tabIndex);
192 this.focusedChild = null;
193 this.inherited(arguments);
196 _onContainerKeypress: function(evt){
198 // When a key is pressed, if it's an arrow key etc. then
199 // it's handled here.
202 if(evt.ctrlKey || evt.altKey){ return; }
203 var func = this._keyNavCodes[evt.charOrCode];
210 _onChildBlur: function(/*dijit/_WidgetBase*/ /*===== widget =====*/){
212 // Called when focus leaves a child widget to go
213 // to a sibling widget.
214 // Used by MenuBase.js (TODO: move code there)
219 _getFirstFocusableChild: function(){
221 // Returns first child that can be focused
222 return this._getNextFocusableChild(null, 1); // dijit/_WidgetBase
225 _getLastFocusableChild: function(){
227 // Returns last child that can be focused
228 return this._getNextFocusableChild(null, -1); // dijit/_WidgetBase
231 _getNextFocusableChild: function(child, dir){
233 // Returns the next or previous focusable child, compared
236 // The current widget
241 child = this._getSiblingOfChild(child, dir);
243 var children = this.getChildren();
244 for(var i=0; i < children.length; i++){
246 child = children[(dir>0) ? 0 : (children.length-1)];
248 if(child.isFocusable()){
249 return child; // dijit/_WidgetBase
251 child = this._getSiblingOfChild(child, dir);
253 // no focusable child found
254 return null; // dijit/_WidgetBase