dojo.provide("dijit._base.focus"); // summary: // These functions are used to query or set the focus and selection. // // Also, they trace when widgets become actived/deactivated, // so that the widget can fire _onFocus/_onBlur events. // "Active" here means something similar to "focused", but // "focus" isn't quite the right word because we keep track of // a whole stack of "active" widgets. Example: Combobutton --> Menu --> // MenuItem. The onBlur event for Combobutton doesn't fire due to focusing // on the Menu or a MenuItem, since they are considered part of the // Combobutton widget. It only happens when focus is shifted // somewhere completely different. dojo.mixin(dijit, { // _curFocus: DomNode // Currently focused item on screen _curFocus: null, // _prevFocus: DomNode // Previously focused item on screen _prevFocus: null, isCollapsed: function(){ // summary: tests whether the current selection is empty var _document = dojo.doc; if(_document.selection){ // IE var s=_document.selection; if(s.type=='Text'){ return !s.createRange().htmlText.length; // Boolean }else{ //Control range return !s.createRange().length; // Boolean } }else{ var _window = dojo.global; var selection = _window.getSelection(); if(dojo.isString(selection)){ // Safari return !selection; // Boolean }else{ // Mozilla/W3 return selection.isCollapsed || !selection.toString(); // Boolean } } }, getBookmark: function(){ // summary: Retrieves a bookmark that can be used with moveToBookmark to return to the same range var bookmark, selection = dojo.doc.selection; if(selection){ // IE var range = selection.createRange(); if(selection.type.toUpperCase()=='CONTROL'){ if(range.length){ bookmark=[]; var i=0,len=range.length; while(i to follow the parentNode chain, // but we need to set focus to iframe.contentWindow if(node){ var focusNode = (node.tagName.toLowerCase()=="iframe") ? node.contentWindow : node; if(focusNode && focusNode.focus){ try{ // Gecko throws sometimes if setting focus is impossible, // node not displayed or something like that focusNode.focus(); }catch(e){/*quiet*/} } dijit._onFocusNode(node); } // set the selection // do not need to restore if current selection is not empty // (use keyboard to select a menu item) if(bookmark && dojo.withGlobal(openedForWindow||dojo.global, dijit.isCollapsed)){ if(openedForWindow){ openedForWindow.focus(); } try{ dojo.withGlobal(openedForWindow||dojo.global, dijit.moveToBookmark, null, [bookmark]); }catch(e){ /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */ } } }, // _activeStack: Array // List of currently active widgets (focused widget and it's ancestors) _activeStack: [], registerWin: function(/*Window?*/targetWindow){ // summary: // Registers listeners on the specified window (either the main // window or an iframe) to detect when the user has clicked somewhere. // Anyone that creates an iframe should call this function. if(!targetWindow){ targetWindow = window; } dojo.connect(targetWindow.document, "onmousedown", function(evt){ dijit._justMouseDowned = true; setTimeout(function(){ dijit._justMouseDowned = false; }, 0); dijit._onTouchNode(evt.target||evt.srcElement); }); //dojo.connect(targetWindow, "onscroll", ???); // Listen for blur and focus events on targetWindow's body var doc = targetWindow.document; if(doc){ if(dojo.isIE){ doc.attachEvent('onactivate', function(evt){ if(evt.srcElement.tagName.toLowerCase() != "#document"){ dijit._onFocusNode(evt.srcElement); } }); doc.attachEvent('ondeactivate', function(evt){ dijit._onBlurNode(evt.srcElement); }); }else{ doc.addEventListener('focus', function(evt){ dijit._onFocusNode(evt.target); }, true); doc.addEventListener('blur', function(evt){ dijit._onBlurNode(evt.target); }, true); } } doc = null; // prevent memory leak (apparent circular reference via closure) }, _onBlurNode: function(/*DomNode*/ node){ // summary: // Called when focus leaves a node. // Usually ignored, _unless_ it *isn't* follwed by touching another node, // which indicates that we tabbed off the last field on the page, // in which case every widget is marked inactive dijit._prevFocus = dijit._curFocus; dijit._curFocus = null; if(dijit._justMouseDowned){ // the mouse down caused a new widget to be marked as active; this blur event // is coming late, so ignore it. return; } // if the blur event isn't followed by a focus event then mark all widgets as inactive. if(dijit._clearActiveWidgetsTimer){ clearTimeout(dijit._clearActiveWidgetsTimer); } dijit._clearActiveWidgetsTimer = setTimeout(function(){ delete dijit._clearActiveWidgetsTimer; dijit._setStack([]); dijit._prevFocus = null; }, 100); }, _onTouchNode: function(/*DomNode*/ node){ // summary: // Callback when node is focused or mouse-downed // ignore the recent blurNode event if(dijit._clearActiveWidgetsTimer){ clearTimeout(dijit._clearActiveWidgetsTimer); delete dijit._clearActiveWidgetsTimer; } // compute stack of active widgets (ex: ComboButton --> Menu --> MenuItem) var newStack=[]; try{ while(node){ if(node.dijitPopupParent){ node=dijit.byId(node.dijitPopupParent).domNode; }else if(node.tagName && node.tagName.toLowerCase()=="body"){ // is this the root of the document or just the root of an iframe? if(node===dojo.body()){ // node is the root of the main document break; } // otherwise, find the iframe this node refers to (can't access it via parentNode, // need to do this trick instead). window.frameElement is supported in IE/FF/Webkit node=dijit.getDocumentWindow(node.ownerDocument).frameElement; }else{ var id = node.getAttribute && node.getAttribute("widgetId"); if(id){ newStack.unshift(id); } node=node.parentNode; } } }catch(e){ /* squelch */ } dijit._setStack(newStack); }, _onFocusNode: function(/*DomNode*/ node){ // summary // Callback when node is focused if(!node){ return; } if(node.nodeType == 9){ // Ignore focus events on the document itself. This is here so that // (for example) clicking the up/down arrows of a spinner // (which don't get focus) won't cause that widget to blur. (FF issue) return; } if(node.nodeType == 9){ // We focused on (the body of) the document itself, either the main document // or an iframe var iframe = dijit.getDocumentWindow(node).frameElement; if(!iframe){ // Ignore focus events on main document. This is specifically here // so that clicking the up/down arrows of a spinner (which don't get focus) // won't cause that widget to blur. return; } node = iframe; } dijit._onTouchNode(node); if(node==dijit._curFocus){ return; } if(dijit._curFocus){ dijit._prevFocus = dijit._curFocus; } dijit._curFocus = node; dojo.publish("focusNode", [node]); }, _setStack: function(newStack){ // summary // The stack of active widgets has changed. Send out appropriate events and record new stack var oldStack = dijit._activeStack; dijit._activeStack = newStack; // compare old stack to new stack to see how many elements they have in common for(var nCommon=0; nCommon=nCommon; i--){ var widget = dijit.byId(oldStack[i]); if(widget){ widget._focused = false; widget._hasBeenBlurred = true; if(widget._onBlur){ widget._onBlur(); } if (widget._setStateClass){ widget._setStateClass(); } dojo.publish("widgetBlur", [widget]); } } // for all element that have come into focus, send focus event for(i=nCommon; i