]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/focus.js.uncompressed.js
1 define("dijit/focus", [
3 "dojo/_base/declare", // declare
4 "dojo/dom", // domAttr.get dom.isDescendant
5 "dojo/dom-attr", // domAttr.get dom.isDescendant
6 "dojo/dom-construct", // connect to domConstruct.empty, domConstruct.destroy
8 "dojo/_base/lang", // lang.hitch
11 "dojo/sniff", // has("ie")
13 "dojo/_base/unload", // unload.addOnWindowUnload
14 "dojo/_base/window", // win.body
15 "dojo/window", // winUtils.get
16 "./a11y", // a11y.isTabNavigable
17 "./registry", // registry.byId
18 "./main" // to set dijit.focus
19 ], function(aspect
, declare
, dom
, domAttr
, domConstruct
, Evented
, lang
, on
, ready
, has
, Stateful
, unload
, win
, winUtils
,
20 a11y
, registry
, dijit
){
25 var FocusManager
= declare([Stateful
, Evented
], {
27 // Tracks the currently focused node, and which widgets are currently "active".
28 // Access via require(["dijit/focus"], function(focus){ ... }).
30 // A widget is considered active if it or a descendant widget has focus,
31 // or if a non-focusable node of this widget or a descendant was recently clicked.
33 // Call focus.watch("curNode", callback) to track the current focused DOMNode,
34 // or focus.watch("activeStack", callback) to track the currently focused stack of widgets.
36 // Call focus.on("widget-blur", func) or focus.on("widget-focus", ...) to monitor when
37 // when widgets become active/inactive
39 // Finally, focus(node) will focus a node, suppressing errors if the node doesn't exist.
42 // Currently focused item on screen
45 // activeStack: dijit/_WidgetBase[]
46 // List of currently active widgets (focused widget and it's ancestors)
49 constructor: function(){
50 // Don't leave curNode/prevNode pointing to bogus elements
51 var check
= lang
.hitch(this, function(node
){
52 if(dom
.isDescendant(this.curNode
, node
)){
53 this.set("curNode", null);
55 if(dom
.isDescendant(this.prevNode
, node
)){
56 this.set("prevNode", null);
59 aspect
.before(domConstruct
, "empty", check
);
60 aspect
.before(domConstruct
, "destroy", check
);
63 registerIframe: function(/*DomNode*/ iframe
){
65 // Registers listeners on the specified iframe so that any click
66 // or focus event on that iframe (or anything in it) is reported
67 // as a focus/click event on the `<iframe>` itself.
69 // Currently only used by editor.
71 // Handle with remove() method to deregister.
72 return this.registerWin(iframe
.contentWindow
, iframe
);
75 registerWin: function(/*Window?*/targetWindow
, /*DomNode?*/ effectiveNode
){
77 // Registers listeners on the specified window (either the main
78 // window or an iframe's window) to detect when the user has clicked somewhere
79 // or focused somewhere.
81 // Users should call registerIframe() instead of this method.
83 // If specified this is the window associated with the iframe,
84 // i.e. iframe.contentWindow.
86 // If specified, report any focus events inside targetWindow as
87 // an event on effectiveNode, rather than on evt.target.
89 // Handle with remove() method to deregister.
91 // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
94 var mousedownListener = function(evt
){
95 _this
._justMouseDowned
= true;
96 setTimeout(function(){ _this
._justMouseDowned
= false; }, 0);
98 // workaround weird IE bug where the click is on an orphaned node
99 // (first time clicking a Select/DropDownButton inside a TooltipDialog)
100 if(has("ie") && evt
&& evt
.srcElement
&& evt
.srcElement
.parentNode
== null){
104 _this
._onTouchNode(effectiveNode
|| evt
.target
|| evt
.srcElement
, "mouse");
107 // Listen for blur and focus events on targetWindow's document.
108 // Using attachEvent()/addEventListener() rather than on() to try to catch mouseDown events even
109 // if other code calls evt.stopPropagation(). But rethink for 2.0 since that doesn't work for attachEvent(),
110 // which watches events at the bubbling phase rather than capturing phase, like addEventListener(..., false).
111 // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
112 // (at least for FF) the focus event doesn't fire on <html> or <body>.
113 var doc
= has("ie") ? targetWindow
.document
.documentElement
: targetWindow
.document
;
116 targetWindow
.document
.body
.attachEvent('onmousedown', mousedownListener
);
117 var focusinListener = function(evt
){
118 // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
119 // ignore those events
120 var tag
= evt
.srcElement
.tagName
.toLowerCase();
121 if(tag
== "#document" || tag
== "body"){ return; }
123 // Previous code called _onTouchNode() for any activate event on a non-focusable node. Can
124 // probably just ignore such an event as it will be handled by onmousedown handler above, but
125 // leaving the code for now.
126 if(a11y
.isTabNavigable(evt
.srcElement
)){
127 _this
._onFocusNode(effectiveNode
|| evt
.srcElement
);
129 _this
._onTouchNode(effectiveNode
|| evt
.srcElement
);
132 doc
.attachEvent('onfocusin', focusinListener
);
133 var focusoutListener = function(evt
){
134 _this
._onBlurNode(effectiveNode
|| evt
.srcElement
);
136 doc
.attachEvent('onfocusout', focusoutListener
);
140 targetWindow
.document
.detachEvent('onmousedown', mousedownListener
);
141 doc
.detachEvent('onfocusin', focusinListener
);
142 doc
.detachEvent('onfocusout', focusoutListener
);
143 doc
= null; // prevent memory leak (apparent circular reference via closure)
147 doc
.body
.addEventListener('mousedown', mousedownListener
, true);
148 doc
.body
.addEventListener('touchstart', mousedownListener
, true);
149 var focusListener = function(evt
){
150 _this
._onFocusNode(effectiveNode
|| evt
.target
);
152 doc
.addEventListener('focus', focusListener
, true);
153 var blurListener = function(evt
){
154 _this
._onBlurNode(effectiveNode
|| evt
.target
);
156 doc
.addEventListener('blur', blurListener
, true);
160 doc
.body
.removeEventListener('mousedown', mousedownListener
, true);
161 doc
.body
.removeEventListener('touchstart', mousedownListener
, true);
162 doc
.removeEventListener('focus', focusListener
, true);
163 doc
.removeEventListener('blur', blurListener
, true);
164 doc
= null; // prevent memory leak (apparent circular reference via closure)
171 _onBlurNode: function(/*DomNode*/ node
){
173 // Called when focus leaves a node.
174 // Usually ignored, _unless_ it *isn't* followed by touching another node,
175 // which indicates that we tabbed off the last field on the page,
176 // in which case every widget is marked inactive
178 // If the blur event isn't followed by a focus event, it means the user clicked on something unfocusable,
180 if(this._clearFocusTimer
){
181 clearTimeout(this._clearFocusTimer
);
183 this._clearFocusTimer
= setTimeout(lang
.hitch(this, function(){
184 this.set("prevNode", this.curNode
);
185 this.set("curNode", null);
188 if(this._justMouseDowned
){
189 // the mouse down caused a new widget to be marked as active; this blur event
190 // is coming late, so ignore it.
194 // If the blur event isn't followed by a focus or touch event then mark all widgets as inactive.
195 if(this._clearActiveWidgetsTimer
){
196 clearTimeout(this._clearActiveWidgetsTimer
);
198 this._clearActiveWidgetsTimer
= setTimeout(lang
.hitch(this, function(){
199 delete this._clearActiveWidgetsTimer
;
204 _onTouchNode: function(/*DomNode*/ node
, /*String*/ by
){
206 // Callback when node is focused or mouse-downed
208 // The node that was touched.
210 // "mouse" if the focus/touch was caused by a mouse down event
212 // ignore the recent blurNode event
213 if(this._clearActiveWidgetsTimer
){
214 clearTimeout(this._clearActiveWidgetsTimer
);
215 delete this._clearActiveWidgetsTimer
;
218 // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem)
222 var popupParent
= domAttr
.get(node
, "dijitPopupParent");
224 node
=registry
.byId(popupParent
).domNode
;
225 }else if(node
.tagName
&& node
.tagName
.toLowerCase() == "body"){
226 // is this the root of the document or just the root of an iframe?
227 if(node
=== win
.body()){
228 // node is the root of the main document
231 // otherwise, find the iframe this node refers to (can't access it via parentNode,
232 // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit
233 node
=winUtils
.get(node
.ownerDocument
).frameElement
;
235 // if this node is the root node of a widget, then add widget id to stack,
236 // except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
237 // to support MenuItem)
238 var id
= node
.getAttribute
&& node
.getAttribute("widgetId"),
239 widget
= id
&& registry
.byId(id
);
240 if(widget
&& !(by
== "mouse" && widget
.get("disabled"))){
241 newStack
.unshift(id
);
243 node
=node
.parentNode
;
246 }catch(e
){ /* squelch */ }
248 this._setStack(newStack
, by
);
251 _onFocusNode: function(/*DomNode*/ node
){
253 // Callback when node is focused
259 if(node
.nodeType
== 9){
260 // Ignore focus events on the document itself. This is here so that
261 // (for example) clicking the up/down arrows of a spinner
262 // (which don't get focus) won't cause that widget to blur. (FF issue)
266 // There was probably a blur event right before this event, but since we have a new focus, don't
267 // do anything with the blur
268 if(this._clearFocusTimer
){
269 clearTimeout(this._clearFocusTimer
);
270 delete this._clearFocusTimer
;
273 this._onTouchNode(node
);
275 if(node
== this.curNode
){ return; }
276 this.set("prevNode", this.curNode
);
277 this.set("curNode", node
);
280 _setStack: function(/*String[]*/ newStack
, /*String*/ by
){
282 // The stack of active widgets has changed. Send out appropriate events and records new stack.
284 // array of widget id's, starting from the top (outermost) widget
286 // "mouse" if the focus/touch was caused by a mouse down event
288 var oldStack
= this.activeStack
;
289 this.set("activeStack", newStack
);
291 // compare old stack to new stack to see how many elements they have in common
292 for(var nCommon
=0; nCommon
<Math
.min(oldStack
.length
, newStack
.length
); nCommon
++){
293 if(oldStack
[nCommon
] != newStack
[nCommon
]){
299 // for all elements that have gone out of focus, set focused=false
300 for(var i
=oldStack
.length
-1; i
>=nCommon
; i
--){
301 widget
= registry
.byId(oldStack
[i
]);
303 widget
._hasBeenBlurred
= true; // TODO: used by form widgets, should be moved there
304 widget
.set("focused", false);
305 if(widget
._focusManager
== this){
308 this.emit("widget-blur", widget
, by
);
312 // for all element that have come into focus, set focused=true
313 for(i
=nCommon
; i
<newStack
.length
; i
++){
314 widget
= registry
.byId(newStack
[i
]);
316 widget
.set("focused", true);
317 if(widget
._focusManager
== this){
320 this.emit("widget-focus", widget
, by
);
325 focus: function(node
){
327 // Focus the specified node, suppressing errors if they occur
329 try{ node
.focus(); }catch(e
){/*quiet*/}
334 var singleton
= new FocusManager();
336 // register top window and all the iframes it contains
338 var handle
= singleton
.registerWin(winUtils
.get(win
.doc
));
340 unload
.addOnWindowUnload(function(){
341 if(handle
){ // because this gets called twice when doh.robot is running
349 // Setup dijit.focus as a pointer to the singleton but also (for backwards compatibility)
350 // as a function to set focus. Remove for 2.0.
351 dijit
.focus = function(node
){
352 singleton
.focus(node
); // indirection here allows dijit/_base/focus.js to override behavior
354 for(var attr
in singleton
){
355 if(!/^_/.test(attr
)){
356 dijit
.focus
[attr
] = typeof singleton
[attr
] == "function" ? lang
.hitch(singleton
, attr
) : singleton
[attr
];
359 singleton
.watch(function(attr
, oldVal
, newVal
){
360 dijit
.focus
[attr
] = newVal
;