Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / settings / SettingsScreen.js
blobfceee6e883a39b4ee075efdb43966c0fafa466f7
1 /*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 /**
32 * @constructor
33 * @param {function()} onHide
34 * @extends {WebInspector.HelpScreen}
36 WebInspector.SettingsScreen = function(onHide)
38 WebInspector.HelpScreen.call(this);
39 this.element.id = "settings-screen";
41 /** @type {function()} */
42 this._onHide = onHide;
44 this._contentElement = this.element.createChild("div", "help-window-main");
45 var settingsLabelElement = createElementWithClass("div", "help-window-label");
46 settingsLabelElement.createTextChild(WebInspector.UIString("Settings"));
47 this._contentElement.appendChild(this.createCloseButton());
49 this._tabbedPane = new WebInspector.TabbedPane();
50 this._tabbedPane.insertBeforeTabStrip(settingsLabelElement);
51 this._tabbedPane.setShrinkableTabs(false);
52 this._tabbedPane.setVerticalTabLayout(true);
53 this._tabbedPane.appendTab("general", WebInspector.UIString("General"), new WebInspector.GenericSettingsTab());
54 this._tabbedPane.appendTab("workspace", WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab());
55 if (Runtime.experiments.supportEnabled())
56 this._tabbedPane.appendTab("experiments", WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab());
57 this._tabbedPaneController = new WebInspector.ExtensibleTabbedPaneController(this._tabbedPane, "settings-view");
58 this._tabbedPane.appendTab("shortcuts", WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView());
60 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
61 this._developerModeCounter = 0;
64 WebInspector.SettingsScreen.prototype = {
65 /**
66 * @override
68 wasShown: function()
70 this._tabbedPane.selectTab("general");
71 this._tabbedPane.show(this._contentElement);
72 WebInspector.HelpScreen.prototype.wasShown.call(this);
75 /**
76 * @param {string} name
78 selectTab: function(name)
80 this._tabbedPane.selectTab(name);
83 /**
84 * @override
85 * @return {boolean}
87 isClosingKey: function(keyCode)
89 return [
90 WebInspector.KeyboardShortcut.Keys.Enter.code,
91 WebInspector.KeyboardShortcut.Keys.Esc.code,
92 ].indexOf(keyCode) >= 0;
95 /**
96 * @override
98 willHide: function()
100 this._onHide();
101 WebInspector.HelpScreen.prototype.willHide.call(this);
105 * @param {!Event} event
107 _keyDown: function(event)
109 var shiftKeyCode = 16;
110 if (event.keyCode === shiftKeyCode && ++this._developerModeCounter > 5)
111 this.element.classList.add("settings-developer-mode");
114 __proto__: WebInspector.HelpScreen.prototype
118 * @constructor
119 * @extends {WebInspector.VBox}
120 * @param {string} name
121 * @param {string=} id
123 WebInspector.SettingsTab = function(name, id)
125 WebInspector.VBox.call(this);
126 this.element.classList.add("settings-tab-container");
127 if (id)
128 this.element.id = id;
129 var header = this.element.createChild("header");
130 header.createChild("h3").createTextChild(name);
131 this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container");
134 WebInspector.SettingsTab.prototype = {
136 * @param {string=} name
137 * @return {!Element}
139 _appendSection: function(name)
141 var block = this.containerElement.createChild("div", "help-block");
142 if (name)
143 block.createChild("div", "help-section-title").textContent = name;
144 return block;
147 _createSelectSetting: function(name, options, setting)
149 var p = createElement("p");
150 p.createChild("label").textContent = name;
152 var select = p.createChild("select", "chrome-select");
153 var settingValue = setting.get();
155 for (var i = 0; i < options.length; ++i) {
156 var option = options[i];
157 select.add(new Option(option[0], option[1]));
158 if (settingValue === option[1])
159 select.selectedIndex = i;
162 function changeListener(e)
164 // Don't use e.target.value to avoid conversion of the value to string.
165 setting.set(options[select.selectedIndex][1]);
168 select.addEventListener("change", changeListener, false);
169 return p;
172 __proto__: WebInspector.VBox.prototype
176 * @constructor
177 * @extends {WebInspector.SettingsTab}
179 WebInspector.GenericSettingsTab = function()
181 WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content");
183 /** @const */
184 var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Network", "Profiler", "Console", "Extensions"];
185 /** @type {!Map<string, !Element>} */
186 this._nameToSection = new Map();
187 /** @type {!Map<string, !Element>} */
188 this._nameToSettingElement = new Map();
189 for (var sectionName of explicitSectionOrder)
190 this._sectionElement(sectionName);
191 self.runtime.extensions("setting").forEach(this._addSetting.bind(this));
192 self.runtime.extensions(WebInspector.SettingUI).forEach(this._addSettingUI.bind(this));
194 this._appendSection().appendChild(createTextButton(WebInspector.UIString("Restore defaults and reload"), restoreAndReload));
196 function restoreAndReload()
198 WebInspector.settings.clearAll();
199 WebInspector.reload();
204 * @param {!Runtime.Extension} extension
205 * @return {boolean}
207 WebInspector.GenericSettingsTab.isSettingVisible = function(extension)
209 var descriptor = extension.descriptor();
210 if (!("title" in descriptor))
211 return false;
212 if (!(("category" in descriptor) || ("parentSettingName" in descriptor)))
213 return false;
214 return true;
217 WebInspector.GenericSettingsTab.prototype = {
219 * @param {!Runtime.Extension} extension
221 _addSetting: function(extension)
223 if (!WebInspector.GenericSettingsTab.isSettingVisible(extension))
224 return;
225 var descriptor = extension.descriptor();
226 var sectionName = descriptor["category"];
227 var settingName = descriptor["settingName"];
228 var setting = WebInspector.moduleSetting(settingName);
229 var uiTitle = WebInspector.UIString(extension.title(WebInspector.platform()));
231 var sectionElement = this._sectionElement(sectionName);
232 var parentSettingName = descriptor["parentSettingName"];
233 var parentSettingElement = parentSettingName ? this._nameToSettingElement.get(descriptor["parentSettingName"]) : null;
234 var parentFieldset = null;
235 if (parentSettingElement) {
236 parentFieldset = parentSettingElement.__fieldset;
237 if (!parentFieldset) {
238 parentFieldset = WebInspector.SettingsUI.createSettingFieldset(WebInspector.moduleSetting(parentSettingName));
239 parentSettingElement.appendChild(parentFieldset);
240 parentSettingElement.__fieldset = parentFieldset;
244 var settingControl;
246 switch (descriptor["settingType"]) {
247 case "boolean":
248 settingControl = WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting);
249 break;
250 case "enum":
251 var descriptorOptions = descriptor["options"];
252 var options = new Array(descriptorOptions.length);
253 for (var i = 0; i < options.length; ++i) {
254 // The third array item flags that the option name is "raw" (non-i18n-izable).
255 var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]);
256 options[i] = [optionName, descriptorOptions[i][1]];
258 settingControl = this._createSelectSetting(uiTitle, options, setting);
259 break;
260 default:
261 console.error("Invalid setting type: " + descriptor["settingType"]);
262 return;
264 this._nameToSettingElement.set(settingName, settingControl);
265 (parentFieldset || sectionElement).appendChild(/** @type {!Element} */ (settingControl));
269 * @param {!Runtime.Extension} extension
271 _addSettingUI: function(extension)
273 var descriptor = extension.descriptor();
274 var sectionName = descriptor["category"] || "";
275 extension.instancePromise().then(appendCustomSetting.bind(this));
278 * @param {!Object} object
279 * @this {WebInspector.GenericSettingsTab}
281 function appendCustomSetting(object)
283 var settingUI = /** @type {!WebInspector.SettingUI} */ (object);
284 var element = settingUI.settingElement();
285 if (element)
286 this._sectionElement(sectionName).appendChild(element);
291 * @param {string} sectionName
292 * @return {!Element}
294 _sectionElement: function(sectionName)
296 var sectionElement = this._nameToSection.get(sectionName);
297 if (!sectionElement) {
298 var uiSectionName = sectionName && WebInspector.UIString(sectionName);
299 sectionElement = this._appendSection(uiSectionName);
300 this._nameToSection.set(sectionName, sectionElement);
302 return sectionElement;
305 __proto__: WebInspector.SettingsTab.prototype
309 * @constructor
310 * @implements {WebInspector.SettingUI}
312 WebInspector.SettingsScreen.SkipStackFramePatternSettingUI = function()
316 WebInspector.SettingsScreen.SkipStackFramePatternSettingUI.prototype = {
318 * @override
319 * @return {!Element}
321 settingElement: function()
323 return createTextButton(WebInspector.manageBlackboxingButtonLabel(), this._onManageButtonClick.bind(this), "", WebInspector.UIString("Skip stepping through sources with particular names"));
326 _onManageButtonClick: function()
328 WebInspector.FrameworkBlackboxDialog.show(WebInspector.inspectorView.element);
333 * @constructor
334 * @extends {WebInspector.SettingsTab}
336 WebInspector.WorkspaceSettingsTab = function()
338 WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content");
339 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
340 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
342 this._commonSection = this._appendSection(WebInspector.UIString("Common"));
343 var folderExcludeSetting = WebInspector.isolatedFileSystemManager.excludedFolderManager().workspaceFolderExcludePatternSetting();
344 var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), folderExcludeSetting, false, 0, "270px", WebInspector.SettingsUI.regexValidator);
345 this._commonSection.appendChild(folderExcludePatternInput);
347 this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders"));
348 this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container");
350 this._addFileSystemRowElement = this._fileSystemsSection.createChild("div");
351 this._addFileSystemRowElement.appendChild(createTextButton(WebInspector.UIString("Add folder\u2026"), this._addFileSystemClicked.bind(this)));
353 this._editFileSystemButton = createTextButton(WebInspector.UIString("Folder options\u2026"), this._editFileSystemClicked.bind(this));
354 this._addFileSystemRowElement.appendChild(this._editFileSystemButton);
355 this._updateEditFileSystemButtonState();
357 this._reset();
360 WebInspector.WorkspaceSettingsTab.prototype = {
361 wasShown: function()
363 WebInspector.SettingsTab.prototype.wasShown.call(this);
364 this._reset();
367 _reset: function()
369 this._resetFileSystems();
372 _resetFileSystems: function()
374 this._fileSystemsListContainer.removeChildren();
375 var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths();
376 delete this._fileSystemsList;
378 if (!fileSystemPaths.length) {
379 var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message");
380 noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added.");
381 return;
384 this._fileSystemsList = new WebInspector.SettingsList([{ id: "path" }], this._renderFileSystem.bind(this));
385 this._fileSystemsList.element.classList.add("file-systems-list");
386 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this));
387 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this));
388 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this));
389 this._fileSystemsListContainer.appendChild(this._fileSystemsList.element);
390 for (var i = 0; i < fileSystemPaths.length; ++i)
391 this._fileSystemsList.addItem(fileSystemPaths[i]);
392 this._updateEditFileSystemButtonState();
395 _updateEditFileSystemButtonState: function()
397 this._editFileSystemButton.disabled = !this._selectedFileSystemPath();
401 * @param {!WebInspector.Event} event
403 _fileSystemSelected: function(event)
405 this._updateEditFileSystemButtonState();
409 * @param {!WebInspector.Event} event
411 _fileSystemDoubleClicked: function(event)
413 var id = /** @type{?string} */ (event.data);
414 this._editFileSystem(id);
417 _editFileSystemClicked: function()
419 this._editFileSystem(this._selectedFileSystemPath());
423 * @param {?string} id
425 _editFileSystem: function(id)
427 WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.element, id);
431 * @param {!Element} columnElement
432 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
433 * @param {?string} id
435 _renderFileSystem: function(columnElement, column, id)
437 if (!id)
438 return "";
439 var fileSystemPath = id;
440 var textElement = columnElement.createChild("span", "list-column-text");
441 var pathElement = textElement.createChild("span", "file-system-path");
442 pathElement.title = fileSystemPath;
444 const maxTotalPathLength = 55;
445 const maxFolderNameLength = 30;
447 var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/");
448 var folderName = fileSystemPath.substr(lastIndexOfSlash + 1);
449 var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1);
450 folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length));
451 folderName = folderName.trimMiddle(maxFolderNameLength);
453 var folderPathElement = pathElement.createChild("span");
454 folderPathElement.textContent = folderPath;
456 var nameElement = pathElement.createChild("span", "file-system-path-name");
457 nameElement.textContent = folderName;
461 * @param {!WebInspector.Event} event
463 _fileSystemRemovedfromList: function(event)
465 var id = /** @type{?string} */ (event.data);
466 if (!id)
467 return;
468 WebInspector.isolatedFileSystemManager.removeFileSystem(id);
471 _addFileSystemClicked: function()
473 WebInspector.isolatedFileSystemManager.addFileSystem();
476 _fileSystemAdded: function(event)
478 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
479 if (!this._fileSystemsList)
480 this._reset();
481 else
482 this._fileSystemsList.addItem(fileSystem.path());
485 _fileSystemRemoved: function(event)
487 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
488 if (this._fileSystemsList.itemForId(fileSystem.path()))
489 this._fileSystemsList.removeItem(fileSystem.path());
490 if (!this._fileSystemsList.itemIds().length)
491 this._reset();
492 this._updateEditFileSystemButtonState();
495 _selectedFileSystemPath: function()
497 return this._fileSystemsList ? this._fileSystemsList.selectedId() : null;
500 __proto__: WebInspector.SettingsTab.prototype
505 * @constructor
506 * @extends {WebInspector.SettingsTab}
508 WebInspector.ExperimentsSettingsTab = function()
510 WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content");
512 var experiments = Runtime.experiments.allConfigurableExperiments();
513 if (experiments.length) {
514 var experimentsSection = this._appendSection();
515 experimentsSection.appendChild(this._createExperimentsWarningSubsection());
516 for (var i = 0; i < experiments.length; ++i)
517 experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i]));
521 WebInspector.ExperimentsSettingsTab.prototype = {
523 * @return {!Element} element
525 _createExperimentsWarningSubsection: function()
527 var subsection = createElement("div");
528 var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning");
529 warning.textContent = WebInspector.UIString("WARNING:");
530 subsection.createTextChild(" ");
531 var message = subsection.createChild("span", "settings-experiments-warning-subsection-message");
532 message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart.");
533 return subsection;
536 _createExperimentCheckbox: function(experiment)
538 var label = createCheckboxLabel(WebInspector.UIString(experiment.title), experiment.isEnabled());
539 var input = label.checkboxElement;
540 input.name = experiment.name;
541 function listener()
543 experiment.setEnabled(input.checked);
545 input.addEventListener("click", listener, false);
547 var p = createElement("p");
548 p.className = experiment.hidden && !experiment.isEnabled() ? "settings-experiment-hidden" : "";
549 p.appendChild(label);
550 return p;
553 __proto__: WebInspector.SettingsTab.prototype
557 * @constructor
559 WebInspector.SettingsController = function()
561 /** @type {?WebInspector.SettingsScreen} */
562 this._settingsScreen;
563 this._resizeBound = this._resize.bind(this);
566 WebInspector.SettingsController.prototype = {
567 _onHideSettingsScreen: function()
569 var window = this._settingsScreen.element.ownerDocument.defaultView;
570 window.removeEventListener("resize", this._resizeBound, false);
571 delete this._settingsScreenVisible;
575 * @param {string=} name
577 showSettingsScreen: function(name)
579 if (!this._settingsScreen)
580 this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this));
581 this._settingsScreen.showModal();
582 if (name)
583 this._settingsScreen.selectTab(name);
584 this._settingsScreenVisible = true;
585 var window = this._settingsScreen.element.ownerDocument.defaultView;
586 window.addEventListener("resize", this._resizeBound, false);
589 _resize: function()
591 if (this._settingsScreen && this._settingsScreen.isShowing())
592 this._settingsScreen.doResize();
597 * @constructor
598 * @implements {WebInspector.ActionDelegate}
600 WebInspector.SettingsController.ActionDelegate = function() { }
602 WebInspector.SettingsController.ActionDelegate.prototype = {
604 * @override
605 * @param {!WebInspector.Context} context
606 * @param {string} actionId
608 handleAction: function(context, actionId)
610 if (actionId === "settings.show")
611 WebInspector._settingsController.showSettingsScreen();
612 else if (actionId === "settings.help")
613 InspectorFrontendHost.openInNewTab("https://developers.google.com/web/tools/chrome-devtools/");
614 else if (actionId === "settings.shortcuts")
615 WebInspector._settingsController.showSettingsScreen("shortcuts");
620 * @constructor
621 * @implements {WebInspector.Revealer}
623 WebInspector.SettingsController.Revealer = function() { }
625 WebInspector.SettingsController.Revealer.prototype = {
627 * @override
628 * @param {!Object} object
629 * @param {number=} lineNumber
630 * @return {!Promise}
632 reveal: function(object, lineNumber)
634 console.assert(object instanceof WebInspector.Setting);
635 var setting = /** @type {!WebInspector.Setting} */ (object);
636 var success = false;
638 self.runtime.extensions("setting").forEach(revealModuleSetting);
639 self.runtime.extensions(WebInspector.SettingUI).forEach(revealSettingUI);
640 self.runtime.extensions("settings-view").forEach(revealSettingsView);
642 return success ? Promise.resolve() : Promise.reject();
645 * @param {!Runtime.Extension} extension
647 function revealModuleSetting(extension)
649 if (!WebInspector.GenericSettingsTab.isSettingVisible(extension))
650 return;
651 if (extension.descriptor()["settingName"] === setting.name) {
652 WebInspector._settingsController.showSettingsScreen("general");
653 success = true;
658 * @param {!Runtime.Extension} extension
660 function revealSettingUI(extension)
662 var settings = extension.descriptor()["settings"];
663 if (settings && settings.indexOf(setting.name) !== -1) {
664 WebInspector._settingsController.showSettingsScreen("general");
665 success = true;
670 * @param {!Runtime.Extension} extension
672 function revealSettingsView(extension)
674 var settings = extension.descriptor()["settings"];
675 if (settings && settings.indexOf(setting.name) !== -1) {
676 WebInspector._settingsController.showSettingsScreen(extension.descriptor()["name"]);
677 success = true;
684 * @constructor
685 * @extends {WebInspector.Object}
686 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns
687 * @param {function(!Element, {id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}, ?string)} itemRenderer
689 WebInspector.SettingsList = function(columns, itemRenderer)
691 this.element = createElementWithClass("div", "settings-list");
692 this.element.tabIndex = -1;
693 this._itemRenderer = itemRenderer;
694 /** @type {!Map.<string, !Element>} */
695 this._listItems = new Map();
696 /** @type {!Array.<?string>} */
697 this._ids = [];
698 this._columns = columns;
701 WebInspector.SettingsList.Events = {
702 Selected: "Selected",
703 Removed: "Removed",
704 DoubleClicked: "DoubleClicked",
707 WebInspector.SettingsList.prototype = {
709 * @param {?string} itemId
710 * @param {?string=} beforeId
711 * @return {!Element}
713 addItem: function(itemId, beforeId)
715 var listItem = createElementWithClass("div", "settings-list-item");
716 listItem._id = itemId;
717 if (typeof beforeId !== "undefined")
718 this.element.insertBefore(listItem, this.itemForId(beforeId));
719 else
720 this.element.appendChild(listItem);
722 var listItemContents = listItem.createChild("div", "settings-list-item-contents");
723 var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns");
725 listItem.columnElements = {};
726 for (var i = 0; i < this._columns.length; ++i) {
727 var column = this._columns[i];
728 var columnElement = listItemColumnsElement.createChild("div", "list-column settings-list-column-" + column.id);
729 listItem.columnElements[column.id] = columnElement;
730 this._itemRenderer(columnElement, column, itemId);
732 var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this));
733 listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false);
734 listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false);
735 listItemContents.appendChild(removeItemButton);
737 this._listItems.set(itemId || "", listItem);
738 if (typeof beforeId !== "undefined")
739 this._ids.splice(this._ids.indexOf(beforeId), 0, itemId);
740 else
741 this._ids.push(itemId);
744 * @param {!Event} event
745 * @this {WebInspector.SettingsList}
747 function removeItemClicked(event)
749 removeItemButton.disabled = true;
750 this.removeItem(itemId);
751 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId);
752 event.consume();
755 return listItem;
759 * @param {?string} id
761 removeItem: function(id)
763 var listItem = this._listItems.remove(id || "");
764 if (listItem)
765 listItem.remove();
766 this._ids.remove(id);
767 if (id === this._selectedId) {
768 delete this._selectedId;
769 if (this._ids.length)
770 this.selectItem(this._ids[0]);
775 * @return {!Array.<?string>}
777 itemIds: function()
779 return this._ids.slice();
783 * @return {!Array.<string>}
785 columns: function()
787 return this._columns.select("id");
791 * @return {?string}
793 selectedId: function()
795 return this._selectedId;
799 * @return {?Element}
801 selectedItem: function()
803 return this._selectedId ? this.itemForId(this._selectedId) : null;
807 * @param {?string} itemId
808 * @return {?Element}
810 itemForId: function(itemId)
812 return this._listItems.get(itemId || "") || null;
816 * @param {?string} id
817 * @param {!Event=} event
819 _onDoubleClick: function(id, event)
821 this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id);
825 * @param {?string} id
826 * @param {!Event=} event
828 selectItem: function(id, event)
830 if (typeof this._selectedId !== "undefined")
831 this.itemForId(this._selectedId).classList.remove("selected");
833 this._selectedId = id;
834 if (typeof this._selectedId !== "undefined")
835 this.itemForId(this._selectedId).classList.add("selected");
837 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id);
838 if (event)
839 event.consume();
843 * @param {function(!Event)} handler
844 * @return {!Element}
846 _createRemoveButton: function(handler)
848 var removeButton = createElementWithClass("div", "remove-item-button");
849 removeButton.addEventListener("click", handler, false);
850 return removeButton;
853 __proto__: WebInspector.Object.prototype
857 * @constructor
858 * @extends {WebInspector.SettingsList}
859 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns
860 * @param {function(string, string):string} valuesProvider
861 * @param {function(?string, !Object):!Array.<string>} validateHandler
862 * @param {function(?string, !Object)} editHandler
864 WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler)
866 WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this));
867 this._valuesProvider = valuesProvider;
868 this._validateHandler = validateHandler;
869 this._editHandler = editHandler;
870 /** @type {!Map.<string, (!HTMLInputElement|!HTMLSelectElement)>} */
871 this._addInputElements = new Map();
872 /** @type {!Map.<string, !Map.<string, (!HTMLInputElement|!HTMLSelectElement)>>} */
873 this._editInputElements = new Map();
874 /** @type {!Map.<string, !Map.<string, !HTMLSpanElement>>} */
875 this._textElements = new Map();
877 this._addMappingItem = this.addItem(null);
878 this._addMappingItem.classList.add("item-editing", "add-list-item");
881 WebInspector.EditableSettingsList.prototype = {
883 * @override
884 * @param {?string} itemId
885 * @param {?string=} beforeId
886 * @return {!Element}
888 addItem: function(itemId, beforeId)
890 var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId);
891 listItem.classList.add("editable");
892 return listItem;
896 * @param {?string} itemId
898 refreshItem: function(itemId)
900 if (!itemId)
901 return;
902 var listItem = this.itemForId(itemId);
903 if (!listItem)
904 return;
905 for (var i = 0; i < this._columns.length; ++i) {
906 var column = this._columns[i];
907 var columnId = column.id;
909 var value = this._valuesProvider(itemId, columnId);
910 this._setTextElementContent(itemId, columnId, value);
912 var editElement = this._editInputElements.get(itemId).get(columnId);
913 this._setEditElementValue(editElement, value || "");
918 * @param {?string} itemId
919 * @param {string} columnId
921 _textElementContent: function(itemId, columnId)
923 if (!itemId)
924 return "";
925 return this._textElements.get(itemId).get(columnId).textContent.replace(/\u200B/g, "");
929 * @param {string} itemId
930 * @param {string} columnId
931 * @param {string} text
933 _setTextElementContent: function(itemId, columnId, text)
935 var textElement = this._textElements.get(itemId).get(columnId);
936 textElement.textContent = text.replace(/.{4}/g, "$&\u200B");
937 textElement.title = text;
941 * @param {!Element} columnElement
942 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
943 * @param {?string} itemId
945 _renderColumn: function(columnElement, column, itemId)
947 var columnId = column.id;
948 if (itemId === null) {
949 this._createEditElement(columnElement, column, itemId);
950 return;
952 var validItemId = itemId;
954 if (!this._editInputElements.has(itemId))
955 this._editInputElements.set(itemId, new Map());
956 if (!this._textElements.has(itemId))
957 this._textElements.set(itemId, new Map());
959 var value = this._valuesProvider(itemId, columnId);
961 var textElement = /** @type {!HTMLSpanElement} */ (columnElement.createChild("span", "list-column-text"));
962 columnElement.addEventListener("click", rowClicked.bind(this), false);
963 this._textElements.get(itemId).set(columnId, textElement);
964 this._setTextElementContent(itemId, columnId, value);
966 this._createEditElement(columnElement, column, itemId, value);
969 * @param {!Event} event
970 * @this {WebInspector.EditableSettingsList}
972 function rowClicked(event)
974 if (itemId === this._editingId)
975 return;
976 console.assert(!this._editingId);
977 this._editingId = validItemId;
978 var listItem = this.itemForId(validItemId);
979 listItem.classList.add("item-editing");
980 var editElement = event.target.editElement || this._editInputElements.get(validItemId).get(this.columns()[0]);
981 editElement.focus();
982 if (editElement.select)
983 editElement.select();
988 * @param {!Element} columnElement
989 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
990 * @param {?string} itemId
991 * @param {string=} value
992 * @return {!Element}
994 _createEditElement: function(columnElement, column, itemId, value)
996 var options = column.options;
997 if (options) {
998 var editElement = /** @type {!HTMLSelectElement} */ (columnElement.createChild("select", "chrome-select list-column-editor"));
999 for (var i = 0; i < options.length; ++i) {
1000 var option = editElement.createChild("option");
1001 option.value = options[i];
1002 option.textContent = options[i];
1004 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false);
1005 editElement.addEventListener("change", this._editMappingBlur.bind(this, itemId), false);
1006 } else {
1007 var editElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor"));
1008 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false);
1009 editElement.addEventListener("input", this._validateEdit.bind(this, itemId), false);
1010 if (itemId === null)
1011 editElement.placeholder = column.placeholder || "";
1014 if (itemId === null)
1015 this._addInputElements.set(column.id, editElement);
1016 else
1017 this._editInputElements.get(itemId).set(column.id, editElement);
1019 this._setEditElementValue(editElement, value || "");
1020 columnElement.editElement = editElement;
1021 return editElement;
1025 * @param {!HTMLInputElement|!HTMLSelectElement|undefined} editElement
1026 * @param {string} value
1028 _setEditElementValue: function(editElement, value)
1030 if (!editElement)
1031 return;
1032 if (editElement instanceof HTMLSelectElement) {
1033 var options = editElement.options;
1034 for (var i = 0; i < options.length; ++i)
1035 options[i].selected = (options[i].value === value);
1036 } else {
1037 editElement.value = value;
1042 * @param {?string} itemId
1043 * @return {!Object}
1045 _data: function(itemId)
1047 var inputElements = this._inputElements(itemId);
1048 var data = { __proto__: null };
1049 var columns = this.columns();
1050 for (var i = 0; i < columns.length; ++i)
1051 data[columns[i]] = inputElements.get(columns[i]).value;
1052 return data;
1056 * @param {?string} itemId
1057 * @return {?Map.<string, (!HTMLInputElement|!HTMLSelectElement)>}
1059 _inputElements: function(itemId)
1061 if (!itemId)
1062 return this._addInputElements;
1063 return this._editInputElements.get(itemId) || null;
1067 * @param {?string} itemId
1068 * @return {boolean}
1070 _validateEdit: function(itemId)
1072 var errorColumns = this._validateHandler(itemId, this._data(itemId));
1073 var hasChanges = this._hasChanges(itemId);
1074 var columns = this.columns();
1075 for (var i = 0; i < columns.length; ++i) {
1076 var columnId = columns[i];
1077 var inputElement = this._inputElements(itemId).get(columnId);
1078 if (hasChanges && errorColumns.indexOf(columnId) !== -1)
1079 inputElement.classList.add("editable-item-error");
1080 else
1081 inputElement.classList.remove("editable-item-error");
1083 return !errorColumns.length;
1087 * @param {?string} itemId
1088 * @return {boolean}
1090 _hasChanges: function(itemId)
1092 var columns = this.columns();
1093 for (var i = 0; i < columns.length; ++i) {
1094 var columnId = columns[i];
1095 var oldValue = this._textElementContent(itemId, columnId);
1096 var newValue = this._inputElements(itemId).get(columnId).value;
1097 if (oldValue !== newValue)
1098 return true;
1100 return false;
1104 * @param {?string} itemId
1105 * @param {!Event} event
1107 _editMappingBlur: function(itemId, event)
1109 if (itemId === null) {
1110 this._onAddMappingInputBlur(event);
1111 return;
1114 var inputElements = this._editInputElements.get(itemId).valuesArray();
1115 if (inputElements.indexOf(event.relatedTarget) !== -1)
1116 return;
1118 var listItem = this.itemForId(itemId);
1119 listItem.classList.remove("item-editing");
1120 delete this._editingId;
1122 if (!this._hasChanges(itemId))
1123 return;
1125 if (!this._validateEdit(itemId)) {
1126 var columns = this.columns();
1127 for (var i = 0; i < columns.length; ++i) {
1128 var columnId = columns[i];
1129 var editElement = this._editInputElements.get(itemId).get(columnId);
1130 this._setEditElementValue(editElement, this._textElementContent(itemId, columnId));
1131 editElement.classList.remove("editable-item-error");
1133 return;
1135 this._editHandler(itemId, this._data(itemId));
1139 * @param {!Event} event
1141 _onAddMappingInputBlur: function(event)
1143 var inputElements = this._addInputElements.valuesArray();
1144 if (inputElements.indexOf(event.relatedTarget) !== -1)
1145 return;
1147 if (!this._hasChanges(null))
1148 return;
1150 if (!this._validateEdit(null))
1151 return;
1153 this._editHandler(null, this._data(null));
1154 var columns = this.columns();
1155 for (var i = 0; i < columns.length; ++i) {
1156 var columnId = columns[i];
1157 var editElement = this._addInputElements.get(columnId);
1158 this._setEditElementValue(editElement, "");
1162 __proto__: WebInspector.SettingsList.prototype
1165 WebInspector._settingsController = new WebInspector.SettingsController();