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){
14 var _FocusMixin = dijit._FocusMixin;
15 var _Container = dijit._Container;
19 // dijit/_KeyNavContainer
21 // A _Container with keyboard navigation of its children.
23 return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], {
26 // A _Container with keyboard navigation of its children.
28 // To use this mixin, call connectKeyNavHandlers() in
30 // It provides normalized keyboard and focusing code for Container
34 // focusedChild: [protected] Widget
35 // The currently focused child widget, or null if there isn't one
40 // Tab index of the container; same as HTML tabIndex attribute.
41 // Note then when user tabs into the container, focus is immediately
42 // moved to the first item in the container.
45 connectKeyNavHandlers: function(/*keys[]*/ prevKeyCodes, /*keys[]*/ nextKeyCodes){
47 // Call in postCreate() to attach the keyboard handlers
49 // preKeyCodes: keys[]
50 // Key codes for navigating to the previous child.
51 // nextKeyCodes: keys[]
52 // Key codes for navigating to the next child.
56 // TODO: call this automatically from my own postCreate()
58 var keyCodes = (this._keyNavCodes = {});
59 var prev = lang.hitch(this, "focusPrev");
60 var next = lang.hitch(this, "focusNext");
61 array.forEach(prevKeyCodes, function(code){ keyCodes[code] = prev; });
62 array.forEach(nextKeyCodes, function(code){ keyCodes[code] = next; });
63 keyCodes[keys.HOME] = lang.hitch(this, "focusFirstChild");
64 keyCodes[keys.END] = lang.hitch(this, "focusLastChild");
65 this.connect(this.domNode, "onkeypress", "_onContainerKeypress");
66 this.connect(this.domNode, "onfocus", "_onContainerFocus");
69 startupKeyNavChildren: function(){
70 kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0");
74 this.inherited(arguments);
75 array.forEach(this.getChildren(), lang.hitch(this, "_startupChild"));
78 addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){
79 this.inherited(arguments);
80 this._startupChild(widget);
85 // Default focus() implementation: focus the first child.
86 this.focusFirstChild();
89 focusFirstChild: function(){
91 // Focus the first focusable child in the container.
94 this.focusChild(this._getFirstFocusableChild());
97 focusLastChild: function(){
99 // Focus the last focusable child in the container.
102 this.focusChild(this._getLastFocusableChild());
105 focusNext: function(){
107 // Focus the next widget
110 this.focusChild(this._getNextFocusableChild(this.focusedChild, 1));
113 focusPrev: function(){
115 // Focus the last focusable node in the previous widget
116 // (ex: go to the ComboButton icon section rather than button section)
119 this.focusChild(this._getNextFocusableChild(this.focusedChild, -1), true);
122 focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){
124 // Focus specified child widget.
126 // Reference to container's child widget
128 // If true and if widget has multiple focusable nodes, focus the
129 // last one instead of the first one
133 if(!widget){ return; }
135 if(this.focusedChild && widget !== this.focusedChild){
136 this._onChildBlur(this.focusedChild); // used by _MenuBase
138 widget.set("tabIndex", this.tabIndex); // for IE focus outline to appear, must set tabIndex before focs
139 widget.focus(last ? "end" : "start");
140 this._set("focusedChild", widget);
143 _startupChild: function(/*dijit._Widget*/ widget){
145 // Setup for each child widget
147 // Sets tabIndex=-1 on each child, so that the tab key will
148 // leave the container rather than visiting each child.
152 widget.set("tabIndex", "-1");
154 this.connect(widget, "_onFocus", function(){
155 // Set valid tabIndex so tabbing away from widget goes to right place, see #10272
156 widget.set("tabIndex", this.tabIndex);
158 this.connect(widget, "_onBlur", function(){
159 widget.set("tabIndex", "-1");
163 _onContainerFocus: function(evt){
165 // Handler for when the container gets focus
167 // Initially the container itself has a tabIndex, but when it gets
168 // focus, switch focus to first child...
172 // Note that we can't use _onFocus() because switching focus from the
173 // _onFocus() handler confuses the focus.js code
174 // (because it causes _onFocusNode() to be called recursively)
175 // Also, _onFocus() would fire when focus went directly to a child widget due to mouse click.
177 // Ignore spurious focus events:
178 // 1. focus on a child widget bubbles on FF
179 // 2. on IE, clicking the scrollbar of a select dropdown moves focus from the focused child item to me
180 if(evt.target !== this.domNode || this.focusedChild){ return; }
182 this.focusFirstChild();
184 // and then set the container's tabIndex to -1,
185 // (don't remove as that breaks Safari 4)
186 // so that tab or shift-tab will go to the fields after/before
187 // the container, rather than the container itself
188 domAttr.set(this.domNode, "tabIndex", "-1");
191 _onBlur: function(evt){
192 // When focus is moved away the container, and its descendant (popup) widgets,
193 // then restore the container's tabIndex so that user can tab to it again.
194 // Note that using _onBlur() so that this doesn't happen when focus is shifted
195 // to one of my child widgets (typically a popup)
197 domAttr.set(this.domNode, "tabIndex", this.tabIndex);
199 this.focusedChild = null;
200 this.inherited(arguments);
203 _onContainerKeypress: function(evt){
205 // When a key is pressed, if it's an arrow key etc. then
206 // it's handled here.
209 if(evt.ctrlKey || evt.altKey){ return; }
210 var func = this._keyNavCodes[evt.charOrCode];
217 _onChildBlur: function(/*dijit._Widget*/ /*===== widget =====*/){
219 // Called when focus leaves a child widget to go
220 // to a sibling widget.
221 // Used by MenuBase.js (TODO: move code there)
226 _getFirstFocusableChild: function(){
228 // Returns first child that can be focused
229 return this._getNextFocusableChild(null, 1); // dijit._Widget
232 _getLastFocusableChild: function(){
234 // Returns last child that can be focused
235 return this._getNextFocusableChild(null, -1); // dijit._Widget
238 _getNextFocusableChild: function(child, dir){
240 // Returns the next or previous focusable child, compared
243 // The current widget
248 child = this._getSiblingOfChild(child, dir);
250 var children = this.getChildren();
251 for(var i=0; i < children.length; i++){
253 child = children[(dir>0) ? 0 : (children.length-1)];
255 if(child.isFocusable()){
256 return child; // dijit._Widget
258 child = this._getSiblingOfChild(child, dir);
260 // no focusable child found
261 return null; // dijit._Widget