1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 cr.define('cr.ui', function() {
6 /** @const */ var Command = cr.ui.Command;
9 * Creates a new menu item element.
10 * @param {Object=} opt_propertyBag Optional properties.
12 * @extends {HTMLDivElement}
14 var MenuItem = cr.ui.define('div');
17 * Creates a new menu separator element.
18 * @return {cr.ui.MenuItem} The new separator element.
20 MenuItem.createSeparator = function() {
21 var el = cr.doc.createElement('hr');
22 MenuItem.decorate(el);
26 MenuItem.prototype = {
27 __proto__: HTMLButtonElement.prototype,
30 * Initializes the menu item.
32 decorate: function() {
34 if ((commandId = this.getAttribute('command')))
35 this.command = commandId;
37 this.addEventListener('mouseup', this.handleMouseUp_);
39 // Adding the 'custom-appearance' class prevents widgets.css from changing
40 // the appearance of this element.
41 this.classList.add('custom-appearance');
43 // Enable Text to Speech on the menu. Additionaly, ID has to be set, since
44 // it is used in element's aria-activedescendant attribute.
45 if (!this.isSeparator())
46 this.setAttribute('role', 'menuitem');
49 if ((iconUrl = this.getAttribute('icon')))
50 this.iconUrl = iconUrl;
54 * The command associated with this menu item. If this is set to a string
55 * of the form "#element-id" then the element is looked up in the document
57 * @type {cr.ui.Command}
63 set command(command) {
65 this.command_.removeEventListener('labelChange', this);
66 this.command_.removeEventListener('disabledChange', this);
67 this.command_.removeEventListener('hiddenChange', this);
68 this.command_.removeEventListener('checkedChange', this);
71 if (typeof command == 'string' && command[0] == '#') {
72 command = this.ownerDocument.getElementById(command.slice(1));
73 cr.ui.decorate(command, Command);
76 this.command_ = command;
79 this.setAttribute('command', '#' + command.id);
81 if (typeof command.label === 'string')
82 this.label = command.label;
83 this.disabled = command.disabled;
84 this.hidden = command.hidden;
86 this.command_.addEventListener('labelChange', this);
87 this.command_.addEventListener('disabledChange', this);
88 this.command_.addEventListener('hiddenChange', this);
89 this.command_.addEventListener('checkedChange', this);
92 this.updateShortcut_();
100 return this.textContent;
103 this.textContent = label;
111 return this.style.backgroundImage;
114 this.style.backgroundImage = 'url(' + url + ')';
118 * @return {boolean} Whether the menu item is a separator.
120 isSeparator: function() {
121 return this.tagName == 'HR';
125 * Updates shortcut text according to associated command. If command has
126 * multiple shortcuts, only first one is displayed.
128 updateShortcut_: function() {
129 this.removeAttribute('shortcutText');
131 if (!(this.command_ && this.command_.shortcut))
134 var shortcuts = this.command_.shortcut.split(/\s+/);
136 if (shortcuts.length == 0)
139 var shortcut = shortcuts[0];
142 shortcut.split('-').forEach(function(part) {
143 var partUc = part.toUpperCase();
152 console.assert(!ident, 'Shortcut has two non-modifier keys');
157 var shortcutText = '';
159 // TODO(zvorygin): if more cornercases appear - optimize following
160 // code. Currently 'Enter' keystroke is passed as 'Enter', and 'Space'
161 // is passed as 'U+0020'
162 if (ident == 'U+0020')
165 ['CTRL', 'ALT', 'SHIFT', 'META'].forEach(function(mod) {
167 shortcutText += loadTimeData.getString('SHORTCUT_' + mod) + '+';
170 if (ident.indexOf('U+') != 0) {
172 loadTimeData.getString('SHORTCUT_' + ident.toUpperCase());
175 String.fromCharCode(parseInt(ident.substring(2), 16));
178 this.setAttribute('shortcutText', shortcutText);
182 * Handles mouseup events. This dispatches an activate event; if there is an
183 * associated command, that command is executed.
184 * @param {Event} e The mouseup event object.
187 handleMouseUp_: function(e) {
188 // Only dispatch an activate event for left or middle click.
192 if (!this.disabled && !this.isSeparator() && this.selected) {
193 // Store |contextElement| since it'll be removed by {Menu} on handling
195 var contextElement = this.parentNode.contextElement;
196 var activationEvent = cr.doc.createEvent('Event');
197 activationEvent.initEvent('activate', true, true);
198 activationEvent.originalEvent = e;
199 // Dispatch command event followed by executing the command object.
200 if (this.dispatchEvent(activationEvent)) {
201 var command = this.command;
203 command.execute(contextElement);
204 cr.ui.swallowDoubleClick(e);
211 * Updates command according to the node on which this menu was invoked.
212 * @param {Node=} opt_node Node on which menu was opened.
214 updateCommand: function(opt_node) {
216 this.command_.canExecuteChange(opt_node);
221 * Handles changes to the associated command.
222 * @param {Event} e The event object.
224 handleEvent: function(e) {
226 case 'disabledChange':
227 this.disabled = this.command.disabled;
230 this.hidden = this.command.hidden;
233 this.label = this.command.label;
235 case 'checkedChange':
236 this.checked = this.command.checked;
243 * Whether the menu item is disabled or not.
246 cr.defineProperty(MenuItem, 'disabled', cr.PropertyKind.BOOL_ATTR);
249 * Whether the menu item is hidden or not.
252 cr.defineProperty(MenuItem, 'hidden', cr.PropertyKind.BOOL_ATTR);
255 * Whether the menu item is selected or not.
258 cr.defineProperty(MenuItem, 'selected', cr.PropertyKind.BOOL_ATTR);
261 * Whether the menu item is checked or not.
264 cr.defineProperty(MenuItem, 'checked', cr.PropertyKind.BOOL_ATTR);
267 * Whether the menu item is checkable or not.
270 cr.defineProperty(MenuItem, 'checkable', cr.PropertyKind.BOOL_ATTR);