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