blob: 9db7b88450701ec3104bad38120cd602d535b990 [file] [log] [blame]
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A class for representing items in menus.
* @see goog.ui.Menu
*
* @author attila@google.com (Attila Bodis)
* @see ../demos/menuitem.html
*/
goog.provide('goog.ui.MenuItem');
goog.require('goog.a11y.aria.Role');
goog.require('goog.array');
goog.require('goog.dom');
goog.require('goog.dom.classlist');
goog.require('goog.math.Coordinate');
goog.require('goog.string');
goog.require('goog.ui.Component');
goog.require('goog.ui.Control');
goog.require('goog.ui.MenuItemRenderer');
goog.require('goog.ui.registry');
/**
* Class representing an item in a menu.
*
* @param {goog.ui.ControlContent} content Text caption or DOM structure to
* display as the content of the item (use to add icons or styling to
* menus).
* @param {*=} opt_model Data/model associated with the menu item.
* @param {goog.dom.DomHelper=} opt_domHelper Optional DOM helper used for
* document interactions.
* @param {goog.ui.MenuItemRenderer=} opt_renderer Optional renderer.
* @constructor
* @extends {goog.ui.Control}
*/
goog.ui.MenuItem = function(content, opt_model, opt_domHelper, opt_renderer) {
goog.ui.Control.call(this, content, opt_renderer ||
goog.ui.MenuItemRenderer.getInstance(), opt_domHelper);
this.setValue(opt_model);
};
goog.inherits(goog.ui.MenuItem, goog.ui.Control);
goog.tagUnsealableClass(goog.ui.MenuItem);
/**
* The access key for this menu item. This key allows the user to quickly
* trigger this item's action with they keyboard. For example, setting the
* mnenomic key to 70 (F), when the user opens the menu and hits "F," the
* menu item is triggered.
*
* @type {goog.events.KeyCodes}
* @private
*/
goog.ui.MenuItem.prototype.mnemonicKey_;
/**
* The class set on an element that contains a parenthetical mnemonic key hint.
* Parenthetical hints are added to items in which the mnemonic key is not found
* within the menu item's caption itself. For example, if you have a menu item
* with the caption "Record," but its mnemonic key is "I", the caption displayed
* in the menu will appear as "Record (I)".
*
* @type {string}
* @private
*/
goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_ =
goog.getCssName('goog-menuitem-mnemonic-separator');
/**
* The class set on an element that contains a keyboard accelerator hint.
* @type {string}
*/
goog.ui.MenuItem.ACCELERATOR_CLASS = goog.getCssName('goog-menuitem-accel');
// goog.ui.Component and goog.ui.Control implementation.
/**
* Returns the value associated with the menu item. The default implementation
* returns the model object associated with the item (if any), or its caption.
* @return {*} Value associated with the menu item, if any, or its caption.
*/
goog.ui.MenuItem.prototype.getValue = function() {
var model = this.getModel();
return model != null ? model : this.getCaption();
};
/**
* Sets the value associated with the menu item. The default implementation
* stores the value as the model of the menu item.
* @param {*} value Value to be associated with the menu item.
*/
goog.ui.MenuItem.prototype.setValue = function(value) {
this.setModel(value);
};
/** @override */
goog.ui.MenuItem.prototype.setSupportedState = function(state, support) {
goog.ui.MenuItem.base(this, 'setSupportedState', state, support);
switch (state) {
case goog.ui.Component.State.SELECTED:
this.setSelectableInternal_(support);
break;
case goog.ui.Component.State.CHECKED:
this.setCheckableInternal_(support);
break;
}
};
/**
* Sets the menu item to be selectable or not. Set to true for menu items
* that represent selectable options.
* @param {boolean} selectable Whether the menu item is selectable.
*/
goog.ui.MenuItem.prototype.setSelectable = function(selectable) {
this.setSupportedState(goog.ui.Component.State.SELECTED, selectable);
};
/**
* Sets the menu item to be selectable or not.
* @param {boolean} selectable Whether the menu item is selectable.
* @private
*/
goog.ui.MenuItem.prototype.setSelectableInternal_ = function(selectable) {
if (this.isChecked() && !selectable) {
this.setChecked(false);
}
var element = this.getElement();
if (element) {
this.getRenderer().setSelectable(this, element, selectable);
}
};
/**
* Sets the menu item to be checkable or not. Set to true for menu items
* that represent checkable options.
* @param {boolean} checkable Whether the menu item is checkable.
*/
goog.ui.MenuItem.prototype.setCheckable = function(checkable) {
this.setSupportedState(goog.ui.Component.State.CHECKED, checkable);
};
/**
* Sets the menu item to be checkable or not.
* @param {boolean} checkable Whether the menu item is checkable.
* @private
*/
goog.ui.MenuItem.prototype.setCheckableInternal_ = function(checkable) {
var element = this.getElement();
if (element) {
this.getRenderer().setCheckable(this, element, checkable);
}
};
/**
* Returns the text caption of the component while ignoring accelerators.
* @override
*/
goog.ui.MenuItem.prototype.getCaption = function() {
var content = this.getContent();
if (goog.isArray(content)) {
var acceleratorClass = goog.ui.MenuItem.ACCELERATOR_CLASS;
var mnemonicWrapClass = goog.ui.MenuItem.MNEMONIC_WRAPPER_CLASS_;
var caption = goog.array.map(content, function(node) {
if (goog.dom.isElement(node) &&
(goog.dom.classlist.contains(/** @type {!Element} */ (node),
acceleratorClass) ||
goog.dom.classlist.contains(/** @type {!Element} */ (node),
mnemonicWrapClass))) {
return '';
} else {
return goog.dom.getRawTextContent(node);
}
}).join('');
return goog.string.collapseBreakingSpaces(caption);
}
return goog.ui.MenuItem.superClass_.getCaption.call(this);
};
/**
* @return {?string} The keyboard accelerator text, or null if the menu item
* doesn't have one.
*/
goog.ui.MenuItem.prototype.getAccelerator = function() {
var dom = this.getDomHelper();
var content = this.getContent();
if (goog.isArray(content)) {
var acceleratorEl = goog.array.find(content, function(e) {
return goog.dom.classlist.contains(/** @type {!Element} */ (e),
goog.ui.MenuItem.ACCELERATOR_CLASS);
});
if (acceleratorEl) {
return dom.getTextContent(acceleratorEl);
}
}
return null;
};
/** @override */
goog.ui.MenuItem.prototype.handleMouseUp = function(e) {
var parentMenu = /** @type {goog.ui.Menu} */ (this.getParent());
if (parentMenu) {
var oldCoords = parentMenu.openingCoords;
// Clear out the saved opening coords immediately so they're not used twice.
parentMenu.openingCoords = null;
if (oldCoords && goog.isNumber(e.clientX)) {
var newCoords = new goog.math.Coordinate(e.clientX, e.clientY);
if (goog.math.Coordinate.equals(oldCoords, newCoords)) {
// This menu was opened by a mousedown and we're handling the consequent
// mouseup. The coords haven't changed, meaning this was a simple click,
// not a click and drag. Don't do the usual behavior because the menu
// just popped up under the mouse and the user didn't mean to activate
// this item.
return;
}
}
}
goog.ui.MenuItem.base(this, 'handleMouseUp', e);
};
/** @override */
goog.ui.MenuItem.prototype.handleKeyEventInternal = function(e) {
if (e.keyCode == this.getMnemonic() && this.performActionInternal(e)) {
return true;
} else {
return goog.ui.MenuItem.base(this, 'handleKeyEventInternal', e);
}
};
/**
* Sets the mnemonic key code. The mnemonic is the key associated with this
* action.
* @param {goog.events.KeyCodes} key The key code.
*/
goog.ui.MenuItem.prototype.setMnemonic = function(key) {
this.mnemonicKey_ = key;
};
/**
* Gets the mnemonic key code. The mnemonic is the key associated with this
* action.
* @return {goog.events.KeyCodes} The key code of the mnemonic key.
*/
goog.ui.MenuItem.prototype.getMnemonic = function() {
return this.mnemonicKey_;
};
// Register a decorator factory function for goog.ui.MenuItems.
goog.ui.registry.setDecoratorByClassName(goog.ui.MenuItemRenderer.CSS_CLASS,
function() {
// MenuItem defaults to using MenuItemRenderer.
return new goog.ui.MenuItem(null);
});
/**
* @override
*/
goog.ui.MenuItem.prototype.getPreferredAriaRole = function() {
if (this.isSupportedState(goog.ui.Component.State.CHECKED)) {
return goog.a11y.aria.Role.MENU_ITEM_CHECKBOX;
}
if (this.isSupportedState(goog.ui.Component.State.SELECTED)) {
return goog.a11y.aria.Role.MENU_ITEM_RADIO;
}
return goog.ui.MenuItem.base(this, 'getPreferredAriaRole');
};
/**
* @override
* @return {goog.ui.Menu}
*/
goog.ui.MenuItem.prototype.getParent = function() {
return /** @type {goog.ui.Menu} */ (
goog.ui.Control.prototype.getParent.call(this));
};
/**
* @override
* @return {goog.ui.Menu}
*/
goog.ui.MenuItem.prototype.getParentEventTarget = function() {
return /** @type {goog.ui.Menu} */ (
goog.ui.Control.prototype.getParentEventTarget.call(this));
};