]>
Commit | Line | Data |
---|---|---|
2f01fe57 | 1 | /* |
81bea17a | 2 | Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. |
2f01fe57 AD |
3 | Available via Academic Free License >= 2.1 OR the modified BSD license. |
4 | see: http://dojotoolkit.org/license for details | |
5 | */ | |
6 | ||
7 | ||
81bea17a AD |
8 | if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
9 | dojo._hasResource["dijit.Menu"] = true; | |
2f01fe57 AD |
10 | dojo.provide("dijit.Menu"); |
11 | dojo.require("dojo.window"); | |
12 | dojo.require("dijit._Widget"); | |
13 | dojo.require("dijit._KeyNavContainer"); | |
14 | dojo.require("dijit._Templated"); | |
2f01fe57 AD |
15 | dojo.require("dijit.MenuItem"); |
16 | dojo.require("dijit.PopupMenuItem"); | |
17 | dojo.require("dijit.CheckedMenuItem"); | |
18 | dojo.require("dijit.MenuSeparator"); | |
81bea17a AD |
19 | |
20 | ||
21 | // "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0) | |
22 | ||
23 | dojo.declare("dijit._MenuBase", | |
24 | [dijit._Widget, dijit._Templated, dijit._KeyNavContainer], | |
25 | { | |
26 | // summary: | |
27 | // Base class for Menu and MenuBar | |
28 | ||
29 | // parentMenu: [readonly] Widget | |
30 | // pointer to menu that displayed me | |
31 | parentMenu: null, | |
32 | ||
33 | // popupDelay: Integer | |
34 | // number of milliseconds before hovering (without clicking) causes the popup to automatically open. | |
35 | popupDelay: 500, | |
36 | ||
37 | startup: function(){ | |
38 | if(this._started){ return; } | |
39 | ||
40 | dojo.forEach(this.getChildren(), function(child){ child.startup(); }); | |
41 | this.startupKeyNavChildren(); | |
42 | ||
43 | this.inherited(arguments); | |
44 | }, | |
45 | ||
46 | onExecute: function(){ | |
47 | // summary: | |
48 | // Attach point for notification about when a menu item has been executed. | |
49 | // This is an internal mechanism used for Menus to signal to their parent to | |
50 | // close them, because they are about to execute the onClick handler. In | |
51 | // general developers should not attach to or override this method. | |
52 | // tags: | |
53 | // protected | |
54 | }, | |
55 | ||
56 | onCancel: function(/*Boolean*/ closeAll){ | |
57 | // summary: | |
58 | // Attach point for notification about when the user cancels the current menu | |
59 | // This is an internal mechanism used for Menus to signal to their parent to | |
60 | // close them. In general developers should not attach to or override this method. | |
61 | // tags: | |
62 | // protected | |
63 | }, | |
64 | ||
65 | _moveToPopup: function(/*Event*/ evt){ | |
66 | // summary: | |
67 | // This handles the right arrow key (left arrow key on RTL systems), | |
68 | // which will either open a submenu, or move to the next item in the | |
69 | // ancestor MenuBar | |
70 | // tags: | |
71 | // private | |
72 | ||
73 | if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){ | |
74 | this.focusedChild._onClick(evt); | |
75 | }else{ | |
76 | var topMenu = this._getTopMenu(); | |
77 | if(topMenu && topMenu._isMenuBar){ | |
78 | topMenu.focusNext(); | |
79 | } | |
80 | } | |
81 | }, | |
82 | ||
83 | _onPopupHover: function(/*Event*/ evt){ | |
84 | // summary: | |
85 | // This handler is called when the mouse moves over the popup. | |
86 | // tags: | |
87 | // private | |
88 | ||
89 | // if the mouse hovers over a menu popup that is in pending-close state, | |
90 | // then stop the close operation. | |
91 | // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker) | |
92 | if(this.currentPopup && this.currentPopup._pendingClose_timer){ | |
93 | var parentMenu = this.currentPopup.parentMenu; | |
94 | // highlight the parent menu item pointing to this popup | |
95 | if(parentMenu.focusedChild){ | |
96 | parentMenu.focusedChild._setSelected(false); | |
97 | } | |
98 | parentMenu.focusedChild = this.currentPopup.from_item; | |
99 | parentMenu.focusedChild._setSelected(true); | |
100 | // cancel the pending close | |
101 | this._stopPendingCloseTimer(this.currentPopup); | |
102 | } | |
103 | }, | |
104 | ||
105 | onItemHover: function(/*MenuItem*/ item){ | |
106 | // summary: | |
107 | // Called when cursor is over a MenuItem. | |
108 | // tags: | |
109 | // protected | |
110 | ||
111 | // Don't do anything unless user has "activated" the menu by: | |
112 | // 1) clicking it | |
113 | // 2) opening it from a parent menu (which automatically focuses it) | |
114 | if(this.isActive){ | |
115 | this.focusChild(item); | |
116 | if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){ | |
117 | this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay); | |
118 | } | |
119 | } | |
120 | // if the user is mixing mouse and keyboard navigation, | |
121 | // then the menu may not be active but a menu item has focus, | |
122 | // but it's not the item that the mouse just hovered over. | |
123 | // To avoid both keyboard and mouse selections, use the latest. | |
124 | if(this.focusedChild){ | |
125 | this.focusChild(item); | |
126 | } | |
127 | this._hoveredChild = item; | |
128 | }, | |
129 | ||
130 | _onChildBlur: function(item){ | |
131 | // summary: | |
132 | // Called when a child MenuItem becomes inactive because focus | |
133 | // has been removed from the MenuItem *and* it's descendant menus. | |
134 | // tags: | |
135 | // private | |
136 | this._stopPopupTimer(); | |
137 | item._setSelected(false); | |
138 | // Close all popups that are open and descendants of this menu | |
139 | var itemPopup = item.popup; | |
140 | if(itemPopup){ | |
141 | this._stopPendingCloseTimer(itemPopup); | |
142 | itemPopup._pendingClose_timer = setTimeout(function(){ | |
143 | itemPopup._pendingClose_timer = null; | |
144 | if(itemPopup.parentMenu){ | |
145 | itemPopup.parentMenu.currentPopup = null; | |
146 | } | |
147 | dijit.popup.close(itemPopup); // this calls onClose | |
148 | }, this.popupDelay); | |
149 | } | |
150 | }, | |
151 | ||
152 | onItemUnhover: function(/*MenuItem*/ item){ | |
153 | // summary: | |
154 | // Callback fires when mouse exits a MenuItem | |
155 | // tags: | |
156 | // protected | |
157 | ||
158 | if(this.isActive){ | |
159 | this._stopPopupTimer(); | |
160 | } | |
161 | if(this._hoveredChild == item){ this._hoveredChild = null; } | |
162 | }, | |
163 | ||
164 | _stopPopupTimer: function(){ | |
165 | // summary: | |
166 | // Cancels the popup timer because the user has stop hovering | |
167 | // on the MenuItem, etc. | |
168 | // tags: | |
169 | // private | |
170 | if(this.hover_timer){ | |
171 | clearTimeout(this.hover_timer); | |
172 | this.hover_timer = null; | |
173 | } | |
174 | }, | |
175 | ||
176 | _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){ | |
177 | // summary: | |
178 | // Cancels the pending-close timer because the close has been preempted | |
179 | // tags: | |
180 | // private | |
181 | if(popup._pendingClose_timer){ | |
182 | clearTimeout(popup._pendingClose_timer); | |
183 | popup._pendingClose_timer = null; | |
184 | } | |
185 | }, | |
186 | ||
187 | _stopFocusTimer: function(){ | |
188 | // summary: | |
189 | // Cancels the pending-focus timer because the menu was closed before focus occured | |
190 | // tags: | |
191 | // private | |
192 | if(this._focus_timer){ | |
193 | clearTimeout(this._focus_timer); | |
194 | this._focus_timer = null; | |
195 | } | |
196 | }, | |
197 | ||
198 | _getTopMenu: function(){ | |
199 | // summary: | |
200 | // Returns the top menu in this chain of Menus | |
201 | // tags: | |
202 | // private | |
203 | for(var top=this; top.parentMenu; top=top.parentMenu); | |
204 | return top; | |
205 | }, | |
206 | ||
207 | onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){ | |
208 | // summary: | |
209 | // Handle clicks on an item. | |
210 | // tags: | |
211 | // private | |
212 | ||
213 | // this can't be done in _onFocus since the _onFocus events occurs asynchronously | |
214 | if(typeof this.isShowingNow == 'undefined'){ // non-popup menu | |
215 | this._markActive(); | |
216 | } | |
217 | ||
218 | this.focusChild(item); | |
219 | ||
220 | if(item.disabled){ return false; } | |
221 | ||
222 | if(item.popup){ | |
223 | this._openPopup(); | |
224 | }else{ | |
225 | // before calling user defined handler, close hierarchy of menus | |
226 | // and restore focus to place it was when menu was opened | |
227 | this.onExecute(); | |
228 | ||
229 | // user defined handler for click | |
230 | item.onClick(evt); | |
231 | } | |
232 | }, | |
233 | ||
234 | _openPopup: function(){ | |
235 | // summary: | |
236 | // Open the popup to the side of/underneath the current menu item | |
237 | // tags: | |
238 | // protected | |
239 | ||
240 | this._stopPopupTimer(); | |
241 | var from_item = this.focusedChild; | |
242 | if(!from_item){ return; } // the focused child lost focus since the timer was started | |
243 | var popup = from_item.popup; | |
244 | if(popup.isShowingNow){ return; } | |
245 | if(this.currentPopup){ | |
246 | this._stopPendingCloseTimer(this.currentPopup); | |
247 | dijit.popup.close(this.currentPopup); | |
248 | } | |
249 | popup.parentMenu = this; | |
250 | popup.from_item = from_item; // helps finding the parent item that should be focused for this popup | |
251 | var self = this; | |
252 | dijit.popup.open({ | |
253 | parent: this, | |
254 | popup: popup, | |
255 | around: from_item.domNode, | |
256 | orient: this._orient || (this.isLeftToRight() ? | |
257 | {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} : | |
258 | {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}), | |
259 | onCancel: function(){ // called when the child menu is canceled | |
260 | // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus | |
261 | // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro) | |
262 | self.focusChild(from_item); // put focus back on my node | |
263 | self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved) | |
264 | from_item._setSelected(true); // oops, _cleanUp() deselected the item | |
265 | self.focusedChild = from_item; // and unset focusedChild | |
266 | }, | |
267 | onExecute: dojo.hitch(this, "_cleanUp") | |
268 | }); | |
269 | ||
270 | this.currentPopup = popup; | |
271 | // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items | |
272 | popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close | |
273 | ||
274 | if(popup.focus){ | |
275 | // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar), | |
276 | // if the cursor happens to collide with the popup, it will generate an onmouseover event | |
277 | // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that | |
278 | // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742) | |
279 | popup._focus_timer = setTimeout(dojo.hitch(popup, function(){ | |
280 | this._focus_timer = null; | |
281 | this.focus(); | |
282 | }), 0); | |
283 | } | |
284 | }, | |
285 | ||
286 | _markActive: function(){ | |
287 | // summary: | |
288 | // Mark this menu's state as active. | |
289 | // Called when this Menu gets focus from: | |
290 | // 1) clicking it (mouse or via space/arrow key) | |
291 | // 2) being opened by a parent menu. | |
292 | // This is not called just from mouse hover. | |
293 | // Focusing a menu via TAB does NOT automatically set isActive | |
294 | // since TAB is a navigation operation and not a selection one. | |
295 | // For Windows apps, pressing the ALT key focuses the menubar | |
296 | // menus (similar to TAB navigation) but the menu is not active | |
297 | // (ie no dropdown) until an item is clicked. | |
298 | this.isActive = true; | |
299 | dojo.replaceClass(this.domNode, "dijitMenuActive", "dijitMenuPassive"); | |
300 | }, | |
301 | ||
302 | onOpen: function(/*Event*/ e){ | |
303 | // summary: | |
304 | // Callback when this menu is opened. | |
305 | // This is called by the popup manager as notification that the menu | |
306 | // was opened. | |
307 | // tags: | |
308 | // private | |
309 | ||
310 | this.isShowingNow = true; | |
311 | this._markActive(); | |
312 | }, | |
313 | ||
314 | _markInactive: function(){ | |
315 | // summary: | |
316 | // Mark this menu's state as inactive. | |
317 | this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here | |
318 | dojo.replaceClass(this.domNode, "dijitMenuPassive", "dijitMenuActive"); | |
319 | }, | |
320 | ||
321 | onClose: function(){ | |
322 | // summary: | |
323 | // Callback when this menu is closed. | |
324 | // This is called by the popup manager as notification that the menu | |
325 | // was closed. | |
326 | // tags: | |
327 | // private | |
328 | ||
329 | this._stopFocusTimer(); | |
330 | this._markInactive(); | |
331 | this.isShowingNow = false; | |
332 | this.parentMenu = null; | |
333 | }, | |
334 | ||
335 | _closeChild: function(){ | |
336 | // summary: | |
337 | // Called when submenu is clicked or focus is lost. Close hierarchy of menus. | |
338 | // tags: | |
339 | // private | |
340 | this._stopPopupTimer(); | |
341 | ||
342 | var fromItem = this.focusedChild && this.focusedChild.from_item; | |
343 | ||
344 | if(this.currentPopup){ | |
345 | // If focus is on my child menu then move focus to me, | |
346 | // because IE doesn't like it when you display:none a node with focus | |
347 | if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){ | |
348 | this.focusedChild.focusNode.focus(); | |
349 | } | |
350 | // Close all popups that are open and descendants of this menu | |
351 | dijit.popup.close(this.currentPopup); | |
352 | this.currentPopup = null; | |
353 | } | |
354 | ||
355 | if(this.focusedChild){ // unhighlight the focused item | |
356 | this.focusedChild._setSelected(false); | |
357 | this.focusedChild._onUnhover(); | |
358 | this.focusedChild = null; | |
359 | } | |
360 | }, | |
361 | ||
362 | _onItemFocus: function(/*MenuItem*/ item){ | |
363 | // summary: | |
364 | // Called when child of this Menu gets focus from: | |
365 | // 1) clicking it | |
366 | // 2) tabbing into it | |
367 | // 3) being opened by a parent menu. | |
368 | // This is not called just from mouse hover. | |
369 | if(this._hoveredChild && this._hoveredChild != item){ | |
370 | this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection | |
371 | } | |
372 | }, | |
373 | ||
374 | _onBlur: function(){ | |
375 | // summary: | |
376 | // Called when focus is moved away from this Menu and it's submenus. | |
377 | // tags: | |
378 | // protected | |
379 | this._cleanUp(); | |
380 | this.inherited(arguments); | |
381 | }, | |
382 | ||
383 | _cleanUp: function(){ | |
384 | // summary: | |
385 | // Called when the user is done with this menu. Closes hierarchy of menus. | |
386 | // tags: | |
387 | // private | |
388 | ||
389 | this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close | |
390 | if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose | |
391 | this._markInactive(); | |
392 | } | |
393 | } | |
394 | }); | |
395 | ||
396 | dojo.declare("dijit.Menu", | |
397 | dijit._MenuBase, | |
398 | { | |
399 | // summary | |
400 | // A context menu you can assign to multiple elements | |
401 | ||
402 | // TODO: most of the code in here is just for context menu (right-click menu) | |
403 | // support. In retrospect that should have been a separate class (dijit.ContextMenu). | |
404 | // Split them for 2.0 | |
405 | ||
406 | constructor: function(){ | |
407 | this._bindings = []; | |
408 | }, | |
409 | ||
410 | templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"), | |
411 | ||
412 | baseClass: "dijitMenu", | |
413 | ||
414 | // targetNodeIds: [const] String[] | |
415 | // Array of dom node ids of nodes to attach to. | |
416 | // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes. | |
417 | targetNodeIds: [], | |
418 | ||
419 | // contextMenuForWindow: [const] Boolean | |
420 | // If true, right clicking anywhere on the window will cause this context menu to open. | |
421 | // If false, must specify targetNodeIds. | |
422 | contextMenuForWindow: false, | |
423 | ||
424 | // leftClickToOpen: [const] Boolean | |
425 | // If true, menu will open on left click instead of right click, similiar to a file menu. | |
426 | leftClickToOpen: false, | |
427 | ||
428 | // refocus: Boolean | |
429 | // When this menu closes, re-focus the element which had focus before it was opened. | |
430 | refocus: true, | |
431 | ||
432 | postCreate: function(){ | |
433 | if(this.contextMenuForWindow){ | |
434 | this.bindDomNode(dojo.body()); | |
435 | }else{ | |
436 | // TODO: should have _setTargetNodeIds() method to handle initialization and a possible | |
437 | // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[] | |
438 | // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610) | |
439 | dojo.forEach(this.targetNodeIds, this.bindDomNode, this); | |
440 | } | |
441 | var k = dojo.keys, l = this.isLeftToRight(); | |
442 | this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW; | |
443 | this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW; | |
444 | this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]); | |
445 | }, | |
446 | ||
447 | _onKeyPress: function(/*Event*/ evt){ | |
448 | // summary: | |
449 | // Handle keyboard based menu navigation. | |
450 | // tags: | |
451 | // protected | |
452 | ||
453 | if(evt.ctrlKey || evt.altKey){ return; } | |
454 | ||
455 | switch(evt.charOrCode){ | |
456 | case this._openSubMenuKey: | |
457 | this._moveToPopup(evt); | |
458 | dojo.stopEvent(evt); | |
459 | break; | |
460 | case this._closeSubMenuKey: | |
461 | if(this.parentMenu){ | |
462 | if(this.parentMenu._isMenuBar){ | |
463 | this.parentMenu.focusPrev(); | |
464 | }else{ | |
465 | this.onCancel(false); | |
466 | } | |
467 | }else{ | |
468 | dojo.stopEvent(evt); | |
469 | } | |
470 | break; | |
471 | } | |
472 | }, | |
473 | ||
474 | // thanks burstlib! | |
475 | _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){ | |
476 | // summary: | |
477 | // Returns the window reference of the passed iframe | |
478 | // tags: | |
479 | // private | |
480 | var win = dojo.window.get(this._iframeContentDocument(iframe_el)) || | |
481 | // Moz. TODO: is this available when defaultView isn't? | |
482 | this._iframeContentDocument(iframe_el)['__parent__'] || | |
483 | (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null; | |
484 | return win; // Window | |
485 | }, | |
486 | ||
487 | _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){ | |
488 | // summary: | |
489 | // Returns a reference to the document object inside iframe_el | |
490 | // tags: | |
491 | // protected | |
492 | var doc = iframe_el.contentDocument // W3 | |
493 | || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE | |
494 | || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document) | |
495 | || null; | |
496 | return doc; // HTMLDocument | |
497 | }, | |
498 | ||
499 | bindDomNode: function(/*String|DomNode*/ node){ | |
500 | // summary: | |
501 | // Attach menu to given node | |
502 | node = dojo.byId(node); | |
503 | ||
504 | var cn; // Connect node | |
505 | ||
506 | // Support context menus on iframes. Rather than binding to the iframe itself we need | |
507 | // to bind to the <body> node inside the iframe. | |
508 | if(node.tagName.toLowerCase() == "iframe"){ | |
509 | var iframe = node, | |
510 | win = this._iframeContentWindow(iframe); | |
511 | cn = dojo.withGlobal(win, dojo.body); | |
512 | }else{ | |
513 | ||
514 | // To capture these events at the top level, attach to <html>, not <body>. | |
515 | // Otherwise right-click context menu just doesn't work. | |
516 | cn = (node == dojo.body() ? dojo.doc.documentElement : node); | |
517 | } | |
518 | ||
519 | ||
520 | // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode()) | |
521 | var binding = { | |
522 | node: node, | |
523 | iframe: iframe | |
524 | }; | |
525 | ||
526 | // Save info about binding in _bindings[], and make node itself record index(+1) into | |
527 | // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may | |
528 | // start with a number, which fails on FF/safari. | |
529 | dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding)); | |
530 | ||
531 | // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished | |
532 | // loading yet, in which case we need to wait for the onload event first, and then connect | |
533 | // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so | |
534 | // we need to monitor keyboard events in addition to the oncontextmenu event. | |
535 | var doConnects = dojo.hitch(this, function(cn){ | |
536 | return [ | |
537 | // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu, | |
538 | // rather than shift-F10? | |
539 | dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){ | |
540 | // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler | |
541 | dojo.stopEvent(evt); | |
542 | this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY}); | |
543 | }), | |
544 | dojo.connect(cn, "onkeydown", this, function(evt){ | |
545 | if(evt.shiftKey && evt.keyCode == dojo.keys.F10){ | |
546 | dojo.stopEvent(evt); | |
547 | this._scheduleOpen(evt.target, iframe); // no coords - open near target node | |
548 | } | |
549 | }) | |
550 | ]; | |
551 | }); | |
552 | binding.connects = cn ? doConnects(cn) : []; | |
553 | ||
554 | if(iframe){ | |
555 | // Setup handler to [re]bind to the iframe when the contents are initially loaded, | |
556 | // and every time the contents change. | |
557 | // Need to do this b/c we are actually binding to the iframe's <body> node. | |
558 | // Note: can't use dojo.connect(), see #9609. | |
559 | ||
560 | binding.onloadHandler = dojo.hitch(this, function(){ | |
561 | // want to remove old connections, but IE throws exceptions when trying to | |
562 | // access the <body> node because it's already gone, or at least in a state of limbo | |
563 | ||
564 | var win = this._iframeContentWindow(iframe); | |
565 | cn = dojo.withGlobal(win, dojo.body); | |
566 | binding.connects = doConnects(cn); | |
567 | }); | |
568 | if(iframe.addEventListener){ | |
569 | iframe.addEventListener("load", binding.onloadHandler, false); | |
570 | }else{ | |
571 | iframe.attachEvent("onload", binding.onloadHandler); | |
572 | } | |
573 | } | |
574 | }, | |
575 | ||
576 | unBindDomNode: function(/*String|DomNode*/ nodeName){ | |
577 | // summary: | |
578 | // Detach menu from given node | |
579 | ||
580 | var node; | |
581 | try{ | |
582 | node = dojo.byId(nodeName); | |
583 | }catch(e){ | |
584 | // On IE the dojo.byId() call will get an exception if the attach point was | |
585 | // the <body> node of an <iframe> that has since been reloaded (and thus the | |
586 | // <body> node is in a limbo state of destruction. | |
587 | return; | |
588 | } | |
589 | ||
590 | // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array | |
591 | var attrName = "_dijitMenu" + this.id; | |
592 | if(node && dojo.hasAttr(node, attrName)){ | |
593 | var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid]; | |
594 | dojo.forEach(b.connects, dojo.disconnect); | |
595 | ||
596 | // Remove listener for iframe onload events | |
597 | var iframe = b.iframe; | |
598 | if(iframe){ | |
599 | if(iframe.removeEventListener){ | |
600 | iframe.removeEventListener("load", b.onloadHandler, false); | |
601 | }else{ | |
602 | iframe.detachEvent("onload", b.onloadHandler); | |
603 | } | |
604 | } | |
605 | ||
606 | dojo.removeAttr(node, attrName); | |
607 | delete this._bindings[bid]; | |
608 | } | |
609 | }, | |
610 | ||
611 | _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){ | |
612 | // summary: | |
613 | // Set timer to display myself. Using a timer rather than displaying immediately solves | |
614 | // two problems: | |
615 | // | |
616 | // 1. IE: without the delay, focus work in "open" causes the system | |
617 | // context menu to appear in spite of stopEvent. | |
618 | // | |
619 | // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event | |
620 | // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the | |
621 | // oncontextmenu event.) | |
622 | ||
623 | if(!this._openTimer){ | |
624 | this._openTimer = setTimeout(dojo.hitch(this, function(){ | |
625 | delete this._openTimer; | |
626 | this._openMyself({ | |
627 | target: target, | |
628 | iframe: iframe, | |
629 | coords: coords | |
630 | }); | |
631 | }), 1); | |
632 | } | |
633 | }, | |
634 | ||
635 | _openMyself: function(args){ | |
636 | // summary: | |
637 | // Internal function for opening myself when the user does a right-click or something similar. | |
638 | // args: | |
639 | // This is an Object containing: | |
640 | // * target: | |
641 | // The node that is being clicked | |
642 | // * iframe: | |
643 | // If an <iframe> is being clicked, iframe points to that iframe | |
644 | // * coords: | |
645 | // Put menu at specified x/y position in viewport, or if iframe is | |
646 | // specified, then relative to iframe. | |
647 | // | |
648 | // _openMyself() formerly took the event object, and since various code references | |
649 | // evt.target (after connecting to _openMyself()), using an Object for parameters | |
650 | // (so that old code still works). | |
651 | ||
652 | var target = args.target, | |
653 | iframe = args.iframe, | |
654 | coords = args.coords; | |
655 | ||
656 | // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard) | |
657 | // then near the node the menu is assigned to. | |
658 | if(coords){ | |
659 | if(iframe){ | |
660 | // Specified coordinates are on <body> node of an <iframe>, convert to match main document | |
661 | var od = target.ownerDocument, | |
662 | ifc = dojo.position(iframe, true), | |
663 | win = this._iframeContentWindow(iframe), | |
664 | scroll = dojo.withGlobal(win, "_docScroll", dojo); | |
665 | ||
666 | var cs = dojo.getComputedStyle(iframe), | |
667 | tp = dojo._toPixelValue, | |
668 | left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0), | |
669 | top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0); | |
670 | ||
671 | coords.x += ifc.x + left - scroll.x; | |
672 | coords.y += ifc.y + top - scroll.y; | |
673 | } | |
674 | }else{ | |
675 | coords = dojo.position(target, true); | |
676 | coords.x += 10; | |
677 | coords.y += 10; | |
678 | } | |
679 | ||
680 | var self=this; | |
681 | var savedFocus = dijit.getFocus(this); | |
682 | function closeAndRestoreFocus(){ | |
683 | // user has clicked on a menu or popup | |
684 | if(self.refocus){ | |
685 | dijit.focus(savedFocus); | |
686 | } | |
687 | dijit.popup.close(self); | |
688 | } | |
689 | dijit.popup.open({ | |
690 | popup: this, | |
691 | x: coords.x, | |
692 | y: coords.y, | |
693 | onExecute: closeAndRestoreFocus, | |
694 | onCancel: closeAndRestoreFocus, | |
695 | orient: this.isLeftToRight() ? 'L' : 'R' | |
696 | }); | |
697 | this.focus(); | |
698 | ||
699 | this._onBlur = function(){ | |
700 | this.inherited('_onBlur', arguments); | |
701 | // Usually the parent closes the child widget but if this is a context | |
702 | // menu then there is no parent | |
703 | dijit.popup.close(this); | |
704 | // don't try to restore focus; user has clicked another part of the screen | |
705 | // and set focus there | |
706 | }; | |
707 | }, | |
708 | ||
709 | uninitialize: function(){ | |
710 | dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this); | |
711 | this.inherited(arguments); | |
712 | } | |
713 | } | |
714 | ); | |
715 | ||
2f01fe57 | 716 | } |