Menu.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. if (!dojo._hasResource["dijit.Menu"]) { // _hasResource checks added by build.
  2. // Do not use _hasResource directly in
  3. // your code.
  4. dojo._hasResource["dijit.Menu"] = true;
  5. dojo.provide("dijit.Menu");
  6. dojo.require("dijit._Widget");
  7. dojo.require("dijit._Container");
  8. dojo.require("dijit._Templated");
  9. dojo.declare("dijit.Menu", [dijit._Widget, dijit._Templated,
  10. dijit._KeyNavContainer], {
  11. constructor : function() {
  12. this._bindings = [];
  13. },
  14. templateString : '<table class="dijit dijitMenu dijitReset dijitMenuTable" waiRole="menu" dojoAttachEvent="onkeypress:_onKeyPress">'
  15. + '<tbody class="dijitReset" dojoAttachPoint="containerNode"></tbody>'
  16. + '</table>',
  17. // targetNodeIds: String[]
  18. // Array of dom node ids of nodes to attach to.
  19. // Fill this with nodeIds upon widget creation and it becomes
  20. // context menu for those nodes.
  21. targetNodeIds : [],
  22. // contextMenuForWindow: Boolean
  23. // if true, right clicking anywhere on the window will cause
  24. // this context menu to open;
  25. // if false, must specify targetNodeIds
  26. contextMenuForWindow : false,
  27. // parentMenu: Widget
  28. // pointer to menu that displayed me
  29. parentMenu : null,
  30. // popupDelay: Integer
  31. // number of milliseconds before hovering (without clicking)
  32. // causes the popup to automatically open
  33. popupDelay : 500,
  34. // _contextMenuWithMouse: Boolean
  35. // used to record mouse and keyboard events to determine if a
  36. // context
  37. // menu is being opened with the keyboard or the mouse
  38. _contextMenuWithMouse : false,
  39. postCreate : function() {
  40. if (this.contextMenuForWindow) {
  41. this.bindDomNode(dojo.body());
  42. } else {
  43. dojo
  44. .forEach(this.targetNodeIds, this.bindDomNode,
  45. this);
  46. }
  47. this.connectKeyNavHandlers([dojo.keys.UP_ARROW],
  48. [dojo.keys.DOWN_ARROW]);
  49. },
  50. startup : function() {
  51. dojo.forEach(this.getChildren(), function(child) {
  52. child.startup();
  53. });
  54. this.startupKeyNavChildren();
  55. },
  56. onExecute : function() {
  57. // summary: attach point for notification about when a menu
  58. // item has been executed
  59. },
  60. onCancel : function(/* Boolean */closeAll) {
  61. // summary: attach point for notification about when the
  62. // user cancels the current menu
  63. },
  64. _moveToPopup : function(/* Event */evt) {
  65. if (this.focusedChild && this.focusedChild.popup
  66. && !this.focusedChild.disabled) {
  67. this.focusedChild._onClick(evt);
  68. }
  69. },
  70. _onKeyPress : function(/* Event */evt) {
  71. // summary
  72. // Handle keyboard based menu navigation.
  73. if (evt.ctrlKey || evt.altKey) {
  74. return;
  75. }
  76. switch (evt.keyCode) {
  77. case dojo.keys.RIGHT_ARROW :
  78. this._moveToPopup(evt);
  79. dojo.stopEvent(evt);
  80. break;
  81. case dojo.keys.LEFT_ARROW :
  82. if (this.parentMenu) {
  83. this.onCancel(false);
  84. } else {
  85. dojo.stopEvent(evt);
  86. }
  87. break;
  88. }
  89. },
  90. onItemHover : function(/* MenuItem */item) {
  91. this.focusChild(item);
  92. if (this.focusedChild.popup && !this.focusedChild.disabled
  93. && !this.hover_timer) {
  94. this.hover_timer = setTimeout(dojo.hitch(this,
  95. "_openPopup"), this.popupDelay);
  96. }
  97. },
  98. _onChildBlur : function(item) {
  99. // Close all popups that are open and descendants of this
  100. // menu
  101. dijit.popup.close(item.popup);
  102. item._blur();
  103. this._stopPopupTimer();
  104. },
  105. onItemUnhover : function(/* MenuItem */item) {
  106. },
  107. _stopPopupTimer : function() {
  108. if (this.hover_timer) {
  109. clearTimeout(this.hover_timer);
  110. this.hover_timer = null;
  111. }
  112. },
  113. _getTopMenu : function() {
  114. for (var top = this; top.parentMenu; top = top.parentMenu);
  115. return top;
  116. },
  117. onItemClick : function(/* Widget */item) {
  118. // summary: user defined function to handle clicks on an
  119. // item
  120. // summary: internal function for clicks
  121. if (item.disabled) {
  122. return false;
  123. }
  124. if (item.popup) {
  125. if (!this.is_open) {
  126. this._openPopup();
  127. }
  128. } else {
  129. // before calling user defined handler, close hierarchy
  130. // of menus
  131. // and restore focus to place it was when menu was
  132. // opened
  133. this.onExecute();
  134. // user defined handler for click
  135. item.onClick();
  136. }
  137. },
  138. // thanks burstlib!
  139. _iframeContentWindow : function(
  140. /* HTMLIFrameElement */iframe_el) {
  141. // summary
  142. // returns the window reference of the passed iframe
  143. var win = dijit.getDocumentWindow(dijit.Menu
  144. ._iframeContentDocument(iframe_el))
  145. ||
  146. // Moz. TODO: is this available when defaultView
  147. // isn't?
  148. dijit.Menu._iframeContentDocument(iframe_el)['__parent__']
  149. || (iframe_el.name && document.frames[iframe_el.name])
  150. || null;
  151. return win; // Window
  152. },
  153. _iframeContentDocument : function(
  154. /* HTMLIFrameElement */iframe_el) {
  155. // summary
  156. // returns a reference to the document object inside
  157. // iframe_el
  158. var doc = iframe_el.contentDocument // W3
  159. || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
  160. || (iframe_el.name
  161. && document.frames[iframe_el.name] && document.frames[iframe_el.name].document)
  162. || null;
  163. return doc; // HTMLDocument
  164. },
  165. bindDomNode : function(/* String|DomNode */node) {
  166. // summary: attach menu to given node
  167. node = dojo.byId(node);
  168. // TODO: this is to support context popups in Editor. Maybe
  169. // this shouldn't be in dijit.Menu
  170. var win = dijit.getDocumentWindow(node.ownerDocument);
  171. if (node.tagName.toLowerCase() == "iframe") {
  172. win = this._iframeContentWindow(node);
  173. node = dojo.withGlobal(win, dojo.body);
  174. }
  175. // to capture these events at the top level,
  176. // attach to document, not body
  177. var cn = (node == dojo.body() ? dojo.doc : node);
  178. node[this.id] = this._bindings.push([
  179. dojo.connect(cn, "oncontextmenu", this,
  180. "_openMyself"),
  181. dojo.connect(cn, "onkeydown", this, "_contextKey"),
  182. dojo.connect(cn, "onmousedown", this,
  183. "_contextMouse")]);
  184. },
  185. unBindDomNode : function(/* String|DomNode */nodeName) {
  186. // summary: detach menu from given node
  187. var node = dojo.byId(nodeName);
  188. var bid = node[this.id] - 1, b = this._bindings[bid];
  189. dojo.forEach(b, dojo.disconnect);
  190. delete this._bindings[bid];
  191. },
  192. _contextKey : function(e) {
  193. this._contextMenuWithMouse = false;
  194. if (e.keyCode == dojo.keys.F10) {
  195. dojo.stopEvent(e);
  196. if (e.shiftKey && e.type == "keydown") {
  197. // FF: copying the wrong property from e will cause
  198. // the system
  199. // context menu to appear in spite of stopEvent.
  200. // Don't know
  201. // exactly which properties cause this effect.
  202. var _e = {
  203. target : e.target,
  204. pageX : e.pageX,
  205. pageY : e.pageY
  206. };
  207. _e.preventDefault = _e.stopPropagation = function() {
  208. };
  209. // IE: without the delay, focus work in "open"
  210. // causes the system
  211. // context menu to appear in spite of stopEvent.
  212. window.setTimeout(dojo.hitch(this, function() {
  213. this._openMyself(_e);
  214. }), 1);
  215. }
  216. }
  217. },
  218. _contextMouse : function(e) {
  219. this._contextMenuWithMouse = true;
  220. },
  221. _openMyself : function(/* Event */e) {
  222. // summary:
  223. // Internal function for opening myself when the user
  224. // does a right-click or something similar
  225. dojo.stopEvent(e);
  226. // Get coordinates.
  227. // if we are opening the menu with the mouse or on safari
  228. // open
  229. // the menu at the mouse cursor
  230. // (Safari does not have a keyboard command to open the
  231. // context menu
  232. // and we don't currently have a reliable way to determine
  233. // _contextMenuWithMouse on Safari)
  234. var x, y;
  235. if (dojo.isSafari || this._contextMenuWithMouse) {
  236. x = e.pageX;
  237. y = e.pageY;
  238. } else {
  239. // otherwise open near e.target
  240. var coords = dojo.coords(e.target, true);
  241. x = coords.x + 10;
  242. y = coords.y + 10;
  243. }
  244. var self = this;
  245. var savedFocus = dijit.getFocus(this);
  246. function closeAndRestoreFocus() {
  247. // user has clicked on a menu or popup
  248. dijit.focus(savedFocus);
  249. dijit.popup.close(self);
  250. }
  251. dijit.popup.open({
  252. popup : this,
  253. x : x,
  254. y : y,
  255. onExecute : closeAndRestoreFocus,
  256. onCancel : closeAndRestoreFocus,
  257. orient : this.isLeftToRight() ? 'L' : 'R'
  258. });
  259. this.focus();
  260. this._onBlur = function() {
  261. // Usually the parent closes the child widget but if
  262. // this is a context
  263. // menu then there is no parent
  264. dijit.popup.close(this);
  265. // don't try to restore focus; user has clicked another
  266. // part of the screen
  267. // and set focus there
  268. }
  269. },
  270. onOpen : function(/* Event */e) {
  271. // summary
  272. // Open menu relative to the mouse
  273. this.isShowingNow = true;
  274. },
  275. onClose : function() {
  276. // summary: callback when this menu is closed
  277. this._stopPopupTimer();
  278. this.parentMenu = null;
  279. this.isShowingNow = false;
  280. this.currentPopup = null;
  281. if (this.focusedChild) {
  282. this._onChildBlur(this.focusedChild);
  283. this.focusedChild = null;
  284. }
  285. },
  286. _openPopup : function() {
  287. // summary: open the popup to the side of the current menu
  288. // item
  289. this._stopPopupTimer();
  290. var from_item = this.focusedChild;
  291. var popup = from_item.popup;
  292. if (popup.isShowingNow) {
  293. return;
  294. }
  295. popup.parentMenu = this;
  296. var self = this;
  297. dijit.popup.open({
  298. parent : this,
  299. popup : popup,
  300. around : from_item.arrowCell,
  301. orient : this.isLeftToRight() ? {
  302. 'TR' : 'TL',
  303. 'TL' : 'TR'
  304. } : {
  305. 'TL' : 'TR',
  306. 'TR' : 'TL'
  307. },
  308. onCancel : function() {
  309. // called when the child menu is canceled
  310. dijit.popup.close(popup);
  311. from_item.focus(); // put focus back on my
  312. // node
  313. self.currentPopup = null;
  314. }
  315. });
  316. this.currentPopup = popup;
  317. if (popup.focus) {
  318. popup.focus();
  319. }
  320. }
  321. });
  322. dojo.declare("dijit.MenuItem", [dijit._Widget, dijit._Templated,
  323. dijit._Contained], {
  324. // summary
  325. // A line item in a Menu2
  326. // Make 3 columns
  327. // icon, label, and expand arrow (BiDi-dependent) indicating
  328. // sub-menu
  329. templateString : '<tr class="dijitReset dijitMenuItem"'
  330. + 'dojoAttachEvent="onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick">'
  331. + '<td class="dijitReset"><div class="dijitMenuItemIcon ${iconClass}" dojoAttachPoint="iconNode" ></div></td>'
  332. + '<td tabIndex="-1" class="dijitReset dijitMenuItemLabel" dojoAttachPoint="containerNode" waiRole="menuitem"></td>'
  333. + '<td class="dijitReset" dojoAttachPoint="arrowCell">'
  334. + '<div class="dijitMenuExpand" dojoAttachPoint="expand" style="display:none">'
  335. + '<span class="dijitInline dijitArrowNode dijitMenuExpandInner">+</span>'
  336. + '</div>' + '</td>' + '</tr>',
  337. // label: String
  338. // menu text
  339. label : '',
  340. // iconClass: String
  341. // class to apply to div in button to make it display an icon
  342. iconClass : "",
  343. // disabled: Boolean
  344. // if true, the menu item is disabled
  345. // if false, the menu item is enabled
  346. disabled : false,
  347. postCreate : function() {
  348. dojo.setSelectable(this.domNode, false);
  349. this.setDisabled(this.disabled);
  350. if (this.label) {
  351. this.containerNode.innerHTML = this.label;
  352. }
  353. },
  354. _onHover : function() {
  355. // summary: callback when mouse is moved onto menu item
  356. this.getParent().onItemHover(this);
  357. },
  358. _onUnhover : function() {
  359. // summary: callback when mouse is moved off of menu item
  360. // if we are unhovering the currently selected item
  361. // then unselect it
  362. this.getParent().onItemUnhover(this);
  363. },
  364. _onClick : function(evt) {
  365. this.getParent().onItemClick(this);
  366. dojo.stopEvent(evt);
  367. },
  368. onClick : function() {
  369. // summary
  370. // User defined function to handle clicks
  371. },
  372. focus : function() {
  373. dojo.addClass(this.domNode, 'dijitMenuItemHover');
  374. try {
  375. dijit.focus(this.containerNode);
  376. } catch (e) {
  377. // this throws on IE (at least) in some scenarios
  378. }
  379. },
  380. _blur : function() {
  381. dojo.removeClass(this.domNode, 'dijitMenuItemHover');
  382. },
  383. setDisabled : function(/* Boolean */value) {
  384. // summary: enable or disable this menu item
  385. this.disabled = value;
  386. dojo[value ? "addClass" : "removeClass"](this.domNode,
  387. 'dijitMenuItemDisabled');
  388. dijit.setWaiState(this.containerNode, 'disabled', value
  389. ? 'true'
  390. : 'false');
  391. }
  392. });
  393. dojo.declare("dijit.PopupMenuItem", dijit.MenuItem, {
  394. _fillContent : function() {
  395. // my inner HTML contains both the menu item text and a
  396. // popup widget, like
  397. // <div dojoType="dijit.PopupMenuItem">
  398. // <span>pick me</span>
  399. // <popup> ... </popup>
  400. // </div>
  401. // the first part holds the menu item text and the second
  402. // part is the popup
  403. if (this.srcNodeRef) {
  404. var nodes = dojo.query("*", this.srcNodeRef);
  405. dijit.PopupMenuItem.superclass._fillContent.call(this,
  406. nodes[0]);
  407. // save pointer to srcNode so we can grab the drop down
  408. // widget after it's instantiated
  409. this.dropDownContainer = this.srcNodeRef;
  410. }
  411. },
  412. startup : function() {
  413. // we didn't copy the dropdown widget from the
  414. // this.srcNodeRef, so it's in no-man's
  415. // land now. move it to document.body.
  416. if (!this.popup) {
  417. var node = dojo.query("[widgetId]",
  418. this.dropDownContainer)[0];
  419. this.popup = dijit.byNode(node);
  420. }
  421. dojo.body().appendChild(this.popup.domNode);
  422. this.popup.domNode.style.display = "none";
  423. dojo.addClass(this.expand, "dijitMenuExpandEnabled");
  424. dojo.style(this.expand, "display", "");
  425. dijit.setWaiState(this.containerNode, "haspopup", "true");
  426. }
  427. });
  428. dojo.declare("dijit.MenuSeparator", [dijit._Widget, dijit._Templated,
  429. dijit._Contained], {
  430. // summary
  431. // A line between two menu items
  432. templateString : '<tr class="dijitMenuSeparator"><td colspan=3>'
  433. + '<div class="dijitMenuSeparatorTop"></div>'
  434. + '<div class="dijitMenuSeparatorBottom"></div>'
  435. + '</td></tr>',
  436. postCreate : function() {
  437. dojo.setSelectable(this.domNode, false);
  438. },
  439. isFocusable : function() {
  440. // summary:
  441. // over ride to always return false
  442. return false;
  443. }
  444. });
  445. }