123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- /*
- Copyright (c) 2004-2022, The Dojo Foundation All Rights Reserved.
- Available via Academic Free License >= 2.1 OR the modified BSD license.
- see: http://dojotoolkit.org/license for details
- */
- if(!dojo._hasResource["dijit._base.focus"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
- dojo._hasResource["dijit._base.focus"] = true;
- dojo.provide("dijit._base.focus");
- dojo.require("dojo.window");
- dojo.require("dijit._base.manager");
- // summary:
- // These functions are used to query or set the focus and selection.
- //
- // Also, they trace when widgets become activated/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:
- // Returns true if there is no text selected
- return dijit.getBookmark().isCollapsed;
- },
- getBookmark: function(){
- // summary:
- // Retrieves a bookmark that can be used with moveToBookmark to return to the same range
- var bm, rg, tg, sel = dojo.doc.selection, cf = dijit._curFocus;
- if(dojo.global.getSelection){
- //W3C Range API for selections.
- sel = dojo.global.getSelection();
- if(sel){
- if(sel.isCollapsed){
- tg = cf? cf.tagName : "";
- if(tg){
- //Create a fake rangelike item to restore selections.
- tg = tg.toLowerCase();
- if(tg == "textarea" ||
- (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
- sel = {
- start: cf.selectionStart,
- end: cf.selectionEnd,
- node: cf,
- pRange: true
- };
- return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
- }
- }
- bm = {isCollapsed:true};
- if(sel.rangeCount){
- bm.mark = sel.getRangeAt(0).cloneRange();
- }
- }else{
- rg = sel.getRangeAt(0);
- bm = {isCollapsed: false, mark: rg.cloneRange()};
- }
- }
- }else if(sel){
- // If the current focus was a input of some sort and no selection, don't bother saving
- // a native bookmark. This is because it causes issues with dialog/page selection restore.
- // So, we need to create psuedo bookmarks to work with.
- tg = cf ? cf.tagName : "";
- tg = tg.toLowerCase();
- if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
- if(sel.type && sel.type.toLowerCase() == "none"){
- return {
- isCollapsed: true,
- mark: null
- }
- }else{
- rg = sel.createRange();
- return {
- isCollapsed: rg.text && rg.text.length?false:true,
- mark: {
- range: rg,
- pRange: true
- }
- };
- }
- }
- bm = {};
- //'IE' way for selections.
- try{
- // createRange() throws exception when dojo in iframe
- //and nothing selected, see #9632
- rg = sel.createRange();
- bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
- }catch(e){
- bm.isCollapsed = true;
- return bm;
- }
- if(sel.type.toUpperCase() == 'CONTROL'){
- if(rg.length){
- bm.mark=[];
- var i=0,len=rg.length;
- while(i<len){
- bm.mark.push(rg.item(i++));
- }
- }else{
- bm.isCollapsed = true;
- bm.mark = null;
- }
- }else{
- bm.mark = rg.getBookmark();
- }
- }else{
- console.warn("No idea how to store the current selection for this browser!");
- }
- return bm; // Object
- },
- moveToBookmark: function(/*Object*/bookmark){
- // summary:
- // Moves current selection to a bookmark
- // bookmark:
- // This should be a returned object from dijit.getBookmark()
- var _doc = dojo.doc,
- mark = bookmark.mark;
- if(mark){
- if(dojo.global.getSelection){
- //W3C Rangi API (FF, WebKit, Opera, etc)
- var sel = dojo.global.getSelection();
- if(sel && sel.removeAllRanges){
- if(mark.pRange){
- var r = mark;
- var n = r.node;
- n.selectionStart = r.start;
- n.selectionEnd = r.end;
- }else{
- sel.removeAllRanges();
- sel.addRange(mark);
- }
- }else{
- console.warn("No idea how to restore selection for this browser!");
- }
- }else if(_doc.selection && mark){
- //'IE' way.
- var rg;
- if(mark.pRange){
- rg = mark.range;
- }else if(dojo.isArray(mark)){
- rg = _doc.body.createControlRange();
- //rg.addElement does not have call/apply method, so can not call it directly
- //rg is not available in "range.addElement(item)", so can't use that either
- dojo.forEach(mark, function(n){
- rg.addElement(n);
- });
- }else{
- rg = _doc.body.createTextRange();
- rg.moveToBookmark(mark);
- }
- rg.select();
- }
- }
- },
- getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
- // summary:
- // Called as getFocus(), this returns an Object showing the current focus
- // and selected text.
- //
- // Called as getFocus(widget), where widget is a (widget representing) a button
- // that was just pressed, it returns where focus was before that button
- // was pressed. (Pressing the button may have either shifted focus to the button,
- // or removed focus altogether.) In this case the selected text is not returned,
- // since it can't be accurately determined.
- //
- // menu: dijit._Widget or {domNode: DomNode} structure
- // The button that was just pressed. If focus has disappeared or moved
- // to this button, returns the previous focus. In this case the bookmark
- // information is already lost, and null is returned.
- //
- // openedForWindow:
- // iframe in which menu was opened
- //
- // returns:
- // A handle to restore focus/selection, to be passed to `dijit.focus`
- var node = !dijit._curFocus || (menu && dojo.isDescendant(dijit._curFocus, menu.domNode)) ? dijit._prevFocus : dijit._curFocus;
- return {
- node: node,
- bookmark: (node == dijit._curFocus) && dojo.withGlobal(openedForWindow || dojo.global, dijit.getBookmark),
- openedForWindow: openedForWindow
- }; // Object
- },
- focus: function(/*Object || DomNode */ handle){
- // summary:
- // Sets the focused node and the selection according to argument.
- // To set focus to an iframe's content, pass in the iframe itself.
- // handle:
- // object returned by get(), or a DomNode
- if(!handle){ return; }
- var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
- bookmark = handle.bookmark,
- openedForWindow = handle.openedForWindow,
- collapsed = bookmark ? bookmark.isCollapsed : false;
- // Set the focus
- // Note that for iframe's we need to use the <iframe> 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) or if previous selection was collapsed
- // as it may cause focus shift (Esp in IE).
- if(bookmark && dojo.withGlobal(openedForWindow || dojo.global, dijit.isCollapsed) && !collapsed){
- if(openedForWindow){
- openedForWindow.focus();
- }
- try{
- dojo.withGlobal(openedForWindow || dojo.global, dijit.moveToBookmark, null, [bookmark]);
- }catch(e2){
- /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
- }
- }
- },
- // _activeStack: dijit._Widget[]
- // List of currently active widgets (focused widget and it's ancestors)
- _activeStack: [],
- registerIframe: function(/*DomNode*/ iframe){
- // summary:
- // Registers listeners on the specified iframe so that any click
- // or focus event on that iframe (or anything in it) is reported
- // as a focus/click event on the <iframe> itself.
- // description:
- // Currently only used by editor.
- // returns:
- // Handle to pass to unregisterIframe()
- return dijit.registerWin(iframe.contentWindow, iframe);
- },
- unregisterIframe: function(/*Object*/ handle){
- // summary:
- // Unregisters listeners on the specified iframe created by registerIframe.
- // After calling be sure to delete or null out the handle itself.
- // handle:
- // Handle returned by registerIframe()
- dijit.unregisterWin(handle);
- },
- registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
- // summary:
- // Registers listeners on the specified window (either the main
- // window or an iframe's window) to detect when the user has clicked somewhere
- // or focused somewhere.
- // description:
- // Users should call registerIframe() instead of this method.
- // targetWindow:
- // If specified this is the window associated with the iframe,
- // i.e. iframe.contentWindow.
- // effectiveNode:
- // If specified, report any focus events inside targetWindow as
- // an event on effectiveNode, rather than on evt.target.
- // returns:
- // Handle to pass to unregisterWin()
- // TODO: make this function private in 2.0; Editor/users should call registerIframe(),
- var mousedownListener = function(evt){
- dijit._justMouseDowned = true;
- setTimeout(function(){ dijit._justMouseDowned = false; }, 0);
-
- // workaround weird IE bug where the click is on an orphaned node
- // (first time clicking a Select/DropDownButton inside a TooltipDialog)
- if(dojo.isIE && evt && evt.srcElement && evt.srcElement.parentNode == null){
- return;
- }
- dijit._onTouchNode(effectiveNode || evt.target || evt.srcElement, "mouse");
- };
- //dojo.connect(targetWindow, "onscroll", ???);
- // Listen for blur and focus events on targetWindow's document.
- // IIRC, I'm using attachEvent() rather than dojo.connect() because focus/blur events don't bubble
- // through dojo.connect(), and also maybe to catch the focus events early, before onfocus handlers
- // fire.
- // Connect to <html> (rather than document) on IE to avoid memory leaks, but document on other browsers because
- // (at least for FF) the focus event doesn't fire on <html> or <body>.
- var doc = dojo.isIE ? targetWindow.document.documentElement : targetWindow.document;
- if(doc){
- if(dojo.isIE){
- targetWindow.document.body.attachEvent('onmousedown', mousedownListener);
- var activateListener = function(evt){
- // IE reports that nodes like <body> have gotten focus, even though they have tabIndex=-1,
- // Should consider those more like a mouse-click than a focus....
- if(evt.srcElement.tagName.toLowerCase() != "#document" &&
- dijit.isTabNavigable(evt.srcElement)){
- dijit._onFocusNode(effectiveNode || evt.srcElement);
- }else{
- dijit._onTouchNode(effectiveNode || evt.srcElement);
- }
- };
- doc.attachEvent('onactivate', activateListener);
- var deactivateListener = function(evt){
- dijit._onBlurNode(effectiveNode || evt.srcElement);
- };
- doc.attachEvent('ondeactivate', deactivateListener);
- return function(){
- targetWindow.document.detachEvent('onmousedown', mousedownListener);
- doc.detachEvent('onactivate', activateListener);
- doc.detachEvent('ondeactivate', deactivateListener);
- doc = null; // prevent memory leak (apparent circular reference via closure)
- };
- }else if(doc.body){
- doc.body.addEventListener('mousedown', mousedownListener, true);
- var focusListener = function(evt){
- dijit._onFocusNode(effectiveNode || evt.target);
- };
- doc.addEventListener('focus', focusListener, true);
- var blurListener = function(evt){
- dijit._onBlurNode(effectiveNode || evt.target);
- };
- doc.addEventListener('blur', blurListener, true);
- return function(){
- doc.body.removeEventListener('mousedown', mousedownListener, true);
- doc.removeEventListener('focus', focusListener, true);
- doc.removeEventListener('blur', blurListener, true);
- doc = null; // prevent memory leak (apparent circular reference via closure)
- };
- }
- }
- },
- unregisterWin: function(/*Handle*/ handle){
- // summary:
- // Unregisters listeners on the specified window (either the main
- // window or an iframe's window) according to handle returned from registerWin().
- // After calling be sure to delete or null out the handle itself.
- // Currently our handle is actually a function
- handle && handle();
- },
- _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, /*String*/ by){
- // summary:
- // Callback when node is focused or mouse-downed
- // node:
- // The node that was touched.
- // by:
- // "mouse" if the focus/touch was caused by a mouse down event
- // 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){
- var popupParent = dojo.attr(node, "dijitPopupParent");
- if(popupParent){
- node=dijit.byId(popupParent).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=dojo.window.get(node.ownerDocument).frameElement;
- }else{
- // if this node is the root node of a widget, then add widget id to stack,
- // except ignore clicks on disabled widgets (actually focusing a disabled widget still works,
- // to support MenuItem)
- var id = node.getAttribute && node.getAttribute("widgetId"),
- widget = id && dijit.byId(id);
- if(widget && !(by == "mouse" && widget.get("disabled"))){
- newStack.unshift(id);
- }
- node=node.parentNode;
- }
- }
- }catch(e){ /* squelch */ }
- dijit._setStack(newStack, by);
- },
- _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;
- }
- dijit._onTouchNode(node);
- if(node == dijit._curFocus){ return; }
- if(dijit._curFocus){
- dijit._prevFocus = dijit._curFocus;
- }
- dijit._curFocus = node;
- dojo.publish("focusNode", [node]);
- },
- _setStack: function(/*String[]*/ newStack, /*String*/ by){
- // summary:
- // The stack of active widgets has changed. Send out appropriate events and records new stack.
- // newStack:
- // array of widget id's, starting from the top (outermost) widget
- // by:
- // "mouse" if the focus/touch was caused by a mouse down event
- 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<Math.min(oldStack.length, newStack.length); nCommon++){
- if(oldStack[nCommon] != newStack[nCommon]){
- break;
- }
- }
- var widget;
- // for all elements that have gone out of focus, send blur event
- for(var i=oldStack.length-1; i>=nCommon; i--){
- widget = dijit.byId(oldStack[i]);
- if(widget){
- widget._focused = false;
- widget.set("focused", false);
- widget._hasBeenBlurred = true;
- if(widget._onBlur){
- widget._onBlur(by);
- }
- dojo.publish("widgetBlur", [widget, by]);
- }
- }
- // for all element that have come into focus, send focus event
- for(i=nCommon; i<newStack.length; i++){
- widget = dijit.byId(newStack[i]);
- if(widget){
- widget._focused = true;
- widget.set("focused", true);
- if(widget._onFocus){
- widget._onFocus(by);
- }
- dojo.publish("widgetFocus", [widget, by]);
- }
- }
- }
- });
- // register top window and all the iframes it contains
- dojo.addOnLoad(function(){
- var handle = dijit.registerWin(window);
- if(dojo.isIE){
- dojo.addOnWindowUnload(function(){
- dijit.unregisterWin(handle);
- handle = null;
- })
- }
- });
- }
|