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
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
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.
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._tabbedPane = new WebInspector.TabbedPane();
45 this._tabbedPane.element.classList.add("help-window-main");
46 var settingsLabelElement = document.createElementWithClass("div", "help-window-label");
47 settingsLabelElement.createTextChild(WebInspector.UIString("Settings"));
48 this._tabbedPane.element.insertBefore(settingsLabelElement, this._tabbedPane.element.firstChild);
49 this._tabbedPane.element.appendChild(this._createCloseButton());
50 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.General, WebInspector.UIString("General"), new WebInspector.GenericSettingsTab());
51 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Workspace, WebInspector.UIString("Workspace"), new WebInspector.WorkspaceSettingsTab());
52 if (WebInspector.experimentsSettings.experimentsEnabled)
53 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Experiments, WebInspector.UIString("Experiments"), new WebInspector.ExperimentsSettingsTab());
54 this._tabbedPane.appendTab(WebInspector.SettingsScreen.Tabs.Shortcuts, WebInspector.UIString("Shortcuts"), WebInspector.shortcutsScreen.createShortcutsTabView());
55 this._tabbedPane.shrinkableTabs = false;
56 this._tabbedPane.verticalTabLayout = true;
58 this._lastSelectedTabSetting = WebInspector.settings.createSetting("lastSelectedSettingsTab", WebInspector.SettingsScreen.Tabs.General);
59 this.selectTab(this._lastSelectedTabSetting.get());
60 this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
61 this.element.addEventListener("keydown", this._keyDown.bind(this), false);
62 this._developerModeCounter = 0;
68 * @param {string} text
71 WebInspector.SettingsScreen.integerValidator = function(min, max, text)
73 var value = Number(text);
75 return WebInspector.UIString("Invalid number format");
76 if (value < min || value > max)
77 return WebInspector.UIString("Value is out of range [%d, %d]", min, max);
81 WebInspector.SettingsScreen.Tabs = {
83 Overrides: "overrides",
84 Workspace: "workspace",
85 Experiments: "experiments",
86 Shortcuts: "shortcuts"
89 WebInspector.SettingsScreen.prototype = {
91 * @param {string} tabId
93 selectTab: function(tabId)
95 this._tabbedPane.selectTab(tabId);
99 * @param {!WebInspector.Event} event
101 _tabSelected: function(event)
103 this._lastSelectedTabSetting.set(this._tabbedPane.selectedTabId);
111 this._tabbedPane.show(this.element);
112 WebInspector.HelpScreen.prototype.wasShown.call(this);
119 isClosingKey: function(keyCode)
122 WebInspector.KeyboardShortcut.Keys.Enter.code,
123 WebInspector.KeyboardShortcut.Keys.Esc.code,
124 ].indexOf(keyCode) >= 0;
133 WebInspector.HelpScreen.prototype.willHide.call(this);
137 * @param {!Event} event
139 _keyDown: function(event)
141 var shiftKeyCode = 16;
142 if (event.keyCode === shiftKeyCode && ++this._developerModeCounter > 5)
143 this.element.classList.add("settings-developer-mode");
146 __proto__: WebInspector.HelpScreen.prototype
151 * @extends {WebInspector.VBox}
152 * @param {string} name
153 * @param {string=} id
155 WebInspector.SettingsTab = function(name, id)
157 WebInspector.VBox.call(this);
158 this.element.classList.add("settings-tab-container");
160 this.element.id = id;
161 var header = this.element.createChild("header");
162 header.createChild("h3").createTextChild(name);
163 this.containerElement = this.element.createChild("div", "help-container-wrapper").createChild("div", "settings-tab help-content help-container");
166 WebInspector.SettingsTab.prototype = {
168 * @param {string=} name
171 _appendSection: function(name)
173 var block = this.containerElement.createChild("div", "help-block");
175 block.createChild("div", "help-section-title").textContent = name;
179 _createSelectSetting: function(name, options, setting)
181 var p = document.createElement("p");
182 p.createChild("label").textContent = name;
184 var select = p.createChild("select", "chrome-select");
185 var settingValue = setting.get();
187 for (var i = 0; i < options.length; ++i) {
188 var option = options[i];
189 select.add(new Option(option[0], option[1]));
190 if (settingValue === option[1])
191 select.selectedIndex = i;
194 function changeListener(e)
196 // Don't use e.target.value to avoid conversion of the value to string.
197 setting.set(options[select.selectedIndex][1]);
200 select.addEventListener("change", changeListener, false);
204 __proto__: WebInspector.VBox.prototype
209 * @extends {WebInspector.SettingsTab}
211 WebInspector.GenericSettingsTab = function()
213 WebInspector.SettingsTab.call(this, WebInspector.UIString("General"), "general-tab-content");
215 this._populateSectionsFromExtensions();
217 var restoreDefaults = this._appendSection().createChild("input", "text-button");
218 restoreDefaults.type = "button";
219 restoreDefaults.value = WebInspector.UIString("Restore defaults and reload");
220 restoreDefaults.addEventListener("click", restoreAndReload, false);
222 function restoreAndReload()
224 if (window.localStorage)
225 window.localStorage.clear();
226 WebInspector.reload();
230 WebInspector.GenericSettingsTab.prototype = {
231 _populateSectionsFromExtensions: function()
234 var explicitSectionOrder = ["", "Appearance", "Elements", "Sources", "Profiler", "Console", "Extensions"];
236 var allExtensions = self.runtime.extensions("ui-setting");
238 /** @type {!StringMultimap.<!Runtime.Extension>} */
239 var extensionsBySectionId = new StringMultimap();
240 /** @type {!StringMultimap.<!Runtime.Extension>} */
241 var childSettingExtensionsByParentName = new StringMultimap();
243 allExtensions.forEach(function(extension) {
244 var descriptor = extension.descriptor();
245 var sectionName = descriptor["section"] || "";
246 if (!sectionName && descriptor["parentSettingName"]) {
247 childSettingExtensionsByParentName.put(descriptor["parentSettingName"], extension);
250 extensionsBySectionId.put(sectionName, extension);
253 var sectionIds = extensionsBySectionId.keys();
254 var explicitlyOrderedSections = explicitSectionOrder.keySet();
255 for (var i = 0; i < explicitSectionOrder.length; ++i) {
256 var extensions = extensionsBySectionId.get(explicitSectionOrder[i]);
257 if (!extensions.size())
259 this._addSectionWithExtensionProvidedSettings(explicitSectionOrder[i], extensions.values(), childSettingExtensionsByParentName);
261 for (var i = 0; i < sectionIds.length; ++i) {
262 if (explicitlyOrderedSections[sectionIds[i]])
264 this._addSectionWithExtensionProvidedSettings(sectionIds[i], extensionsBySectionId.get(sectionIds[i]).values(), childSettingExtensionsByParentName);
269 * @param {string} sectionName
270 * @param {!Array.<!Runtime.Extension>} extensions
271 * @param {!StringMultimap.<!Runtime.Extension>} childSettingExtensionsByParentName
273 _addSectionWithExtensionProvidedSettings: function(sectionName, extensions, childSettingExtensionsByParentName)
275 var uiSectionName = sectionName && WebInspector.UIString(sectionName);
276 var sectionElement = this._appendSection(uiSectionName);
277 extensions.forEach(processSetting.bind(this, null));
280 * @param {?Element} parentFieldset
281 * @param {!Runtime.Extension} extension
282 * @this {WebInspector.GenericSettingsTab}
284 function processSetting(parentFieldset, extension)
286 var descriptor = extension.descriptor();
287 var experimentName = descriptor["experiment"];
288 if (experimentName && (!WebInspector.experimentsSettings[experimentName] || !WebInspector.experimentsSettings[experimentName].isEnabled()))
291 var settingName = descriptor["settingName"];
292 var setting = WebInspector.settings[settingName];
293 var instance = extension.instance();
295 if (instance && descriptor["settingType"] === "custom") {
296 settingControl = instance.settingElement();
300 if (!settingControl) {
301 var uiTitle = WebInspector.UIString(descriptor["title"]);
302 settingControl = createSettingControl.call(this, uiTitle, setting, descriptor, instance);
305 var childSettings = childSettingExtensionsByParentName.get(settingName);
306 if (childSettings.size()) {
307 var fieldSet = WebInspector.SettingsUI.createSettingFieldset(setting);
308 settingControl.appendChild(fieldSet);
309 childSettings.values().forEach(function(item) { processSetting.call(this, fieldSet, item); }, this);
312 var containerElement = parentFieldset || sectionElement;
313 containerElement.appendChild(settingControl);
317 * @param {string} uiTitle
318 * @param {!WebInspector.Setting} setting
319 * @param {!Object} descriptor
320 * @param {?Object} instance
322 * @this {WebInspector.GenericSettingsTab}
324 function createSettingControl(uiTitle, setting, descriptor, instance)
326 switch (descriptor["settingType"]) {
328 return WebInspector.SettingsUI.createSettingCheckbox(uiTitle, setting);
330 var descriptorOptions = descriptor["options"]
331 var options = new Array(descriptorOptions.length);
332 for (var i = 0; i < options.length; ++i) {
333 // The third array item flags that the option name is "raw" (non-i18n-izable).
334 var optionName = descriptorOptions[i][2] ? descriptorOptions[i][0] : WebInspector.UIString(descriptorOptions[i][0]);
335 options[i] = [WebInspector.UIString(descriptorOptions[i][0]), descriptorOptions[i][1]];
337 return this._createSelectSetting(uiTitle, options, setting);
339 throw "Invalid setting type: " + descriptor["settingType"];
344 __proto__: WebInspector.SettingsTab.prototype
349 * @extends {WebInspector.UISettingDelegate}
351 WebInspector.SettingsScreen.SkipStackFramePatternSettingDelegate = function()
353 WebInspector.UISettingDelegate.call(this);
356 WebInspector.SettingsScreen.SkipStackFramePatternSettingDelegate.prototype = {
361 settingElement: function()
363 var button = document.createElementWithClass("input", "text-button");
364 button.type = "button";
365 button.value = WebInspector.manageBlackboxingButtonLabel();
366 button.title = WebInspector.UIString("Skip stepping through sources with particular names");
367 button.addEventListener("click", this._onManageButtonClick.bind(this), false);
371 _onManageButtonClick: function()
373 WebInspector.FrameworkBlackboxDialog.show(WebInspector.inspectorView.element);
376 __proto__: WebInspector.UISettingDelegate.prototype
381 * @extends {WebInspector.SettingsTab}
383 WebInspector.WorkspaceSettingsTab = function()
385 WebInspector.SettingsTab.call(this, WebInspector.UIString("Workspace"), "workspace-tab-content");
386 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemAdded, this._fileSystemAdded, this);
387 WebInspector.isolatedFileSystemManager.addEventListener(WebInspector.IsolatedFileSystemManager.Events.FileSystemRemoved, this._fileSystemRemoved, this);
389 this._commonSection = this._appendSection(WebInspector.UIString("Common"));
390 var folderExcludePatternInput = WebInspector.SettingsUI.createSettingInputField(WebInspector.UIString("Folder exclude pattern"), WebInspector.settings.workspaceFolderExcludePattern, false, 0, "270px", WebInspector.SettingsUI.regexValidator);
391 this._commonSection.appendChild(folderExcludePatternInput);
393 this._fileSystemsSection = this._appendSection(WebInspector.UIString("Folders"));
394 this._fileSystemsListContainer = this._fileSystemsSection.createChild("p", "settings-list-container");
396 this._addFileSystemRowElement = this._fileSystemsSection.createChild("div");
397 var addFileSystemButton = this._addFileSystemRowElement.createChild("input", "text-button");
398 addFileSystemButton.type = "button";
399 addFileSystemButton.value = WebInspector.UIString("Add folder\u2026");
400 addFileSystemButton.addEventListener("click", this._addFileSystemClicked.bind(this), false);
402 this._editFileSystemButton = this._addFileSystemRowElement.createChild("input", "text-button");
403 this._editFileSystemButton.type = "button";
404 this._editFileSystemButton.value = WebInspector.UIString("Folder options\u2026");
405 this._editFileSystemButton.addEventListener("click", this._editFileSystemClicked.bind(this), false);
406 this._updateEditFileSystemButtonState();
411 WebInspector.WorkspaceSettingsTab.prototype = {
414 WebInspector.SettingsTab.prototype.wasShown.call(this);
420 this._resetFileSystems();
423 _resetFileSystems: function()
425 this._fileSystemsListContainer.removeChildren();
426 var fileSystemPaths = WebInspector.isolatedFileSystemManager.mapping().fileSystemPaths();
427 delete this._fileSystemsList;
429 if (!fileSystemPaths.length) {
430 var noFileSystemsMessageElement = this._fileSystemsListContainer.createChild("div", "no-file-systems-message");
431 noFileSystemsMessageElement.textContent = WebInspector.UIString("You have no file systems added.");
435 this._fileSystemsList = new WebInspector.SettingsList([{ id: "path" }], this._renderFileSystem.bind(this));
436 this._fileSystemsList.element.classList.add("file-systems-list");
437 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Selected, this._fileSystemSelected.bind(this));
438 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.Removed, this._fileSystemRemovedfromList.bind(this));
439 this._fileSystemsList.addEventListener(WebInspector.SettingsList.Events.DoubleClicked, this._fileSystemDoubleClicked.bind(this));
440 this._fileSystemsListContainer.appendChild(this._fileSystemsList.element);
441 for (var i = 0; i < fileSystemPaths.length; ++i)
442 this._fileSystemsList.addItem(fileSystemPaths[i]);
443 this._updateEditFileSystemButtonState();
446 _updateEditFileSystemButtonState: function()
448 this._editFileSystemButton.disabled = !this._selectedFileSystemPath();
452 * @param {!WebInspector.Event} event
454 _fileSystemSelected: function(event)
456 this._updateEditFileSystemButtonState();
460 * @param {!WebInspector.Event} event
462 _fileSystemDoubleClicked: function(event)
464 var id = /** @type{?string} */ (event.data);
465 this._editFileSystem(id);
468 _editFileSystemClicked: function()
470 this._editFileSystem(this._selectedFileSystemPath());
474 * @param {?string} id
476 _editFileSystem: function(id)
478 WebInspector.EditFileSystemDialog.show(WebInspector.inspectorView.element, id);
482 * @param {!Element} columnElement
483 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
484 * @param {?string} id
486 _renderFileSystem: function(columnElement, column, id)
490 var fileSystemPath = id;
491 var textElement = columnElement.createChild("span", "list-column-text");
492 var pathElement = textElement.createChild("span", "file-system-path");
493 pathElement.title = fileSystemPath;
495 const maxTotalPathLength = 55;
496 const maxFolderNameLength = 30;
498 var lastIndexOfSlash = fileSystemPath.lastIndexOf(WebInspector.isWin() ? "\\" : "/");
499 var folderName = fileSystemPath.substr(lastIndexOfSlash + 1);
500 var folderPath = fileSystemPath.substr(0, lastIndexOfSlash + 1);
501 folderPath = folderPath.trimMiddle(maxTotalPathLength - Math.min(maxFolderNameLength, folderName.length));
502 folderName = folderName.trimMiddle(maxFolderNameLength);
504 var folderPathElement = pathElement.createChild("span");
505 folderPathElement.textContent = folderPath;
507 var nameElement = pathElement.createChild("span", "file-system-path-name");
508 nameElement.textContent = folderName;
512 * @param {!WebInspector.Event} event
514 _fileSystemRemovedfromList: function(event)
516 var id = /** @type{?string} */ (event.data);
519 WebInspector.isolatedFileSystemManager.removeFileSystem(id);
522 _addFileSystemClicked: function()
524 WebInspector.isolatedFileSystemManager.addFileSystem();
527 _fileSystemAdded: function(event)
529 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
530 if (!this._fileSystemsList)
533 this._fileSystemsList.addItem(fileSystem.path());
536 _fileSystemRemoved: function(event)
538 var fileSystem = /** @type {!WebInspector.IsolatedFileSystem} */ (event.data);
539 var selectedFileSystemPath = this._selectedFileSystemPath();
540 if (this._fileSystemsList.itemForId(fileSystem.path()))
541 this._fileSystemsList.removeItem(fileSystem.path());
542 if (!this._fileSystemsList.itemIds().length)
544 this._updateEditFileSystemButtonState();
547 _selectedFileSystemPath: function()
549 return this._fileSystemsList ? this._fileSystemsList.selectedId() : null;
552 __proto__: WebInspector.SettingsTab.prototype
558 * @extends {WebInspector.SettingsTab}
560 WebInspector.ExperimentsSettingsTab = function()
562 WebInspector.SettingsTab.call(this, WebInspector.UIString("Experiments"), "experiments-tab-content");
564 var experiments = WebInspector.experimentsSettings.experiments;
565 if (experiments.length) {
566 var experimentsSection = this._appendSection();
567 experimentsSection.appendChild(this._createExperimentsWarningSubsection());
568 for (var i = 0; i < experiments.length; ++i)
569 experimentsSection.appendChild(this._createExperimentCheckbox(experiments[i]));
573 WebInspector.ExperimentsSettingsTab.prototype = {
575 * @return {!Element} element
577 _createExperimentsWarningSubsection: function()
579 var subsection = document.createElement("div");
580 var warning = subsection.createChild("span", "settings-experiments-warning-subsection-warning");
581 warning.textContent = WebInspector.UIString("WARNING:");
582 subsection.createTextChild(" ");
583 var message = subsection.createChild("span", "settings-experiments-warning-subsection-message");
584 message.textContent = WebInspector.UIString("These experiments could be dangerous and may require restart.");
588 _createExperimentCheckbox: function(experiment)
590 var input = document.createElement("input");
591 input.type = "checkbox";
592 input.name = experiment.name;
593 input.checked = experiment.isEnabled();
596 experiment.setEnabled(input.checked);
598 input.addEventListener("click", listener, false);
600 var p = document.createElement("p");
601 p.className = experiment.hidden && !experiment.isEnabled() ? "settings-experiment-hidden" : "";
602 var label = p.createChild("label");
603 label.appendChild(input);
604 label.createTextChild(WebInspector.UIString(experiment.title));
605 p.appendChild(label);
609 __proto__: WebInspector.SettingsTab.prototype
615 WebInspector.SettingsController = function()
617 /** @type {?WebInspector.SettingsScreen} */
618 this._settingsScreen;
620 window.addEventListener("resize", this._resize.bind(this), false);
623 WebInspector.SettingsController.prototype = {
624 _onHideSettingsScreen: function()
626 delete this._settingsScreenVisible;
630 * @param {string=} tabId
632 showSettingsScreen: function(tabId)
634 if (!this._settingsScreen)
635 this._settingsScreen = new WebInspector.SettingsScreen(this._onHideSettingsScreen.bind(this));
638 this._settingsScreen.selectTab(tabId);
640 this._settingsScreen.showModal();
641 this._settingsScreenVisible = true;
646 if (this._settingsScreen && this._settingsScreen.isShowing())
647 this._settingsScreen.doResize();
653 * @implements {WebInspector.ActionDelegate}
655 WebInspector.SettingsController.SettingsScreenActionDelegate = function() { }
657 WebInspector.SettingsController.SettingsScreenActionDelegate.prototype = {
661 handleAction: function()
663 WebInspector._settingsController.showSettingsScreen(WebInspector.SettingsScreen.Tabs.General);
670 * @extends {WebInspector.Object}
671 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns
672 * @param {function(!Element, {id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}, ?string)} itemRenderer
674 WebInspector.SettingsList = function(columns, itemRenderer)
676 this.element = document.createElementWithClass("div", "settings-list");
677 this.element.tabIndex = -1;
678 this._itemRenderer = itemRenderer;
679 /** @type {!StringMap.<!Element>} */
680 this._listItems = new StringMap();
681 /** @type {!Array.<?string>} */
683 this._columns = columns;
686 WebInspector.SettingsList.Events = {
687 Selected: "Selected",
689 DoubleClicked: "DoubleClicked",
692 WebInspector.SettingsList.prototype = {
694 * @param {?string} itemId
695 * @param {?string=} beforeId
698 addItem: function(itemId, beforeId)
700 var listItem = document.createElementWithClass("div", "settings-list-item");
701 listItem._id = itemId;
702 if (typeof beforeId !== "undefined")
703 this.element.insertBefore(listItem, this.itemForId(beforeId));
705 this.element.appendChild(listItem);
707 var listItemContents = listItem.createChild("div", "settings-list-item-contents");
708 var listItemColumnsElement = listItemContents.createChild("div", "settings-list-item-columns");
710 listItem.columnElements = {};
711 for (var i = 0; i < this._columns.length; ++i) {
712 var column = this._columns[i];
713 var columnElement = listItemColumnsElement.createChild("div", "list-column settings-list-column-" + column.id);
714 listItem.columnElements[column.id] = columnElement;
715 this._itemRenderer(columnElement, column, itemId);
717 var removeItemButton = this._createRemoveButton(removeItemClicked.bind(this));
718 listItemContents.addEventListener("click", this.selectItem.bind(this, itemId), false);
719 listItemContents.addEventListener("dblclick", this._onDoubleClick.bind(this, itemId), false);
720 listItemContents.appendChild(removeItemButton);
722 this._listItems.put(itemId || "", listItem);
723 if (typeof beforeId !== "undefined")
724 this._ids.splice(this._ids.indexOf(beforeId), 0, itemId);
726 this._ids.push(itemId);
729 * @param {!Event} event
730 * @this {WebInspector.SettingsList}
732 function removeItemClicked(event)
734 removeItemButton.disabled = true;
735 this.removeItem(itemId);
736 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Removed, itemId);
744 * @param {?string} id
746 removeItem: function(id)
748 var listItem = this._listItems.remove(id || "");
751 this._ids.remove(id);
752 if (id === this._selectedId) {
753 delete this._selectedId;
754 if (this._ids.length)
755 this.selectItem(this._ids[0]);
760 * @return {!Array.<?string>}
764 return this._ids.slice();
768 * @return {!Array.<string>}
772 return this._columns.select("id");
778 selectedId: function()
780 return this._selectedId;
786 selectedItem: function()
788 return this._selectedId ? this.itemForId(this._selectedId) : null;
792 * @param {?string} itemId
795 itemForId: function(itemId)
797 return this._listItems.get(itemId || "") || null;
801 * @param {?string} id
802 * @param {!Event=} event
804 _onDoubleClick: function(id, event)
806 this.dispatchEventToListeners(WebInspector.SettingsList.Events.DoubleClicked, id);
810 * @param {?string} id
811 * @param {!Event=} event
813 selectItem: function(id, event)
815 if (typeof this._selectedId !== "undefined")
816 this.itemForId(this._selectedId).classList.remove("selected");
818 this._selectedId = id;
819 if (typeof this._selectedId !== "undefined")
820 this.itemForId(this._selectedId).classList.add("selected");
822 this.dispatchEventToListeners(WebInspector.SettingsList.Events.Selected, id);
828 * @param {function(!Event)} handler
831 _createRemoveButton: function(handler)
833 var removeButton = document.createElementWithClass("div", "remove-item-button");
834 removeButton.addEventListener("click", handler, false);
838 __proto__: WebInspector.Object.prototype
843 * @extends {WebInspector.SettingsList}
844 * @param {!Array.<{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}>} columns
845 * @param {function(string, string):string} valuesProvider
846 * @param {function(?string, !Object):!Array.<string>} validateHandler
847 * @param {function(?string, !Object)} editHandler
849 WebInspector.EditableSettingsList = function(columns, valuesProvider, validateHandler, editHandler)
851 WebInspector.SettingsList.call(this, columns, this._renderColumn.bind(this));
852 this._valuesProvider = valuesProvider;
853 this._validateHandler = validateHandler;
854 this._editHandler = editHandler;
855 /** @type {!StringMap.<(!HTMLInputElement|!HTMLSelectElement)>} */
856 this._addInputElements = new StringMap();
857 /** @type {!StringMap.<!StringMap.<(!HTMLInputElement|!HTMLSelectElement)>>} */
858 this._editInputElements = new StringMap();
859 /** @type {!StringMap.<!StringMap.<!HTMLSpanElement>>} */
860 this._textElements = new StringMap();
862 this._addMappingItem = this.addItem(null);
863 this._addMappingItem.classList.add("item-editing", "add-list-item");
866 WebInspector.EditableSettingsList.prototype = {
868 * @param {?string} itemId
869 * @param {?string=} beforeId
872 addItem: function(itemId, beforeId)
874 var listItem = WebInspector.SettingsList.prototype.addItem.call(this, itemId, beforeId);
875 listItem.classList.add("editable");
880 * @param {?string} itemId
882 refreshItem: function(itemId)
886 var listItem = this.itemForId(itemId);
889 for (var i = 0; i < this._columns.length; ++i) {
890 var column = this._columns[i];
891 var columnId = column.id;
893 var value = this._valuesProvider(itemId, columnId);
894 var textElement = this._textElements.get(itemId).get(columnId);
895 textElement.textContent = value;
896 textElement.title = value;
898 var editElement = this._editInputElements.get(itemId).get(columnId);
899 this._setEditElementValue(editElement, value || "");
904 * @param {!Element} columnElement
905 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
906 * @param {?string} itemId
908 _renderColumn: function(columnElement, column, itemId)
910 var columnId = column.id;
911 if (itemId === null) {
912 this._createEditElement(columnElement, column, itemId);
915 var validItemId = itemId;
917 if (!this._editInputElements.contains(itemId))
918 this._editInputElements.put(itemId, new StringMap());
919 if (!this._textElements.contains(itemId))
920 this._textElements.put(itemId, new StringMap());
922 var value = this._valuesProvider(itemId, columnId);
924 var textElement = /** @type {!HTMLSpanElement} */ (columnElement.createChild("span", "list-column-text"));
925 textElement.textContent = value;
926 textElement.title = value;
927 columnElement.addEventListener("click", rowClicked.bind(this), false);
928 this._textElements.get(itemId).put(columnId, textElement);
930 this._createEditElement(columnElement, column, itemId, value);
933 * @param {!Event} event
934 * @this {WebInspector.EditableSettingsList}
936 function rowClicked(event)
938 if (itemId === this._editingId)
941 console.assert(!this._editingId);
942 this._editingId = validItemId;
943 var listItem = this.itemForId(validItemId);
944 listItem.classList.add("item-editing");
945 var editElement = event.target.editElement || this._editInputElements.get(validItemId).get(this.columns()[0]);
947 if (editElement.select)
948 editElement.select();
953 * @param {!Element} columnElement
954 * @param {{id: string, placeholder: (string|undefined), options: (!Array.<string>|undefined)}} column
955 * @param {?string} itemId
956 * @param {string=} value
959 _createEditElement: function(columnElement, column, itemId, value)
961 var options = column.options;
963 var editElement = /** @type {!HTMLSelectElement} */ (columnElement.createChild("select", "chrome-select list-column-editor"));
964 for (var i = 0; i < options.length; ++i) {
965 var option = editElement.createChild("option");
966 option.value = options[i];
967 option.textContent = options[i];
969 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false);
970 editElement.addEventListener("change", this._editMappingBlur.bind(this, itemId), false);
972 var editElement = /** @type {!HTMLInputElement} */ (columnElement.createChild("input", "list-column-editor"));
973 editElement.addEventListener("blur", this._editMappingBlur.bind(this, itemId), false);
974 editElement.addEventListener("input", this._validateEdit.bind(this, itemId), false);
976 editElement.placeholder = column.placeholder || "";
980 this._addInputElements.put(column.id, editElement);
982 this._editInputElements.get(itemId).put(column.id, editElement);
984 this._setEditElementValue(editElement, value || "");
985 columnElement.editElement = editElement;
990 * @param {!HTMLInputElement|!HTMLSelectElement|undefined} editElement
991 * @param {string} value
993 _setEditElementValue: function(editElement, value)
997 if (editElement instanceof HTMLSelectElement) {
998 var options = editElement.options;
999 for (var i = 0; i < options.length; ++i)
1000 options[i].selected = (options[i].value === value);
1002 editElement.value = value;
1007 * @param {?string} itemId
1010 _data: function(itemId)
1012 var inputElements = this._inputElements(itemId);
1013 var data = { __proto__: null };
1014 var columns = this.columns();
1015 for (var i = 0; i < columns.length; ++i)
1016 data[columns[i]] = inputElements.get(columns[i]).value;
1021 * @param {?string} itemId
1022 * @return {?StringMap.<(!HTMLInputElement|!HTMLSelectElement)>}
1024 _inputElements: function(itemId)
1027 return this._addInputElements;
1028 return this._editInputElements.get(itemId) || null;
1032 * @param {?string} itemId
1035 _validateEdit: function(itemId)
1037 var errorColumns = this._validateHandler(itemId, this._data(itemId));
1038 var hasChanges = this._hasChanges(itemId);
1039 var columns = this.columns();
1040 for (var i = 0; i < columns.length; ++i) {
1041 var columnId = columns[i];
1042 var inputElement = this._inputElements(itemId).get(columnId);
1043 if (hasChanges && errorColumns.indexOf(columnId) !== -1)
1044 inputElement.classList.add("editable-item-error");
1046 inputElement.classList.remove("editable-item-error");
1048 return !errorColumns.length;
1052 * @param {?string} itemId
1055 _hasChanges: function(itemId)
1057 var columns = this.columns();
1058 for (var i = 0; i < columns.length; ++i) {
1059 var columnId = columns[i];
1060 var oldValue = itemId ? this._textElements.get(itemId).get(columnId).textContent : "";
1061 var newValue = this._inputElements(itemId).get(columnId).value;
1062 if (oldValue !== newValue)
1069 * @param {?string} itemId
1070 * @param {!Event} event
1072 _editMappingBlur: function(itemId, event)
1074 if (itemId === null) {
1075 this._onAddMappingInputBlur(event);
1079 var inputElements = this._editInputElements.get(itemId).values();
1080 if (inputElements.indexOf(event.relatedTarget) !== -1)
1083 var listItem = this.itemForId(itemId);
1084 listItem.classList.remove("item-editing");
1085 delete this._editingId;
1087 if (!this._hasChanges(itemId))
1090 if (!this._validateEdit(itemId)) {
1091 var columns = this.columns();
1092 for (var i = 0; i < columns.length; ++i) {
1093 var columnId = columns[i];
1094 var editElement = this._editInputElements.get(itemId).get(columnId);
1095 this._setEditElementValue(editElement, this._textElements.get(itemId).get(columnId).textContent);
1096 editElement.classList.remove("editable-item-error");
1100 this._editHandler(itemId, this._data(itemId));
1104 * @param {!Event} event
1106 _onAddMappingInputBlur: function(event)
1108 var inputElements = this._addInputElements.values();
1109 if (inputElements.indexOf(event.relatedTarget) !== -1)
1112 if (!this._hasChanges(null))
1115 if (!this._validateEdit(null))
1118 this._editHandler(null, this._data(null));
1119 var columns = this.columns();
1120 for (var i = 0; i < columns.length; ++i) {
1121 var columnId = columns[i];
1122 var editElement = this._addInputElements.get(columnId);
1123 this._setEditElementValue(editElement, "");
1127 __proto__: WebInspector.SettingsList.prototype
1130 WebInspector._settingsController = new WebInspector.SettingsController();