popupmenu.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. // Copyright 2007 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview A menu class for showing popups. A single popup can be
  16. * attached to multiple anchor points. The menu will try to reposition itself
  17. * if it goes outside the viewport.
  18. *
  19. * Decoration is the same as goog.ui.Menu except that the outer DIV can have a
  20. * 'for' property, which is the ID of the element which triggers the popup.
  21. *
  22. * Decorate Example:
  23. * <button id="dButton">Decorated Popup</button>
  24. * <div id="dMenu" for="dButton" class="goog-menu">
  25. * <div class="goog-menuitem">A a</div>
  26. * <div class="goog-menuitem">B b</div>
  27. * <div class="goog-menuitem">C c</div>
  28. * <div class="goog-menuitem">D d</div>
  29. * <div class="goog-menuitem">E e</div>
  30. * <div class="goog-menuitem">F f</div>
  31. * </div>
  32. *
  33. * TESTED=FireFox 2.0, IE6, Opera 9, Chrome.
  34. * TODO(user): Key handling is flakey in Opera and Chrome
  35. *
  36. * @see ../demos/popupmenu.html
  37. */
  38. goog.provide('goog.ui.PopupMenu');
  39. goog.require('goog.events');
  40. goog.require('goog.events.BrowserEvent');
  41. goog.require('goog.events.EventType');
  42. goog.require('goog.events.KeyCodes');
  43. goog.require('goog.positioning.AnchoredViewportPosition');
  44. goog.require('goog.positioning.Corner');
  45. goog.require('goog.positioning.MenuAnchoredPosition');
  46. goog.require('goog.positioning.Overflow');
  47. goog.require('goog.positioning.ViewportClientPosition');
  48. goog.require('goog.structs.Map');
  49. goog.require('goog.style');
  50. goog.require('goog.ui.Component');
  51. goog.require('goog.ui.Menu');
  52. goog.require('goog.ui.PopupBase');
  53. /**
  54. * A basic menu class.
  55. * @param {?goog.dom.DomHelper=} opt_domHelper Optional DOM helper.
  56. * @param {?goog.ui.MenuRenderer=} opt_renderer Renderer used to render or
  57. * decorate the container; defaults to {@link goog.ui.MenuRenderer}.
  58. * @extends {goog.ui.Menu}
  59. * @constructor
  60. */
  61. goog.ui.PopupMenu = function(opt_domHelper, opt_renderer) {
  62. goog.ui.Menu.call(this, opt_domHelper, opt_renderer);
  63. this.setAllowAutoFocus(true);
  64. // Popup menus are hidden by default.
  65. this.setVisible(false, true);
  66. /**
  67. * Map of attachment points for the menu. Key -> Object
  68. * @type {!goog.structs.Map}
  69. * @private
  70. */
  71. this.targets_ = new goog.structs.Map();
  72. };
  73. goog.inherits(goog.ui.PopupMenu, goog.ui.Menu);
  74. goog.tagUnsealableClass(goog.ui.PopupMenu);
  75. /**
  76. * If true, then if the menu will toggle off if it is already visible.
  77. * @type {boolean}
  78. * @private
  79. */
  80. goog.ui.PopupMenu.prototype.toggleMode_ = false;
  81. /**
  82. * Time that the menu was last shown.
  83. * @type {number}
  84. * @private
  85. */
  86. goog.ui.PopupMenu.prototype.lastHide_ = 0;
  87. /**
  88. * Current element where the popup menu is anchored.
  89. * @type {?Element}
  90. * @private
  91. */
  92. goog.ui.PopupMenu.prototype.currentAnchor_ = null;
  93. /**
  94. * Decorate an existing HTML structure with the menu. Menu items will be
  95. * constructed from elements with classname 'goog-menuitem', separators will be
  96. * made from HR elements.
  97. * @param {?Element} element Element to decorate.
  98. * @override
  99. */
  100. goog.ui.PopupMenu.prototype.decorateInternal = function(element) {
  101. goog.ui.PopupMenu.superClass_.decorateInternal.call(this, element);
  102. // 'for' is a custom attribute for attaching the menu to a click target
  103. var htmlFor = element.getAttribute('for') || element.htmlFor;
  104. if (htmlFor) {
  105. this.attach(
  106. this.getDomHelper().getElement(htmlFor),
  107. goog.positioning.Corner.BOTTOM_LEFT);
  108. }
  109. };
  110. /** @override */
  111. goog.ui.PopupMenu.prototype.enterDocument = function() {
  112. goog.ui.PopupMenu.superClass_.enterDocument.call(this);
  113. this.targets_.forEach(this.attachEvent_, this);
  114. var handler = this.getHandler();
  115. handler.listen(this, goog.ui.Component.EventType.ACTION, this.onAction_);
  116. handler.listen(
  117. this.getDomHelper().getDocument(), goog.events.EventType.MOUSEDOWN,
  118. this.onDocClick, true);
  119. };
  120. /**
  121. * Attaches the menu to a new popup position and anchor element. A menu can
  122. * only be attached to an element once, since attaching the same menu for
  123. * multiple positions doesn't make sense.
  124. *
  125. * @param {?Element} element Element whose click event should trigger the menu.
  126. * @param {?goog.positioning.Corner=} opt_targetCorner Corner of the target that
  127. * the menu should be anchored to.
  128. * @param {goog.positioning.Corner=} opt_menuCorner Corner of the menu that
  129. * should be anchored.
  130. * @param {boolean=} opt_contextMenu Whether the menu should show on
  131. * {@link goog.events.EventType.CONTEXTMENU} events, false if it should
  132. * show on {@link goog.events.EventType.MOUSEDOWN} events. Default is
  133. * MOUSEDOWN.
  134. * @param {?goog.math.Box=} opt_margin Margin for the popup used in positioning
  135. * algorithms.
  136. */
  137. goog.ui.PopupMenu.prototype.attach = function(
  138. element, opt_targetCorner, opt_menuCorner, opt_contextMenu, opt_margin) {
  139. if (this.isAttachTarget(element)) {
  140. // Already in the popup, so just return.
  141. return;
  142. }
  143. var target = this.createAttachTarget(
  144. element, opt_targetCorner, opt_menuCorner, opt_contextMenu, opt_margin);
  145. if (this.isInDocument()) {
  146. this.attachEvent_(target);
  147. }
  148. // Add a listener for keyboard actions on the menu.
  149. var handler = goog.partial(this.onMenuKeyboardAction_, element);
  150. if (this.getElement()) {
  151. this.getHandler().listen(
  152. this.getElement(), goog.events.EventType.KEYDOWN, handler);
  153. }
  154. };
  155. /**
  156. * Handles keyboard actions on the PopupMenu, according to
  157. * http://www.w3.org/WAI/PF/aria-practices/#menubutton.
  158. *
  159. * <p>If the ESC key is pressed, the menu is hidden (which is handled by
  160. * this.onAction_), and the focus is returned to the element whose click event
  161. * triggered opening of the menu.
  162. *
  163. * <p>If the SPACE or ENTER keys are pressed, the highlighted menu item's
  164. * listeners are fired.
  165. *
  166. * @param {Element} element Element whose click event triggered the menu.
  167. * @param {!goog.events.BrowserEvent} e The key down event.
  168. * @private
  169. */
  170. goog.ui.PopupMenu.prototype.onMenuKeyboardAction_ = function(element, e) {
  171. if (e.keyCode == goog.events.KeyCodes.ESC) {
  172. element.focus();
  173. return;
  174. }
  175. var highlightedItem = this.getChildAt(this.getHighlightedIndex());
  176. if (!highlightedItem) {
  177. return;
  178. }
  179. var targetElement = highlightedItem.getElement();
  180. // Create an event to pass to the menu item's listener.
  181. var event = new goog.events.BrowserEvent(e.getBrowserEvent(), targetElement);
  182. event.target = targetElement;
  183. // If an item is highlighted and the user presses the SPACE/ENTER key, the
  184. // event target is the menu rather than the menu item, so we manually fire
  185. // the listener of the correct menu item.
  186. if (e.keyCode == goog.events.KeyCodes.SPACE ||
  187. e.keyCode == goog.events.KeyCodes.ENTER) {
  188. goog.events.fireListeners(
  189. targetElement, goog.events.EventType.KEYDOWN, false, event);
  190. }
  191. // After activating a menu item the PopupMenu should be hidden (already
  192. // implemented in this.onAction_ for ENTER/MOUSEDOWN).
  193. if (e.keyCode == goog.events.KeyCodes.SPACE) {
  194. this.hide();
  195. }
  196. };
  197. /**
  198. * Creates an object describing how the popup menu should be attached to the
  199. * anchoring element based on the given parameters. The created object is
  200. * stored, keyed by {@code element} and is retrievable later by invoking
  201. * {@link #getAttachTarget(element)} at a later point.
  202. *
  203. * Subclass may add more properties to the returned object, as needed.
  204. *
  205. * @param {?Element} element Element whose click event should trigger the menu.
  206. * @param {?goog.positioning.Corner=} opt_targetCorner Corner of the target that
  207. * the menu should be anchored to.
  208. * @param {?goog.positioning.Corner=} opt_menuCorner Corner of the menu that
  209. * should be anchored.
  210. * @param {boolean=} opt_contextMenu Whether the menu should show on
  211. * {@link goog.events.EventType.CONTEXTMENU} events, false if it should
  212. * show on {@link goog.events.EventType.MOUSEDOWN} events. Default is
  213. * MOUSEDOWN.
  214. * @param {?goog.math.Box=} opt_margin Margin for the popup used in positioning
  215. * algorithms.
  216. *
  217. * @return {?Object} An object that describes how the popup menu should be
  218. * attached to the anchoring element.
  219. *
  220. * @protected
  221. */
  222. goog.ui.PopupMenu.prototype.createAttachTarget = function(
  223. element, opt_targetCorner, opt_menuCorner, opt_contextMenu, opt_margin) {
  224. if (!element) {
  225. return null;
  226. }
  227. var target = {
  228. element_: element,
  229. targetCorner_: opt_targetCorner,
  230. menuCorner_: opt_menuCorner,
  231. eventType_: opt_contextMenu ? goog.events.EventType.CONTEXTMENU :
  232. goog.events.EventType.MOUSEDOWN,
  233. margin_: opt_margin
  234. };
  235. this.targets_.set(goog.getUid(element), target);
  236. return target;
  237. };
  238. /**
  239. * Returns the object describing how the popup menu should be attach to given
  240. * element or {@code null}. The object is created and the association is formed
  241. * when {@link #attach} is invoked.
  242. *
  243. * @param {?Element} element DOM element.
  244. * @return {?Object} The object created when {@link attach} is invoked on
  245. * {@code element}. Returns {@code null} if the element does not trigger
  246. * the menu (i.e. {@link attach} has never been invoked on
  247. * {@code element}).
  248. * @protected
  249. */
  250. goog.ui.PopupMenu.prototype.getAttachTarget = function(element) {
  251. return element ?
  252. /** @type {?Object} */ (this.targets_.get(goog.getUid(element))) :
  253. null;
  254. };
  255. /**
  256. * @param {?Element} element Any DOM element.
  257. * @return {boolean} Whether clicking on the given element will trigger the
  258. * menu.
  259. *
  260. * @protected
  261. */
  262. goog.ui.PopupMenu.prototype.isAttachTarget = function(element) {
  263. return element ? this.targets_.containsKey(goog.getUid(element)) : false;
  264. };
  265. /**
  266. * @return {?Element} The current element where the popup is anchored, if it's
  267. * visible.
  268. */
  269. goog.ui.PopupMenu.prototype.getAttachedElement = function() {
  270. return this.currentAnchor_;
  271. };
  272. /**
  273. * Attaches two event listeners to a target. One with corresponding event type,
  274. * and one with the KEYDOWN event type for accessibility purposes.
  275. * @param {?Object} target The target to attach an event to.
  276. * @private
  277. */
  278. goog.ui.PopupMenu.prototype.attachEvent_ = function(target) {
  279. this.getHandler().listen(
  280. target.element_, target.eventType_, this.onTargetClick_);
  281. if (target.eventType_ != goog.events.EventType.CONTEXTMENU) {
  282. this.getHandler().listen(
  283. target.element_, goog.events.EventType.KEYDOWN,
  284. this.onTargetKeyboardAction_);
  285. }
  286. };
  287. /**
  288. * Detaches all listeners
  289. */
  290. goog.ui.PopupMenu.prototype.detachAll = function() {
  291. if (this.isInDocument()) {
  292. var keys = this.targets_.getKeys();
  293. for (var i = 0; i < keys.length; i++) {
  294. this.detachEvent_(/** @type {!Object} */ (this.targets_.get(keys[i])));
  295. }
  296. }
  297. this.targets_.clear();
  298. };
  299. /**
  300. * Detaches a menu from a given element.
  301. * @param {?Element} element Element whose click event should trigger the menu.
  302. */
  303. goog.ui.PopupMenu.prototype.detach = function(element) {
  304. if (!this.isAttachTarget(element)) {
  305. throw Error('Menu not attached to provided element, unable to detach.');
  306. }
  307. var key = goog.getUid(element);
  308. if (this.isInDocument()) {
  309. this.detachEvent_(/** @type {!Object} */ (this.targets_.get(key)));
  310. }
  311. this.targets_.remove(key);
  312. };
  313. /**
  314. * Detaches an event listener to a target
  315. * @param {!Object} target The target to detach events from.
  316. * @private
  317. */
  318. goog.ui.PopupMenu.prototype.detachEvent_ = function(target) {
  319. this.getHandler().unlisten(
  320. target.element_, target.eventType_, this.onTargetClick_);
  321. };
  322. /**
  323. * Sets whether the menu should toggle if it is already open. For context
  324. * menus this should be false, for toolbar menus it makes more sense to be true.
  325. * @param {boolean} toggle The new toggle mode.
  326. */
  327. goog.ui.PopupMenu.prototype.setToggleMode = function(toggle) {
  328. this.toggleMode_ = toggle;
  329. };
  330. /**
  331. * Gets whether the menu is in toggle mode
  332. * @return {boolean} toggle.
  333. */
  334. goog.ui.PopupMenu.prototype.getToggleMode = function() {
  335. return this.toggleMode_;
  336. };
  337. /**
  338. * Show the menu using given positioning object.
  339. * @param {?goog.positioning.AbstractPosition} position The positioning
  340. * instance.
  341. * @param {goog.positioning.Corner=} opt_menuCorner The corner of the menu to be
  342. * positioned.
  343. * @param {?goog.math.Box=} opt_margin A margin specified in pixels.
  344. * @param {?Element=} opt_anchor The element which acts as visual anchor for
  345. * this menu.
  346. */
  347. goog.ui.PopupMenu.prototype.showWithPosition = function(
  348. position, opt_menuCorner, opt_margin, opt_anchor) {
  349. var isVisible = this.isVisible();
  350. if (this.isOrWasRecentlyVisible() && this.toggleMode_) {
  351. this.hide();
  352. return;
  353. }
  354. // Set current anchor before dispatching BEFORE_SHOW. This is typically useful
  355. // when we would need to make modifications based on the current anchor to the
  356. // menu just before displaying it.
  357. this.currentAnchor_ = opt_anchor || null;
  358. // Notify event handlers that the menu is about to be shown.
  359. if (!this.dispatchEvent(goog.ui.Component.EventType.BEFORE_SHOW)) {
  360. return;
  361. }
  362. var menuCorner = typeof opt_menuCorner != 'undefined' ?
  363. opt_menuCorner :
  364. goog.positioning.Corner.TOP_START;
  365. // This is a little hacky so that we can position the menu with minimal
  366. // flicker.
  367. if (!isVisible) {
  368. // On IE, setting visibility = 'hidden' on a visible menu
  369. // will cause a blur, forcing the menu to close immediately.
  370. this.getElement().style.visibility = 'hidden';
  371. }
  372. goog.style.setElementShown(this.getElement(), true);
  373. position.reposition(this.getElement(), menuCorner, opt_margin);
  374. if (!isVisible) {
  375. this.getElement().style.visibility = 'visible';
  376. }
  377. this.setHighlightedIndex(-1);
  378. // setVisible dispatches a goog.ui.Component.EventType.SHOW event, which may
  379. // be canceled to prevent the menu from being shown.
  380. this.setVisible(true);
  381. };
  382. /**
  383. * Show the menu at a given attached target.
  384. * @param {!Object} target Popup target.
  385. * @param {number} x The client-X associated with the show event.
  386. * @param {number} y The client-Y associated with the show event.
  387. * @protected
  388. */
  389. goog.ui.PopupMenu.prototype.showMenu = function(target, x, y) {
  390. var position = goog.isDef(target.targetCorner_) ?
  391. new goog.positioning.AnchoredViewportPosition(
  392. target.element_, target.targetCorner_, true) :
  393. new goog.positioning.ViewportClientPosition(x, y);
  394. if (position.setLastResortOverflow) {
  395. // This is a ViewportClientPosition, so we can set the overflow policy.
  396. // Allow the menu to slide from the corner rather than clipping if it is
  397. // completely impossible to fit it otherwise.
  398. position.setLastResortOverflow(
  399. goog.positioning.Overflow.ADJUST_X |
  400. goog.positioning.Overflow.ADJUST_Y);
  401. }
  402. this.showWithPosition(
  403. position, target.menuCorner_, target.margin_, target.element_);
  404. };
  405. /**
  406. * Shows the menu immediately at the given client coordinates.
  407. * @param {number} x The client-X associated with the show event.
  408. * @param {number} y The client-Y associated with the show event.
  409. * @param {goog.positioning.Corner=} opt_menuCorner Corner of the menu that
  410. * should be anchored.
  411. */
  412. goog.ui.PopupMenu.prototype.showAt = function(x, y, opt_menuCorner) {
  413. this.showWithPosition(
  414. new goog.positioning.ViewportClientPosition(x, y), opt_menuCorner);
  415. };
  416. /**
  417. * Shows the menu immediately attached to the given element
  418. * @param {?Element} element The element to show at.
  419. * @param {goog.positioning.Corner} targetCorner The corner of the target to
  420. * anchor to.
  421. * @param {goog.positioning.Corner=} opt_menuCorner Corner of the menu that
  422. * should be anchored.
  423. */
  424. goog.ui.PopupMenu.prototype.showAtElement = function(
  425. element, targetCorner, opt_menuCorner) {
  426. this.showWithPosition(
  427. new goog.positioning.MenuAnchoredPosition(element, targetCorner, true),
  428. opt_menuCorner, null, element);
  429. };
  430. /**
  431. * Hides the menu.
  432. */
  433. goog.ui.PopupMenu.prototype.hide = function() {
  434. if (!this.isVisible()) {
  435. return;
  436. }
  437. // setVisible dispatches a goog.ui.Component.EventType.HIDE event, which may
  438. // be canceled to prevent the menu from being hidden.
  439. this.setVisible(false);
  440. if (!this.isVisible()) {
  441. // HIDE event wasn't canceled; the menu is now hidden.
  442. this.lastHide_ = goog.now();
  443. this.currentAnchor_ = null;
  444. }
  445. };
  446. /**
  447. * Returns whether the menu is currently visible or was visible within about
  448. * 150 ms ago. This stops the menu toggling back on if the toggleMode == false.
  449. * @return {boolean} Whether the popup is currently visible or was visible
  450. * within about 150 ms ago.
  451. */
  452. goog.ui.PopupMenu.prototype.isOrWasRecentlyVisible = function() {
  453. return this.isVisible() || this.wasRecentlyHidden();
  454. };
  455. /**
  456. * Used to stop the menu toggling back on if the toggleMode == false.
  457. * @return {boolean} Whether the menu was recently hidden.
  458. * @protected
  459. */
  460. goog.ui.PopupMenu.prototype.wasRecentlyHidden = function() {
  461. return goog.now() - this.lastHide_ < goog.ui.PopupBase.DEBOUNCE_DELAY_MS;
  462. };
  463. /**
  464. * Dismiss the popup menu when an action fires.
  465. * @param {?goog.events.Event=} opt_e The optional event.
  466. * @private
  467. */
  468. goog.ui.PopupMenu.prototype.onAction_ = function(opt_e) {
  469. this.hide();
  470. };
  471. /**
  472. * Handles a browser click event on one of the popup targets.
  473. * @param {?goog.events.BrowserEvent} e The browser event.
  474. * @private
  475. */
  476. goog.ui.PopupMenu.prototype.onTargetClick_ = function(e) {
  477. this.onTargetActivation_(e);
  478. };
  479. /**
  480. * Handles a KEYDOWN browser event on one of the popup targets.
  481. * @param {!goog.events.BrowserEvent} e The browser event.
  482. * @private
  483. */
  484. goog.ui.PopupMenu.prototype.onTargetKeyboardAction_ = function(e) {
  485. if (e.keyCode == goog.events.KeyCodes.SPACE ||
  486. e.keyCode == goog.events.KeyCodes.ENTER ||
  487. e.keyCode == goog.events.KeyCodes.DOWN) {
  488. this.onTargetActivation_(e);
  489. }
  490. // If the popupmenu is opened using the DOWN key, the focus should be on the
  491. // first menu item.
  492. if (e.keyCode == goog.events.KeyCodes.DOWN) {
  493. this.highlightFirst();
  494. }
  495. };
  496. /**
  497. * Handles a browser event on one of the popup targets.
  498. * @param {?goog.events.BrowserEvent} e The browser event.
  499. * @private
  500. */
  501. goog.ui.PopupMenu.prototype.onTargetActivation_ = function(e) {
  502. var keys = this.targets_.getKeys();
  503. for (var i = 0; i < keys.length; i++) {
  504. var target = /** @type {!Object} */ (this.targets_.get(keys[i]));
  505. if (target.element_ == e.currentTarget) {
  506. this.showMenu(target, (e.clientX), (e.clientY));
  507. e.preventDefault();
  508. e.stopPropagation();
  509. return;
  510. }
  511. }
  512. };
  513. /**
  514. * Handles click events that propagate to the document.
  515. * @param {!goog.events.BrowserEvent} e The browser event.
  516. * @protected
  517. */
  518. goog.ui.PopupMenu.prototype.onDocClick = function(e) {
  519. if (this.isVisible() &&
  520. !this.containsElement(/** @type {!Element} */ (e.target))) {
  521. this.hide();
  522. }
  523. };
  524. /**
  525. * Handles the key event target losing focus.
  526. * @param {?goog.events.BrowserEvent} e The browser event.
  527. * @protected
  528. * @override
  529. */
  530. goog.ui.PopupMenu.prototype.handleBlur = function(e) {
  531. goog.ui.PopupMenu.superClass_.handleBlur.call(this, e);
  532. this.hide();
  533. };
  534. /** @override */
  535. goog.ui.PopupMenu.prototype.disposeInternal = function() {
  536. // Always call the superclass' disposeInternal() first (Bug 715885).
  537. goog.ui.PopupMenu.superClass_.disposeInternal.call(this);
  538. // Disposes of the attachment target map.
  539. if (this.targets_) {
  540. this.targets_.clear();
  541. delete this.targets_;
  542. }
  543. };