]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
1 | define("dijit/_KeyNavContainer", [ |
2 | "dojo/_base/kernel", // kernel.deprecated | |
3 | "./_Container", | |
4 | "./_FocusMixin", | |
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){ | |
12 | ||
13 | /*===== | |
14 | var _FocusMixin = dijit._FocusMixin; | |
15 | var _Container = dijit._Container; | |
16 | =====*/ | |
17 | ||
18 | // module: | |
19 | // dijit/_KeyNavContainer | |
20 | // summary: | |
21 | // A _Container with keyboard navigation of its children. | |
22 | ||
23 | return declare("dijit._KeyNavContainer", [_FocusMixin, _Container], { | |
24 | ||
25 | // summary: | |
26 | // A _Container with keyboard navigation of its children. | |
27 | // description: | |
28 | // To use this mixin, call connectKeyNavHandlers() in | |
29 | // postCreate(). | |
30 | // It provides normalized keyboard and focusing code for Container | |
31 | // widgets. | |
32 | ||
33 | /*===== | |
34 | // focusedChild: [protected] Widget | |
35 | // The currently focused child widget, or null if there isn't one | |
36 | focusedChild: null, | |
37 | =====*/ | |
38 | ||
39 | // tabIndex: Integer | |
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. | |
43 | tabIndex: "0", | |
44 | ||
45 | connectKeyNavHandlers: function(/*keys[]*/ prevKeyCodes, /*keys[]*/ nextKeyCodes){ | |
46 | // summary: | |
47 | // Call in postCreate() to attach the keyboard handlers | |
48 | // to the container. | |
49 | // preKeyCodes: keys[] | |
50 | // Key codes for navigating to the previous child. | |
51 | // nextKeyCodes: keys[] | |
52 | // Key codes for navigating to the next child. | |
53 | // tags: | |
54 | // protected | |
55 | ||
56 | // TODO: call this automatically from my own postCreate() | |
57 | ||
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"); | |
67 | }, | |
68 | ||
69 | startupKeyNavChildren: function(){ | |
70 | kernel.deprecated("startupKeyNavChildren() call no longer needed", "", "2.0"); | |
71 | }, | |
72 | ||
73 | startup: function(){ | |
74 | this.inherited(arguments); | |
75 | array.forEach(this.getChildren(), lang.hitch(this, "_startupChild")); | |
76 | }, | |
77 | ||
78 | addChild: function(/*dijit._Widget*/ widget, /*int?*/ insertIndex){ | |
79 | this.inherited(arguments); | |
80 | this._startupChild(widget); | |
81 | }, | |
82 | ||
83 | focus: function(){ | |
84 | // summary: | |
85 | // Default focus() implementation: focus the first child. | |
86 | this.focusFirstChild(); | |
87 | }, | |
88 | ||
89 | focusFirstChild: function(){ | |
90 | // summary: | |
91 | // Focus the first focusable child in the container. | |
92 | // tags: | |
93 | // protected | |
94 | this.focusChild(this._getFirstFocusableChild()); | |
95 | }, | |
96 | ||
97 | focusLastChild: function(){ | |
98 | // summary: | |
99 | // Focus the last focusable child in the container. | |
100 | // tags: | |
101 | // protected | |
102 | this.focusChild(this._getLastFocusableChild()); | |
103 | }, | |
104 | ||
105 | focusNext: function(){ | |
106 | // summary: | |
107 | // Focus the next widget | |
108 | // tags: | |
109 | // protected | |
110 | this.focusChild(this._getNextFocusableChild(this.focusedChild, 1)); | |
111 | }, | |
112 | ||
113 | focusPrev: function(){ | |
114 | // summary: | |
115 | // Focus the last focusable node in the previous widget | |
116 | // (ex: go to the ComboButton icon section rather than button section) | |
117 | // tags: | |
118 | // protected | |
119 | this.focusChild(this._getNextFocusableChild(this.focusedChild, -1), true); | |
120 | }, | |
121 | ||
122 | focusChild: function(/*dijit._Widget*/ widget, /*Boolean*/ last){ | |
123 | // summary: | |
124 | // Focus specified child widget. | |
125 | // widget: | |
126 | // Reference to container's child widget | |
127 | // last: | |
128 | // If true and if widget has multiple focusable nodes, focus the | |
129 | // last one instead of the first one | |
130 | // tags: | |
131 | // protected | |
132 | ||
133 | if(!widget){ return; } | |
134 | ||
135 | if(this.focusedChild && widget !== this.focusedChild){ | |
136 | this._onChildBlur(this.focusedChild); // used by _MenuBase | |
137 | } | |
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); | |
141 | }, | |
142 | ||
143 | _startupChild: function(/*dijit._Widget*/ widget){ | |
144 | // summary: | |
145 | // Setup for each child widget | |
146 | // description: | |
147 | // Sets tabIndex=-1 on each child, so that the tab key will | |
148 | // leave the container rather than visiting each child. | |
149 | // tags: | |
150 | // private | |
151 | ||
152 | widget.set("tabIndex", "-1"); | |
153 | ||
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); | |
157 | }); | |
158 | this.connect(widget, "_onBlur", function(){ | |
159 | widget.set("tabIndex", "-1"); | |
160 | }); | |
161 | }, | |
162 | ||
163 | _onContainerFocus: function(evt){ | |
164 | // summary: | |
165 | // Handler for when the container gets focus | |
166 | // description: | |
167 | // Initially the container itself has a tabIndex, but when it gets | |
168 | // focus, switch focus to first child... | |
169 | // tags: | |
170 | // private | |
171 | ||
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. | |
176 | ||
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; } | |
181 | ||
182 | this.focusFirstChild(); | |
183 | ||
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"); | |
189 | }, | |
190 | ||
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) | |
196 | if(this.tabIndex){ | |
197 | domAttr.set(this.domNode, "tabIndex", this.tabIndex); | |
198 | } | |
199 | this.focusedChild = null; | |
200 | this.inherited(arguments); | |
201 | }, | |
202 | ||
203 | _onContainerKeypress: function(evt){ | |
204 | // summary: | |
205 | // When a key is pressed, if it's an arrow key etc. then | |
206 | // it's handled here. | |
207 | // tags: | |
208 | // private | |
209 | if(evt.ctrlKey || evt.altKey){ return; } | |
210 | var func = this._keyNavCodes[evt.charOrCode]; | |
211 | if(func){ | |
212 | func(); | |
213 | event.stop(evt); | |
214 | } | |
215 | }, | |
216 | ||
217 | _onChildBlur: function(/*dijit._Widget*/ /*===== widget =====*/){ | |
218 | // summary: | |
219 | // Called when focus leaves a child widget to go | |
220 | // to a sibling widget. | |
221 | // Used by MenuBase.js (TODO: move code there) | |
222 | // tags: | |
223 | // protected | |
224 | }, | |
225 | ||
226 | _getFirstFocusableChild: function(){ | |
227 | // summary: | |
228 | // Returns first child that can be focused | |
229 | return this._getNextFocusableChild(null, 1); // dijit._Widget | |
230 | }, | |
231 | ||
232 | _getLastFocusableChild: function(){ | |
233 | // summary: | |
234 | // Returns last child that can be focused | |
235 | return this._getNextFocusableChild(null, -1); // dijit._Widget | |
236 | }, | |
237 | ||
238 | _getNextFocusableChild: function(child, dir){ | |
239 | // summary: | |
240 | // Returns the next or previous focusable child, compared | |
241 | // to "child" | |
242 | // child: Widget | |
243 | // The current widget | |
244 | // dir: Integer | |
245 | // * 1 = after | |
246 | // * -1 = before | |
247 | if(child){ | |
248 | child = this._getSiblingOfChild(child, dir); | |
249 | } | |
250 | var children = this.getChildren(); | |
251 | for(var i=0; i < children.length; i++){ | |
252 | if(!child){ | |
253 | child = children[(dir>0) ? 0 : (children.length-1)]; | |
254 | } | |
255 | if(child.isFocusable()){ | |
256 | return child; // dijit._Widget | |
257 | } | |
258 | child = this._getSiblingOfChild(child, dir); | |
259 | } | |
260 | // no focusable child found | |
261 | return null; // dijit._Widget | |
262 | } | |
263 | }); | |
264 | }); |