2150 |
mathias |
1 |
if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
2 |
dojo._hasResource["dijit._base.focus"] = true;
|
|
|
3 |
dojo.provide("dijit._base.focus");
|
|
|
4 |
|
|
|
5 |
// summary:
|
|
|
6 |
// These functions are used to query or set the focus and selection.
|
|
|
7 |
//
|
|
|
8 |
// Also, they trace when widgets become actived/deactivated,
|
|
|
9 |
// so that the widget can fire _onFocus/_onBlur events.
|
|
|
10 |
// "Active" here means something similar to "focused", but
|
|
|
11 |
// "focus" isn't quite the right word because we keep track of
|
|
|
12 |
// a whole stack of "active" widgets. Example: Combobutton --> Menu -->
|
|
|
13 |
// MenuItem. The onBlur event for Combobutton doesn't fire due to focusing
|
|
|
14 |
// on the Menu or a MenuItem, since they are considered part of the
|
|
|
15 |
// Combobutton widget. It only happens when focus is shifted
|
|
|
16 |
// somewhere completely different.
|
|
|
17 |
|
|
|
18 |
dojo.mixin(dijit,
|
|
|
19 |
{
|
|
|
20 |
// _curFocus: DomNode
|
|
|
21 |
// Currently focused item on screen
|
|
|
22 |
_curFocus: null,
|
|
|
23 |
|
|
|
24 |
// _prevFocus: DomNode
|
|
|
25 |
// Previously focused item on screen
|
|
|
26 |
_prevFocus: null,
|
|
|
27 |
|
|
|
28 |
isCollapsed: function(){
|
|
|
29 |
// summary: tests whether the current selection is empty
|
|
|
30 |
var _window = dojo.global;
|
|
|
31 |
var _document = dojo.doc;
|
|
|
32 |
if(_document.selection){ // IE
|
|
|
33 |
return !_document.selection.createRange().text; // Boolean
|
|
|
34 |
}else if(_window.getSelection){
|
|
|
35 |
var selection = _window.getSelection();
|
|
|
36 |
if(dojo.isString(selection)){ // Safari
|
|
|
37 |
return !selection; // Boolean
|
|
|
38 |
}else{ // Mozilla/W3
|
|
|
39 |
return selection.isCollapsed || !selection.toString(); // Boolean
|
|
|
40 |
}
|
|
|
41 |
}
|
|
|
42 |
},
|
|
|
43 |
|
|
|
44 |
getBookmark: function(){
|
|
|
45 |
// summary: Retrieves a bookmark that can be used with moveToBookmark to return to the same range
|
|
|
46 |
var bookmark, selection = dojo.doc.selection;
|
|
|
47 |
if(selection){ // IE
|
|
|
48 |
var range = selection.createRange();
|
|
|
49 |
if(selection.type.toUpperCase()=='CONTROL'){
|
|
|
50 |
bookmark = range.length ? dojo._toArray(range) : null;
|
|
|
51 |
}else{
|
|
|
52 |
bookmark = range.getBookmark();
|
|
|
53 |
}
|
|
|
54 |
}else{
|
|
|
55 |
if(dojo.global.getSelection){
|
|
|
56 |
selection = dojo.global.getSelection();
|
|
|
57 |
if(selection){
|
|
|
58 |
var range = selection.getRangeAt(0);
|
|
|
59 |
bookmark = range.cloneRange();
|
|
|
60 |
}
|
|
|
61 |
}else{
|
|
|
62 |
console.debug("No idea how to store the current selection for this browser!");
|
|
|
63 |
}
|
|
|
64 |
}
|
|
|
65 |
return bookmark; // Array
|
|
|
66 |
},
|
|
|
67 |
|
|
|
68 |
moveToBookmark: function(/*Object*/bookmark){
|
|
|
69 |
// summary: Moves current selection to a bookmark
|
|
|
70 |
// bookmark: this should be a returned object from dojo.html.selection.getBookmark()
|
|
|
71 |
var _document = dojo.doc;
|
|
|
72 |
if(_document.selection){ // IE
|
|
|
73 |
var range;
|
|
|
74 |
if(dojo.isArray(bookmark)){
|
|
|
75 |
range = _document.body.createControlRange();
|
|
|
76 |
dojo.forEach(bookmark, range.addElement);
|
|
|
77 |
}else{
|
|
|
78 |
range = _document.selection.createRange();
|
|
|
79 |
range.moveToBookmark(bookmark);
|
|
|
80 |
}
|
|
|
81 |
range.select();
|
|
|
82 |
}else{ //Moz/W3C
|
|
|
83 |
var selection = dojo.global.getSelection && dojo.global.getSelection();
|
|
|
84 |
if(selection && selection.removeAllRanges){
|
|
|
85 |
selection.removeAllRanges();
|
|
|
86 |
selection.addRange(bookmark);
|
|
|
87 |
}else{
|
|
|
88 |
console.debug("No idea how to restore selection for this browser!");
|
|
|
89 |
}
|
|
|
90 |
}
|
|
|
91 |
},
|
|
|
92 |
|
|
|
93 |
getFocus: function(/*Widget*/menu, /*Window*/ openedForWindow){
|
|
|
94 |
// summary:
|
|
|
95 |
// Returns the current focus and selection.
|
|
|
96 |
// Called when a popup appears (either a top level menu or a dialog),
|
|
|
97 |
// or when a toolbar/menubar receives focus
|
|
|
98 |
//
|
|
|
99 |
// menu:
|
|
|
100 |
// the menu that's being opened
|
|
|
101 |
//
|
|
|
102 |
// openedForWindow:
|
|
|
103 |
// iframe in which menu was opened
|
|
|
104 |
//
|
|
|
105 |
// returns:
|
|
|
106 |
// a handle to restore focus/selection
|
|
|
107 |
|
|
|
108 |
return {
|
|
|
109 |
// Node to return focus to
|
|
|
110 |
node: menu && dojo.isDescendant(dijit._curFocus, menu.domNode) ? dijit._prevFocus : dijit._curFocus,
|
|
|
111 |
|
|
|
112 |
// Previously selected text
|
|
|
113 |
bookmark:
|
|
|
114 |
!dojo.withGlobal(openedForWindow||dojo.global, dijit.isCollapsed) ?
|
|
|
115 |
dojo.withGlobal(openedForWindow||dojo.global, dijit.getBookmark) :
|
|
|
116 |
null,
|
|
|
117 |
|
|
|
118 |
openedForWindow: openedForWindow
|
|
|
119 |
}; // Object
|
|
|
120 |
},
|
|
|
121 |
|
|
|
122 |
focus: function(/*Object || DomNode */ handle){
|
|
|
123 |
// summary:
|
|
|
124 |
// Sets the focused node and the selection according to argument.
|
|
|
125 |
// To set focus to an iframe's content, pass in the iframe itself.
|
|
|
126 |
// handle:
|
|
|
127 |
// object returned by get(), or a DomNode
|
|
|
128 |
|
|
|
129 |
if(!handle){ return; }
|
|
|
130 |
|
|
|
131 |
var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
|
|
|
132 |
bookmark = handle.bookmark,
|
|
|
133 |
openedForWindow = handle.openedForWindow;
|
|
|
134 |
|
|
|
135 |
// Set the focus
|
|
|
136 |
// Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
|
|
|
137 |
// but we need to set focus to iframe.contentWindow
|
|
|
138 |
if(node){
|
|
|
139 |
var focusNode = (node.tagName.toLowerCase()=="iframe") ? node.contentWindow : node;
|
|
|
140 |
if(focusNode && focusNode.focus){
|
|
|
141 |
try{
|
|
|
142 |
// Gecko throws sometimes if setting focus is impossible,
|
|
|
143 |
// node not displayed or something like that
|
|
|
144 |
focusNode.focus();
|
|
|
145 |
}catch(e){/*quiet*/}
|
|
|
146 |
}
|
|
|
147 |
dijit._onFocusNode(node);
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
// set the selection
|
|
|
151 |
// do not need to restore if current selection is not empty
|
|
|
152 |
// (use keyboard to select a menu item)
|
|
|
153 |
if(bookmark && dojo.withGlobal(openedForWindow||dojo.global, dijit.isCollapsed)){
|
|
|
154 |
if(openedForWindow){
|
|
|
155 |
openedForWindow.focus();
|
|
|
156 |
}
|
|
|
157 |
try{
|
|
|
158 |
dojo.withGlobal(openedForWindow||dojo.global, moveToBookmark, null, [bookmark]);
|
|
|
159 |
}catch(e){
|
|
|
160 |
/*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
|
|
|
161 |
}
|
|
|
162 |
}
|
|
|
163 |
},
|
|
|
164 |
|
|
|
165 |
// List of currently active widgets (focused widget and it's ancestors)
|
|
|
166 |
_activeStack: [],
|
|
|
167 |
|
|
|
168 |
registerWin: function(/*Window?*/targetWindow){
|
|
|
169 |
// summary:
|
|
|
170 |
// Registers listeners on the specified window (either the main
|
|
|
171 |
// window or an iframe) to detect when the user has clicked somewhere.
|
|
|
172 |
// Anyone that creates an iframe should call this function.
|
|
|
173 |
|
|
|
174 |
if(!targetWindow){
|
|
|
175 |
targetWindow = window;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
dojo.connect(targetWindow.document, "onmousedown", null, function(evt){
|
|
|
179 |
dijit._justMouseDowned = true;
|
|
|
180 |
setTimeout(function(){ dijit._justMouseDowned = false; }, 0);
|
|
|
181 |
dijit._onTouchNode(evt.target||evt.srcElement);
|
|
|
182 |
});
|
|
|
183 |
//dojo.connect(targetWindow, "onscroll", ???);
|
|
|
184 |
|
|
|
185 |
// Listen for blur and focus events on targetWindow's body
|
|
|
186 |
var body = targetWindow.document.body || targetWindow.document.getElementsByTagName("body")[0];
|
|
|
187 |
if(body){
|
|
|
188 |
if(dojo.isIE){
|
|
|
189 |
body.attachEvent('onactivate', function(evt){
|
|
|
190 |
if(evt.srcElement.tagName.toLowerCase() != "body"){
|
|
|
191 |
dijit._onFocusNode(evt.srcElement);
|
|
|
192 |
}
|
|
|
193 |
});
|
|
|
194 |
body.attachEvent('ondeactivate', function(evt){ dijit._onBlurNode(evt.srcElement); });
|
|
|
195 |
}else{
|
|
|
196 |
body.addEventListener('focus', function(evt){ dijit._onFocusNode(evt.target); }, true);
|
|
|
197 |
body.addEventListener('blur', function(evt){ dijit._onBlurNode(evt.target); }, true);
|
|
|
198 |
}
|
|
|
199 |
}
|
|
|
200 |
body = null; // prevent memory leak (apparent circular reference via closure)
|
|
|
201 |
},
|
|
|
202 |
|
|
|
203 |
_onBlurNode: function(/*DomNode*/ node){
|
|
|
204 |
// summary:
|
|
|
205 |
// Called when focus leaves a node.
|
|
|
206 |
// Usually ignored, _unless_ it *isn't* follwed by touching another node,
|
|
|
207 |
// which indicates that we tabbed off the last field on the page,
|
|
|
208 |
// in which case every widget is marked inactive
|
|
|
209 |
dijit._prevFocus = dijit._curFocus;
|
|
|
210 |
dijit._curFocus = null;
|
|
|
211 |
|
|
|
212 |
var w = dijit.getEnclosingWidget(node);
|
|
|
213 |
if (w && w._setStateClass){
|
|
|
214 |
w._focused = false;
|
|
|
215 |
w._setStateClass();
|
|
|
216 |
}
|
|
|
217 |
if(dijit._justMouseDowned){
|
|
|
218 |
// the mouse down caused a new widget to be marked as active; this blur event
|
|
|
219 |
// is coming late, so ignore it.
|
|
|
220 |
return;
|
|
|
221 |
}
|
|
|
222 |
|
|
|
223 |
// if the blur event isn't followed by a focus event then mark all widgets as inactive.
|
|
|
224 |
if(dijit._clearActiveWidgetsTimer){
|
|
|
225 |
clearTimeout(dijit._clearActiveWidgetsTimer);
|
|
|
226 |
}
|
|
|
227 |
dijit._clearActiveWidgetsTimer = setTimeout(function(){
|
|
|
228 |
delete dijit._clearActiveWidgetsTimer; dijit._setStack([]); }, 100);
|
|
|
229 |
},
|
|
|
230 |
|
|
|
231 |
_onTouchNode: function(/*DomNode*/ node){
|
|
|
232 |
// summary
|
|
|
233 |
// Callback when node is focused or mouse-downed
|
|
|
234 |
|
|
|
235 |
// ignore the recent blurNode event
|
|
|
236 |
if(dijit._clearActiveWidgetsTimer){
|
|
|
237 |
clearTimeout(dijit._clearActiveWidgetsTimer);
|
|
|
238 |
delete dijit._clearActiveWidgetsTimer;
|
|
|
239 |
}
|
|
|
240 |
|
|
|
241 |
// compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
|
|
|
242 |
var newStack=[];
|
|
|
243 |
try{
|
|
|
244 |
while(node){
|
|
|
245 |
if(node.dijitPopupParent){
|
|
|
246 |
node=dijit.byId(node.dijitPopupParent).domNode;
|
|
|
247 |
}else if(node.tagName && node.tagName.toLowerCase()=="body"){
|
|
|
248 |
// is this the root of the document or just the root of an iframe?
|
|
|
249 |
if(node===dojo.body()){
|
|
|
250 |
// node is the root of the main document
|
|
|
251 |
break;
|
|
|
252 |
}
|
|
|
253 |
// otherwise, find the iframe this node refers to (can't access it via parentNode,
|
|
|
254 |
// need to do this trick instead) and continue tracing up the document
|
|
|
255 |
node=dojo.query("iframe").filter(function(iframe){ return iframe.contentDocument.body===node; })[0];
|
|
|
256 |
}else{
|
|
|
257 |
var id = node.getAttribute && node.getAttribute("widgetId");
|
|
|
258 |
if(id){
|
|
|
259 |
newStack.unshift(id);
|
|
|
260 |
}
|
|
|
261 |
node=node.parentNode;
|
|
|
262 |
}
|
|
|
263 |
}
|
|
|
264 |
}catch(e){ /* squelch */ }
|
|
|
265 |
|
|
|
266 |
dijit._setStack(newStack);
|
|
|
267 |
},
|
|
|
268 |
|
|
|
269 |
_onFocusNode: function(/*DomNode*/ node){
|
|
|
270 |
// summary
|
|
|
271 |
// Callback when node is focused
|
|
|
272 |
if(node && node.tagName && node.tagName.toLowerCase() == "body"){
|
|
|
273 |
return;
|
|
|
274 |
}
|
|
|
275 |
dijit._onTouchNode(node);
|
|
|
276 |
if(node==dijit._curFocus){ return; }
|
|
|
277 |
dijit._prevFocus = dijit._curFocus;
|
|
|
278 |
dijit._curFocus = node;
|
|
|
279 |
dojo.publish("focusNode", [node]);
|
|
|
280 |
|
|
|
281 |
// handle focus/blur styling
|
|
|
282 |
var w = dijit.getEnclosingWidget(node);
|
|
|
283 |
if (w && w._setStateClass){
|
|
|
284 |
w._focused = true;
|
|
|
285 |
w._setStateClass();
|
|
|
286 |
}
|
|
|
287 |
},
|
|
|
288 |
|
|
|
289 |
_setStack: function(newStack){
|
|
|
290 |
// summary
|
|
|
291 |
// The stack of active widgets has changed. Send out appropriate events and record new stack
|
|
|
292 |
|
|
|
293 |
var oldStack = dijit._activeStack;
|
|
|
294 |
dijit._activeStack = newStack;
|
|
|
295 |
|
|
|
296 |
// compare old stack to new stack to see how many elements they have in common
|
|
|
297 |
for(var nCommon=0; nCommon<Math.min(oldStack.length, newStack.length); nCommon++){
|
|
|
298 |
if(oldStack[nCommon] != newStack[nCommon]){
|
|
|
299 |
break;
|
|
|
300 |
}
|
|
|
301 |
}
|
|
|
302 |
|
|
|
303 |
// for all elements that have gone out of focus, send blur event
|
|
|
304 |
for(var i=oldStack.length-1; i>=nCommon; i--){
|
|
|
305 |
var widget = dijit.byId(oldStack[i]);
|
|
|
306 |
if(widget){
|
|
|
307 |
dojo.publish("widgetBlur", [widget]);
|
|
|
308 |
if(widget._onBlur){
|
|
|
309 |
widget._onBlur();
|
|
|
310 |
}
|
|
|
311 |
}
|
|
|
312 |
}
|
|
|
313 |
|
|
|
314 |
// for all element that have come into focus, send focus event
|
|
|
315 |
for(var i=nCommon; i<newStack.length; i++){
|
|
|
316 |
var widget = dijit.byId(newStack[i]);
|
|
|
317 |
if(widget){
|
|
|
318 |
dojo.publish("widgetFocus", [widget]);
|
|
|
319 |
if(widget._onFocus){
|
|
|
320 |
widget._onFocus();
|
|
|
321 |
}
|
|
|
322 |
}
|
|
|
323 |
}
|
|
|
324 |
}
|
|
|
325 |
});
|
|
|
326 |
|
|
|
327 |
// register top window and all the iframes it contains
|
|
|
328 |
dojo.addOnLoad(dijit.registerWin);
|
|
|
329 |
|
|
|
330 |
}
|