menuitem.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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 class for representing items in menus.
  16. * @see goog.ui.Menu
  17. *
  18. * @author attila@google.com (Attila Bodis)
  19. * @see ../demos/menuitem.html
  20. */
  21. goog.provide('goog.ui.MenuItem');
  22. goog.require('goog.a11y.aria.Role');
  23. goog.require('goog.array');
  24. goog.require('goog.dom');
  25. goog.require('goog.dom.classlist');
  26. goog.require('goog.math.Coordinate');
  27. goog.require('goog.string');
  28. goog.require('goog.ui.Component');
  29. goog.require('goog.ui.Control');
  30. goog.require('goog.ui.MenuItemRenderer');
  31. goog.require('goog.ui.registry');
  32. goog.forwardDeclare('goog.ui.Menu'); // circular
  33. /**
  34. * Class representing an item in a menu.
  35. *
  36. * @param {goog.ui.ControlContent} content Text caption or DOM structure to
  37. * display as the content of the item (use to add icons or styling to
  38. * menus).
  39. * @param {*=} opt_model Data/model associated with the menu item.
  40. * @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper used for
  41. * document interactions.
  42. * @param {goog.ui.MenuItemRenderer=} opt_renderer Optional renderer.
  43. * @constructor
  44. * @extends {goog.ui.Control}
  45. */
  46. goog.ui.MenuItem = function(content, opt_model, opt_domHelper, opt_renderer) {
  47. goog.ui.Control.call(
  48. this, content, opt_renderer || goog.ui.MenuItemRenderer.getInstance(),
  49. opt_domHelper);
  50. this.setValue(opt_model);
  51. };
  52. goog.inherits(goog.ui.MenuItem, goog.ui.Control);
  53. goog.tagUnsealableClass(goog.ui.MenuItem);
  54. /**
  55. * The access key for this menu item. This key allows the user to quickly
  56. * trigger this item's action with they keyboard. For example, setting the
  57. * mnenomic key to 70 (F), when the user opens the menu and hits "F," the
  58. * menu item is triggered.
  59. *
  60. * @type {goog.events.KeyCodes}
  61. * @private
  62. */
  63. goog.ui.MenuItem.prototype.mnemonicKey_;
  64. /**
  65. * The class set on an element that contains a parenthetical mnemonic key hint.
  66. * Parenthetical hints are added to items in which the mnemonic key is not found
  67. * within the menu item's caption itself. For example, if you have a menu item
  68. * with the caption "Record," but its mnemonic key is "I", the caption displayed
  69. * in the menu will appear as "Record (I)".
  70. *
  71. * @type {string}
  72. * @private
  73. */
  74. goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_ =
  75. goog.getCssName('goog-menuitem-mnemonic-separator');
  76. /**
  77. * The class set on an element that contains a keyboard accelerator hint.
  78. * @type {string}
  79. */
  80. goog.ui.MenuItem.ACCELERATOR_CLASS = goog.getCssName('goog-menuitem-accel');
  81. // goog.ui.Component and goog.ui.Control implementation.
  82. /**
  83. * Returns the value associated with the menu item. The default implementation
  84. * returns the model object associated with the item (if any), or its caption.
  85. * @return {*} Value associated with the menu item, if any, or its caption.
  86. */
  87. goog.ui.MenuItem.prototype.getValue = function() {
  88. var model = this.getModel();
  89. return model != null ? model : this.getCaption();
  90. };
  91. /**
  92. * Sets the value associated with the menu item. The default implementation
  93. * stores the value as the model of the menu item.
  94. * @param {*} value Value to be associated with the menu item.
  95. */
  96. goog.ui.MenuItem.prototype.setValue = function(value) {
  97. this.setModel(value);
  98. };
  99. /** @override */
  100. goog.ui.MenuItem.prototype.setSupportedState = function(state, support) {
  101. goog.ui.MenuItem.base(this, 'setSupportedState', state, support);
  102. switch (state) {
  103. case goog.ui.Component.State.SELECTED:
  104. this.setSelectableInternal_(support);
  105. break;
  106. case goog.ui.Component.State.CHECKED:
  107. this.setCheckableInternal_(support);
  108. break;
  109. }
  110. };
  111. /**
  112. * Sets the menu item to be selectable or not. Set to true for menu items
  113. * that represent selectable options.
  114. * @param {boolean} selectable Whether the menu item is selectable.
  115. */
  116. goog.ui.MenuItem.prototype.setSelectable = function(selectable) {
  117. this.setSupportedState(goog.ui.Component.State.SELECTED, selectable);
  118. };
  119. /**
  120. * Sets the menu item to be selectable or not.
  121. * @param {boolean} selectable Whether the menu item is selectable.
  122. * @private
  123. */
  124. goog.ui.MenuItem.prototype.setSelectableInternal_ = function(selectable) {
  125. if (this.isChecked() && !selectable) {
  126. this.setChecked(false);
  127. }
  128. var element = this.getElement();
  129. if (element) {
  130. this.getRenderer().setSelectable(this, element, selectable);
  131. }
  132. };
  133. /**
  134. * Sets the menu item to be checkable or not. Set to true for menu items
  135. * that represent checkable options.
  136. * @param {boolean} checkable Whether the menu item is checkable.
  137. */
  138. goog.ui.MenuItem.prototype.setCheckable = function(checkable) {
  139. this.setSupportedState(goog.ui.Component.State.CHECKED, checkable);
  140. };
  141. /**
  142. * Sets the menu item to be checkable or not.
  143. * @param {boolean} checkable Whether the menu item is checkable.
  144. * @private
  145. */
  146. goog.ui.MenuItem.prototype.setCheckableInternal_ = function(checkable) {
  147. var element = this.getElement();
  148. if (element) {
  149. this.getRenderer().setCheckable(this, element, checkable);
  150. }
  151. };
  152. /**
  153. * Returns the text caption of the component while ignoring accelerators.
  154. * @override
  155. */
  156. goog.ui.MenuItem.prototype.getCaption = function() {
  157. var content = this.getContent();
  158. if (goog.isArray(content)) {
  159. var acceleratorClass = goog.ui.MenuItem.ACCELERATOR_CLASS;
  160. var mnemonicWrapClass = goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_;
  161. var caption =
  162. goog.array
  163. .map(
  164. content,
  165. function(node) {
  166. if (goog.dom.isElement(node) &&
  167. (goog.dom.classlist.contains(
  168. /** @type {!Element} */ (node), acceleratorClass) ||
  169. goog.dom.classlist.contains(
  170. /** @type {!Element} */ (node),
  171. mnemonicWrapClass))) {
  172. return '';
  173. } else {
  174. return goog.dom.getRawTextContent(node);
  175. }
  176. })
  177. .join('');
  178. return goog.string.collapseBreakingSpaces(caption);
  179. }
  180. return goog.ui.MenuItem.superClass_.getCaption.call(this);
  181. };
  182. /**
  183. * @return {?string} The keyboard accelerator text, or null if the menu item
  184. * doesn't have one.
  185. */
  186. goog.ui.MenuItem.prototype.getAccelerator = function() {
  187. var dom = this.getDomHelper();
  188. var content = this.getContent();
  189. if (goog.isArray(content)) {
  190. var acceleratorEl = goog.array.find(content, function(e) {
  191. return goog.dom.classlist.contains(
  192. /** @type {!Element} */ (e), goog.ui.MenuItem.ACCELERATOR_CLASS);
  193. });
  194. if (acceleratorEl) {
  195. return dom.getTextContent(acceleratorEl);
  196. }
  197. }
  198. return null;
  199. };
  200. /** @override */
  201. goog.ui.MenuItem.prototype.handleMouseUp = function(e) {
  202. var parentMenu = /** @type {goog.ui.Menu} */ (this.getParent());
  203. if (parentMenu) {
  204. var oldCoords = parentMenu.openingCoords;
  205. // Clear out the saved opening coords immediately so they're not used twice.
  206. parentMenu.openingCoords = null;
  207. if (oldCoords && goog.isNumber(e.clientX)) {
  208. var newCoords = new goog.math.Coordinate(e.clientX, e.clientY);
  209. if (goog.math.Coordinate.equals(oldCoords, newCoords)) {
  210. // This menu was opened by a mousedown and we're handling the consequent
  211. // mouseup. The coords haven't changed, meaning this was a simple click,
  212. // not a click and drag. Don't do the usual behavior because the menu
  213. // just popped up under the mouse and the user didn't mean to activate
  214. // this item.
  215. return;
  216. }
  217. }
  218. }
  219. goog.ui.MenuItem.base(this, 'handleMouseUp', e);
  220. };
  221. /** @override */
  222. goog.ui.MenuItem.prototype.handleKeyEventInternal = function(e) {
  223. if (e.keyCode == this.getMnemonic() && this.performActionInternal(e)) {
  224. return true;
  225. } else {
  226. return goog.ui.MenuItem.base(this, 'handleKeyEventInternal', e);
  227. }
  228. };
  229. /**
  230. * Sets the mnemonic key code. The mnemonic is the key associated with this
  231. * action.
  232. * @param {goog.events.KeyCodes} key The key code.
  233. */
  234. goog.ui.MenuItem.prototype.setMnemonic = function(key) {
  235. this.mnemonicKey_ = key;
  236. };
  237. /**
  238. * Gets the mnemonic key code. The mnemonic is the key associated with this
  239. * action.
  240. * @return {goog.events.KeyCodes} The key code of the mnemonic key.
  241. */
  242. goog.ui.MenuItem.prototype.getMnemonic = function() {
  243. return this.mnemonicKey_;
  244. };
  245. // Register a decorator factory function for goog.ui.MenuItems.
  246. goog.ui.registry.setDecoratorByClassName(
  247. goog.ui.MenuItemRenderer.CSS_CLASS, function() {
  248. // MenuItem defaults to using MenuItemRenderer.
  249. return new goog.ui.MenuItem(null);
  250. });
  251. /**
  252. * @override
  253. */
  254. goog.ui.MenuItem.prototype.getPreferredAriaRole = function() {
  255. if (this.isSupportedState(goog.ui.Component.State.CHECKED)) {
  256. return goog.a11y.aria.Role.MENU_ITEM_CHECKBOX;
  257. }
  258. if (this.isSupportedState(goog.ui.Component.State.SELECTED)) {
  259. return goog.a11y.aria.Role.MENU_ITEM_RADIO;
  260. }
  261. return goog.ui.MenuItem.base(this, 'getPreferredAriaRole');
  262. };
  263. /**
  264. * @override
  265. * @return {goog.ui.Menu}
  266. */
  267. goog.ui.MenuItem.prototype.getParent = function() {
  268. return /** @type {goog.ui.Menu} */ (
  269. goog.ui.Control.prototype.getParent.call(this));
  270. };
  271. /**
  272. * @override
  273. * @return {goog.ui.Menu}
  274. */
  275. goog.ui.MenuItem.prototype.getParentEventTarget = function() {
  276. return /** @type {goog.ui.Menu} */ (
  277. goog.ui.Control.prototype.getParentEventTarget.call(this));
  278. };