focus.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. define("dijit/_base/focus", [
  2. "dojo/_base/array", // array.forEach
  3. "dojo/dom", // dom.isDescendant
  4. "dojo/_base/lang", // lang.isArray
  5. "dojo/topic", // publish
  6. "dojo/_base/window", // win.doc win.doc.selection win.global win.global.getSelection win.withGlobal
  7. "../focus",
  8. ".." // for exporting symbols to dijit
  9. ], function(array, dom, lang, topic, win, focus, dijit){
  10. // module:
  11. // dijit/_base/focus
  12. // summary:
  13. // Deprecated module to monitor currently focused node and stack of currently focused widgets.
  14. // New code should access dijit/focus directly.
  15. lang.mixin(dijit, {
  16. // _curFocus: DomNode
  17. // Currently focused item on screen
  18. _curFocus: null,
  19. // _prevFocus: DomNode
  20. // Previously focused item on screen
  21. _prevFocus: null,
  22. isCollapsed: function(){
  23. // summary:
  24. // Returns true if there is no text selected
  25. return dijit.getBookmark().isCollapsed;
  26. },
  27. getBookmark: function(){
  28. // summary:
  29. // Retrieves a bookmark that can be used with moveToBookmark to return to the same range
  30. var bm, rg, tg, sel = win.doc.selection, cf = focus.curNode;
  31. if(win.global.getSelection){
  32. //W3C Range API for selections.
  33. sel = win.global.getSelection();
  34. if(sel){
  35. if(sel.isCollapsed){
  36. tg = cf? cf.tagName : "";
  37. if(tg){
  38. //Create a fake rangelike item to restore selections.
  39. tg = tg.toLowerCase();
  40. if(tg == "textarea" ||
  41. (tg == "input" && (!cf.type || cf.type.toLowerCase() == "text"))){
  42. sel = {
  43. start: cf.selectionStart,
  44. end: cf.selectionEnd,
  45. node: cf,
  46. pRange: true
  47. };
  48. return {isCollapsed: (sel.end <= sel.start), mark: sel}; //Object.
  49. }
  50. }
  51. bm = {isCollapsed:true};
  52. if(sel.rangeCount){
  53. bm.mark = sel.getRangeAt(0).cloneRange();
  54. }
  55. }else{
  56. rg = sel.getRangeAt(0);
  57. bm = {isCollapsed: false, mark: rg.cloneRange()};
  58. }
  59. }
  60. }else if(sel){
  61. // If the current focus was a input of some sort and no selection, don't bother saving
  62. // a native bookmark. This is because it causes issues with dialog/page selection restore.
  63. // So, we need to create psuedo bookmarks to work with.
  64. tg = cf ? cf.tagName : "";
  65. tg = tg.toLowerCase();
  66. if(cf && tg && (tg == "button" || tg == "textarea" || tg == "input")){
  67. if(sel.type && sel.type.toLowerCase() == "none"){
  68. return {
  69. isCollapsed: true,
  70. mark: null
  71. }
  72. }else{
  73. rg = sel.createRange();
  74. return {
  75. isCollapsed: rg.text && rg.text.length?false:true,
  76. mark: {
  77. range: rg,
  78. pRange: true
  79. }
  80. };
  81. }
  82. }
  83. bm = {};
  84. //'IE' way for selections.
  85. try{
  86. // createRange() throws exception when dojo in iframe
  87. //and nothing selected, see #9632
  88. rg = sel.createRange();
  89. bm.isCollapsed = !(sel.type == 'Text' ? rg.htmlText.length : rg.length);
  90. }catch(e){
  91. bm.isCollapsed = true;
  92. return bm;
  93. }
  94. if(sel.type.toUpperCase() == 'CONTROL'){
  95. if(rg.length){
  96. bm.mark=[];
  97. var i=0,len=rg.length;
  98. while(i<len){
  99. bm.mark.push(rg.item(i++));
  100. }
  101. }else{
  102. bm.isCollapsed = true;
  103. bm.mark = null;
  104. }
  105. }else{
  106. bm.mark = rg.getBookmark();
  107. }
  108. }else{
  109. console.warn("No idea how to store the current selection for this browser!");
  110. }
  111. return bm; // Object
  112. },
  113. moveToBookmark: function(/*Object*/ bookmark){
  114. // summary:
  115. // Moves current selection to a bookmark
  116. // bookmark:
  117. // This should be a returned object from dijit.getBookmark()
  118. var _doc = win.doc,
  119. mark = bookmark.mark;
  120. if(mark){
  121. if(win.global.getSelection){
  122. //W3C Rangi API (FF, WebKit, Opera, etc)
  123. var sel = win.global.getSelection();
  124. if(sel && sel.removeAllRanges){
  125. if(mark.pRange){
  126. var n = mark.node;
  127. n.selectionStart = mark.start;
  128. n.selectionEnd = mark.end;
  129. }else{
  130. sel.removeAllRanges();
  131. sel.addRange(mark);
  132. }
  133. }else{
  134. console.warn("No idea how to restore selection for this browser!");
  135. }
  136. }else if(_doc.selection && mark){
  137. //'IE' way.
  138. var rg;
  139. if(mark.pRange){
  140. rg = mark.range;
  141. }else if(lang.isArray(mark)){
  142. rg = _doc.body.createControlRange();
  143. //rg.addElement does not have call/apply method, so can not call it directly
  144. //rg is not available in "range.addElement(item)", so can't use that either
  145. array.forEach(mark, function(n){
  146. rg.addElement(n);
  147. });
  148. }else{
  149. rg = _doc.body.createTextRange();
  150. rg.moveToBookmark(mark);
  151. }
  152. rg.select();
  153. }
  154. }
  155. },
  156. getFocus: function(/*Widget?*/ menu, /*Window?*/ openedForWindow){
  157. // summary:
  158. // Called as getFocus(), this returns an Object showing the current focus
  159. // and selected text.
  160. //
  161. // Called as getFocus(widget), where widget is a (widget representing) a button
  162. // that was just pressed, it returns where focus was before that button
  163. // was pressed. (Pressing the button may have either shifted focus to the button,
  164. // or removed focus altogether.) In this case the selected text is not returned,
  165. // since it can't be accurately determined.
  166. //
  167. // menu: dijit._Widget or {domNode: DomNode} structure
  168. // The button that was just pressed. If focus has disappeared or moved
  169. // to this button, returns the previous focus. In this case the bookmark
  170. // information is already lost, and null is returned.
  171. //
  172. // openedForWindow:
  173. // iframe in which menu was opened
  174. //
  175. // returns:
  176. // A handle to restore focus/selection, to be passed to `dijit.focus`
  177. var node = !focus.curNode || (menu && dom.isDescendant(focus.curNode, menu.domNode)) ? dijit._prevFocus : focus.curNode;
  178. return {
  179. node: node,
  180. bookmark: node && (node == focus.curNode) && win.withGlobal(openedForWindow || win.global, dijit.getBookmark),
  181. openedForWindow: openedForWindow
  182. }; // Object
  183. },
  184. // _activeStack: dijit._Widget[]
  185. // List of currently active widgets (focused widget and it's ancestors)
  186. _activeStack: [],
  187. registerIframe: function(/*DomNode*/ iframe){
  188. // summary:
  189. // Registers listeners on the specified iframe so that any click
  190. // or focus event on that iframe (or anything in it) is reported
  191. // as a focus/click event on the <iframe> itself.
  192. // description:
  193. // Currently only used by editor.
  194. // returns:
  195. // Handle to pass to unregisterIframe()
  196. return focus.registerIframe(iframe);
  197. },
  198. unregisterIframe: function(/*Object*/ handle){
  199. // summary:
  200. // Unregisters listeners on the specified iframe created by registerIframe.
  201. // After calling be sure to delete or null out the handle itself.
  202. // handle:
  203. // Handle returned by registerIframe()
  204. handle && handle.remove();
  205. },
  206. registerWin: function(/*Window?*/targetWindow, /*DomNode?*/ effectiveNode){
  207. // summary:
  208. // Registers listeners on the specified window (either the main
  209. // window or an iframe's window) to detect when the user has clicked somewhere
  210. // or focused somewhere.
  211. // description:
  212. // Users should call registerIframe() instead of this method.
  213. // targetWindow:
  214. // If specified this is the window associated with the iframe,
  215. // i.e. iframe.contentWindow.
  216. // effectiveNode:
  217. // If specified, report any focus events inside targetWindow as
  218. // an event on effectiveNode, rather than on evt.target.
  219. // returns:
  220. // Handle to pass to unregisterWin()
  221. return focus.registerWin(targetWindow, effectiveNode);
  222. },
  223. unregisterWin: function(/*Handle*/ handle){
  224. // summary:
  225. // Unregisters listeners on the specified window (either the main
  226. // window or an iframe's window) according to handle returned from registerWin().
  227. // After calling be sure to delete or null out the handle itself.
  228. handle && handle.remove();
  229. }
  230. });
  231. // Override focus singleton's focus function so that dijit.focus()
  232. // has backwards compatible behavior of restoring selection (although
  233. // probably no one is using that).
  234. focus.focus = function(/*Object || DomNode */ handle){
  235. // summary:
  236. // Sets the focused node and the selection according to argument.
  237. // To set focus to an iframe's content, pass in the iframe itself.
  238. // handle:
  239. // object returned by get(), or a DomNode
  240. if(!handle){ return; }
  241. var node = "node" in handle ? handle.node : handle, // because handle is either DomNode or a composite object
  242. bookmark = handle.bookmark,
  243. openedForWindow = handle.openedForWindow,
  244. collapsed = bookmark ? bookmark.isCollapsed : false;
  245. // Set the focus
  246. // Note that for iframe's we need to use the <iframe> to follow the parentNode chain,
  247. // but we need to set focus to iframe.contentWindow
  248. if(node){
  249. var focusNode = (node.tagName.toLowerCase() == "iframe") ? node.contentWindow : node;
  250. if(focusNode && focusNode.focus){
  251. try{
  252. // Gecko throws sometimes if setting focus is impossible,
  253. // node not displayed or something like that
  254. focusNode.focus();
  255. }catch(e){/*quiet*/}
  256. }
  257. focus._onFocusNode(node);
  258. }
  259. // set the selection
  260. // do not need to restore if current selection is not empty
  261. // (use keyboard to select a menu item) or if previous selection was collapsed
  262. // as it may cause focus shift (Esp in IE).
  263. if(bookmark && win.withGlobal(openedForWindow || win.global, dijit.isCollapsed) && !collapsed){
  264. if(openedForWindow){
  265. openedForWindow.focus();
  266. }
  267. try{
  268. win.withGlobal(openedForWindow || win.global, dijit.moveToBookmark, null, [bookmark]);
  269. }catch(e2){
  270. /*squelch IE internal error, see http://trac.dojotoolkit.org/ticket/1984 */
  271. }
  272. }
  273. };
  274. // For back compatibility, monitor changes to focused node and active widget stack,
  275. // publishing events and copying changes from focus manager variables into dijit (top level) variables
  276. focus.watch("curNode", function(name, oldVal, newVal){
  277. dijit._curFocus = newVal;
  278. dijit._prevFocus = oldVal;
  279. if(newVal){
  280. topic.publish("focusNode", newVal); // publish
  281. }
  282. });
  283. focus.watch("activeStack", function(name, oldVal, newVal){
  284. dijit._activeStack = newVal;
  285. });
  286. focus.on("widget-blur", function(widget, by){
  287. topic.publish("widgetBlur", widget, by); // publish
  288. });
  289. focus.on("widget-focus", function(widget, by){
  290. topic.publish("widgetFocus", widget, by); // publish
  291. });
  292. return dijit;
  293. });