Updated drag and drop thumbnails.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_command_list.js
blob16901ecf25e5063b9d9af518bee1840ef82e0a67
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('options', function() {
6 'use strict';
8 /**
9 * Creates a new list of extension commands.
10 * @param {Object=} opt_propertyBag Optional properties.
11 * @constructor
12 * @extends {cr.ui.div}
14 var ExtensionCommandList = cr.ui.define('div');
16 /**
17 * Returns whether the passed in |keyCode| is a valid extension command
18 * char or not. This is restricted to A-Z and 0-9 (ignoring modifiers) at
19 * the moment.
20 * @param {int} keyCode The keycode to consider.
21 * @return {boolean} Returns whether the char is valid.
23 function validChar(keyCode) {
24 return (keyCode >= 'A'.charCodeAt(0) && keyCode <= 'Z'.charCodeAt(0)) ||
25 (keyCode >= '0'.charCodeAt(0) && keyCode <= '9'.charCodeAt(0));
28 /**
29 * Convert a keystroke event to string form, while taking into account
30 * (ignoring) invalid extension commands.
31 * @param {Event} event The keyboard event to convert.
32 * @return {string} The keystroke as a string.
34 function keystrokeToString(event) {
35 var output = '';
36 if (cr.isMac && event.metaKey)
37 output = 'Command+';
38 if (event.ctrlKey)
39 output = 'Ctrl+';
40 if (!event.ctrlKey && event.altKey)
41 output += 'Alt+';
42 if (event.shiftKey)
43 output += 'Shift+';
44 if (validChar(event.keyCode))
45 output += String.fromCharCode('A'.charCodeAt(0) + event.keyCode - 65);
46 return output;
49 ExtensionCommandList.prototype = {
50 __proto__: HTMLDivElement.prototype,
52 /**
53 * While capturing, this records the current (last) keyboard event generated
54 * by the user. Will be |null| after capture and during capture when no
55 * keyboard event has been generated.
56 * @type: {keyboard event}.
57 * @private
59 currentKeyEvent_: null,
61 /**
62 * While capturing, this keeps track of the previous selection so we can
63 * revert back to if no valid assignment is made during capture.
64 * @type: {string}.
65 * @private
67 oldValue_: '',
69 /**
70 * While capturing, this keeps track of which element the user asked to
71 * change.
72 * @type: {HTMLElement}.
73 * @private
75 capturingElement_: null,
77 /** @override */
78 decorate: function() {
79 this.textContent = '';
81 // Iterate over the extension data and add each item to the list.
82 this.data_.commands.forEach(this.createNodeForExtension_.bind(this));
85 /**
86 * Synthesizes and initializes an HTML element for the extension command
87 * metadata given in |extension|.
88 * @param {Object} extension A dictionary of extension metadata.
89 * @private
91 createNodeForExtension_: function(extension) {
92 var template = $('template-collection-extension-commands').querySelector(
93 '.extension-command-list-extension-item-wrapper');
94 var node = template.cloneNode(true);
96 var title = node.querySelector('.extension-title');
97 title.textContent = extension.name;
99 this.appendChild(node);
101 // Iterate over the commands data within the extension and add each item
102 // to the list.
103 extension.commands.forEach(this.createNodeForCommand_.bind(this));
107 * Synthesizes and initializes an HTML element for the extension command
108 * metadata given in |command|.
109 * @param {Object} command A dictionary of extension command metadata.
110 * @private
112 createNodeForCommand_: function(command) {
113 var template = $('template-collection-extension-commands').querySelector(
114 '.extension-command-list-command-item-wrapper');
115 var node = template.cloneNode(true);
116 node.id = this.createElementId_(
117 'command', command.extension_id, command.command_name);
119 var description = node.querySelector('.command-description');
120 description.textContent = command.description;
122 var shortcutNode = node.querySelector('.command-shortcut-text');
123 shortcutNode.addEventListener('mouseup',
124 this.startCapture_.bind(this));
125 shortcutNode.addEventListener('blur', this.endCapture_.bind(this));
126 shortcutNode.addEventListener('keydown',
127 this.handleKeyDown_.bind(this));
128 shortcutNode.addEventListener('keyup', this.handleKeyUp_.bind(this));
129 if (!command.active) {
130 shortcutNode.textContent =
131 loadTimeData.getString('extensionCommandsInactive');
133 var commandShortcut = node.querySelector('.command-shortcut');
134 commandShortcut.classList.add('inactive-keybinding');
135 } else {
136 shortcutNode.textContent = command.keybinding;
139 var commandClear = node.querySelector('.command-clear');
140 commandClear.id = this.createElementId_(
141 'clear', command.extension_id, command.command_name);
142 commandClear.title = loadTimeData.getString('extensionCommandsDelete');
143 commandClear.addEventListener('click', this.handleClear_.bind(this));
145 this.appendChild(node);
149 * Starts keystroke capture to determine which key to use for a particular
150 * extension command.
151 * @param {Event} event The keyboard event to consider.
152 * @private
154 startCapture_: function(event) {
155 if (this.capturingElement_)
156 return; // Already capturing.
158 chrome.send('setShortcutHandlingSuspended', [true]);
160 var shortcutNode = event.target;
161 this.oldValue_ = shortcutNode.textContent;
162 shortcutNode.textContent =
163 loadTimeData.getString('extensionCommandsStartTyping');
164 shortcutNode.parentElement.classList.add('capturing');
166 var commandClear =
167 shortcutNode.parentElement.querySelector('.command-clear');
168 commandClear.hidden = true;
170 this.capturingElement_ = event.target;
174 * Ends keystroke capture and either restores the old value or (if valid
175 * value) sets the new value as active..
176 * @param {Event} event The keyboard event to consider.
177 * @private
179 endCapture_: function(event) {
180 if (!this.capturingElement_)
181 return; // Not capturing.
183 chrome.send('setShortcutHandlingSuspended', [false]);
185 var shortcutNode = this.capturingElement_;
186 var commandShortcut = shortcutNode.parentElement;
188 commandShortcut.classList.remove('capturing');
189 commandShortcut.classList.remove('contains-chars');
191 // When the capture ends, the user may have not given a complete and valid
192 // input (or even no input at all). Only a valid key event followed by a
193 // valid key combination will cause a shortcut selection to be activated.
194 // If no valid selection was made, howver, revert back to what the textbox
195 // had before to indicate that the shortcut registration was cancelled.
196 if (!this.currentKeyEvent_ || !validChar(this.currentKeyEvent_.keyCode))
197 shortcutNode.textContent = this.oldValue_;
199 var commandClear = commandShortcut.querySelector('.command-clear');
200 if (this.oldValue_ == '') {
201 commandShortcut.classList.remove('clearable');
202 commandClear.hidden = true;
203 } else {
204 commandShortcut.classList.add('clearable');
205 commandClear.hidden = false;
208 this.oldValue_ = '';
209 this.capturingElement_ = null;
210 this.currentKeyEvent_ = null;
214 * The KeyDown handler.
215 * @param {Event} event The keyboard event to consider.
216 * @private
218 handleKeyDown_: function(event) {
219 if (!this.capturingElement_)
220 this.startCapture_(event);
222 this.handleKey_(event);
226 * The KeyUp handler.
227 * @param {Event} event The keyboard event to consider.
228 * @private
230 handleKeyUp_: function(event) {
231 // We want to make it easy to change from Ctrl+Shift+ to just Ctrl+ by
232 // releasing Shift, but we also don't want it to be easy to lose for
233 // example Ctrl+Shift+F to Ctrl+ just because you didn't release Ctrl
234 // as fast as the other two keys. Therefore, we process KeyUp until you
235 // have a valid combination and then stop processing it (meaning that once
236 // you have a valid combination, we won't change it until the next
237 // KeyDown message arrives).
238 if (!this.currentKeyEvent_ || !validChar(this.currentKeyEvent_.keyCode)) {
239 if (!event.ctrlKey && !event.altKey) {
240 // If neither Ctrl nor Alt is pressed then it is not a valid shortcut.
241 // That means we're back at the starting point so we should restart
242 // capture.
243 this.endCapture_(event);
244 this.startCapture_(event);
245 } else {
246 this.handleKey_(event);
252 * A general key handler (used for both KeyDown and KeyUp).
253 * @param {Event} event The keyboard event to consider.
254 * @private
256 handleKey_: function(event) {
257 // While capturing, we prevent all events from bubbling, to prevent
258 // shortcuts lacking the right modifier (F3 for example) from activating
259 // and ending capture prematurely.
260 event.preventDefault();
261 event.stopPropagation();
263 if (!event.ctrlKey && !event.altKey && (!cr.isMac || !event.metaKey))
264 return; // Ctrl or Alt is a must (or Cmd on Mac).
266 var shortcutNode = this.capturingElement_;
267 var keystroke = keystrokeToString(event);
268 shortcutNode.textContent = keystroke;
269 event.target.classList.add('contains-chars');
271 if (validChar(event.keyCode)) {
272 var node = event.target;
273 while (node && !node.id)
274 node = node.parentElement;
276 this.oldValue_ = keystroke; // Forget what the old value was.
277 var parsed = this.parseElementId_('command', node.id);
278 chrome.send('setExtensionCommandShortcut',
279 [parsed.extensionId, parsed.commandName, keystroke]);
280 this.endCapture_(event);
283 this.currentKeyEvent_ = event;
287 * A handler for the delete command button.
288 * @param {Event} event The mouse event to consider.
289 * @private
291 handleClear_: function(event) {
292 var parsed = this.parseElementId_('clear', event.target.id);
293 chrome.send('setExtensionCommandShortcut',
294 [parsed.extensionId, parsed.commandName, '']);
298 * A utility function to create a unique element id based on a namespace,
299 * extension id and a command name.
300 * @param {string} namespace The namespace to prepend the id with.
301 * @param {string} extensionId The extension ID to use in the id.
302 * @param {string} commandName The command name to append the id with.
303 * @private
305 createElementId_: function(namespace, extensionId, commandName) {
306 return namespace + '-' + extensionId + '-' + commandName;
310 * A utility function to parse a unique element id based on a namespace,
311 * extension id and a command name.
312 * @param {string} namespace The namespace to prepend the id with.
313 * @param {string} id The id to parse.
314 * @return {object} The parsed id, as an object with two members:
315 * extensionID and commandName.
316 * @private
318 parseElementId_: function(namespace, id) {
319 var kExtensionIdLength = 32;
320 return {
321 extensionId: id.substring(namespace.length + 1,
322 namespace.length + 1 + kExtensionIdLength),
323 commandName: id.substring(namespace.length + 1 + kExtensionIdLength + 1)
328 return {
329 ExtensionCommandList: ExtensionCommandList