Menu.js 23 KB

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