/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview A UI for editing tweak settings / clicking tweak actions.
*/
goog.provide('goog.tweak.EntriesPanel');
goog.provide('goog.tweak.TweakUi');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.safe');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeStyleSheet');
goog.require('goog.object');
goog.require('goog.string.Const');
goog.require('goog.style');
goog.require('goog.tweak');
goog.require('goog.tweak.BaseEntry');
goog.require('goog.tweak.BooleanGroup');
goog.require('goog.tweak.BooleanInGroupSetting');
goog.require('goog.tweak.BooleanSetting');
goog.require('goog.tweak.ButtonAction');
goog.require('goog.tweak.NumericSetting');
goog.require('goog.tweak.StringSetting');
goog.require('goog.ui.Zippy');
goog.require('goog.userAgent');
goog.requireType('goog.tweak.Registry');
/**
* A UI for editing tweak settings / clicking tweak actions.
* @param {!goog.tweak.Registry} registry The registry to render.
* @param {goog.dom.DomHelper=} opt_domHelper The DomHelper to render with.
* @constructor
* @final
*/
goog.tweak.TweakUi = function(registry, opt_domHelper) {
'use strict';
/**
* The registry to create a UI from.
* @type {!goog.tweak.Registry}
* @private
*/
this.registry_ = registry;
/**
* The element to display when the UI is visible.
* @type {goog.tweak.EntriesPanel|undefined}
* @private
*/
this.entriesPanel_;
/**
* The DomHelper to render with.
* @type {!goog.dom.DomHelper}
* @private
*/
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
// Listen for newly registered entries (happens with lazy-loaded modules).
registry.addOnRegisterListener(goog.bind(this.onNewRegisteredEntry_, this));
};
/**
* The CSS class name unique to the root tweak panel div.
* @type {string}
* @private
*/
goog.tweak.TweakUi.ROOT_PANEL_CLASS_ = goog.getCssName('goog-tweak-root');
/**
* The CSS class name unique to the tweak entry div.
* @type {string}
* @private
*/
goog.tweak.TweakUi.ENTRY_CSS_CLASS_ = goog.getCssName('goog-tweak-entry');
/**
* The CSS classes for each tweak entry div.
* @type {string}
* @private
*/
goog.tweak.TweakUi.ENTRY_CSS_CLASSES_ = goog.tweak.TweakUi.ENTRY_CSS_CLASS_ +
' ' + goog.getCssName('goog-inline-block');
/**
* The CSS classes for each namespace tweak entry div.
* @type {string}
* @private
*/
goog.tweak.TweakUi.ENTRY_GROUP_CSS_CLASSES_ =
goog.tweak.TweakUi.ENTRY_CSS_CLASS_;
/**
* Marker that the style sheet has already been installed.
* @type {string}
* @private
*/
goog.tweak.TweakUi.STYLE_SHEET_INSTALLED_MARKER_ = '__closure_tweak_installed_';
/**
* CSS used by TweakUI.
* @type {!goog.html.SafeStyleSheet}
* @private
*/
goog.tweak.TweakUi.CSS_STYLES_ = (function() {
'use strict';
var MOBILE = goog.userAgent.MOBILE;
var IE = goog.userAgent.IE;
var ROOT_PANEL_CLASS = '.' + goog.tweak.TweakUi.ROOT_PANEL_CLASS_;
var GOOG_INLINE_BLOCK_CLASS = '.' + goog.getCssName('goog-inline-block');
var ret = [goog.html.SafeStyleSheet.createRule(
ROOT_PANEL_CLASS, {'background': '#ffc', 'padding': '0 4px'})];
// Make this work even if the user hasn't included common.css.
if (!IE) {
ret.push(goog.html.SafeStyleSheet.createRule(
GOOG_INLINE_BLOCK_CLASS, {'display': 'inline-block'}));
}
// Space things out vertically for touch UIs.
if (MOBILE) {
ret.push(goog.html.SafeStyleSheet.createRule(
ROOT_PANEL_CLASS + ',' + ROOT_PANEL_CLASS + ' fieldset',
{'line-height': '2em'}));
}
return goog.html.SafeStyleSheet.concat(ret);
})();
/**
* Creates a TweakUi if tweaks are enabled.
* @param {goog.dom.DomHelper=} opt_domHelper The DomHelper to render with.
* @return {!Element|undefined} The root UI element or undefined if tweaks are
* not enabled.
*/
goog.tweak.TweakUi.create = function(opt_domHelper) {
'use strict';
var registry = goog.tweak.getRegistry();
if (registry) {
var ui = new goog.tweak.TweakUi(registry, opt_domHelper);
ui.render();
return ui.getRootElement();
}
};
/**
* Creates a TweakUi inside of a show/hide link.
* @param {goog.dom.DomHelper=} opt_domHelper The DomHelper to render with.
* @return {!Element|undefined} The root UI element or undefined if tweaks are
* not enabled.
*/
goog.tweak.TweakUi.createCollapsible = function(opt_domHelper) {
'use strict';
var registry = goog.tweak.getRegistry();
if (registry) {
var dh = opt_domHelper || goog.dom.getDomHelper();
// The following strings are for internal debugging only. No translation
// necessary. Do NOT wrap goog.getMsg() around these strings.
var showLink =
dh.createDom(goog.dom.TagName.A, {href: 'javascript:;'}, 'Show Tweaks');
var hideLink =
dh.createDom(goog.dom.TagName.A, {href: 'javascript:;'}, 'Hide Tweaks');
var ret = dh.createDom(goog.dom.TagName.DIV, null, showLink);
var lazyCreate = function() {
'use strict';
// Lazily render the UI.
var ui = new goog.tweak.TweakUi(
/** @type {!goog.tweak.Registry} */ (registry), dh);
ui.render();
// Put the hide link on the same line as the "Show Descriptions" link.
// Set the style lazily because we can.
hideLink.style.marginRight = '10px';
var tweakElem = ui.getRootElement();
tweakElem.insertBefore(hideLink, tweakElem.firstChild);
ret.appendChild(tweakElem);
return tweakElem;
};
new goog.ui.Zippy(showLink, lazyCreate, false /* expanded */, hideLink);
return ret;
}
};
/**
* Compares the given entries. Orders alphabetically and groups buttons and
* expandable groups.
* @param {!goog.tweak.BaseEntry} a The first entry to compare.
* @param {!goog.tweak.BaseEntry} b The second entry to compare.
* @return {number} Refer to goog.array.defaultCompare.
* @private
*/
goog.tweak.TweakUi.entryCompare_ = function(a, b) {
'use strict';
return (
goog.array.defaultCompare(
a instanceof goog.tweak.NamespaceEntry_,
b instanceof goog.tweak.NamespaceEntry_) ||
goog.array.defaultCompare(
a instanceof goog.tweak.BooleanGroup,
b instanceof goog.tweak.BooleanGroup) ||
goog.array.defaultCompare(
a instanceof goog.tweak.ButtonAction,
b instanceof goog.tweak.ButtonAction) ||
goog.array.defaultCompare(a.label, b.label) ||
goog.array.defaultCompare(a.getId(), b.getId()));
};
/**
* @param {!goog.tweak.BaseEntry} entry The entry.
* @return {boolean} Returns whether the given entry contains sub-entries.
* @private
*/
goog.tweak.TweakUi.isGroupEntry_ = function(entry) {
'use strict';
return entry instanceof goog.tweak.NamespaceEntry_ ||
entry instanceof goog.tweak.BooleanGroup;
};
/**
* Returns the list of entries from the given boolean group.
* @param {!goog.tweak.BooleanGroup} group The group to get the entries from.
* @return {!Array<!goog.tweak.BaseEntry>} The sorted entries.
* @private
*/
goog.tweak.TweakUi.extractBooleanGroupEntries_ = function(group) {
'use strict';
var ret = goog.object.getValues(group.getChildEntries());
ret.sort(goog.tweak.TweakUi.entryCompare_);
return ret;
};
/**
* @param {!goog.tweak.BaseEntry} entry The entry.
* @return {string} Returns the namespace for the entry, or '' if it is not
* namespaced.
* @private
*/
goog.tweak.TweakUi.extractNamespace_ = function(entry) {
'use strict';
var namespaceMatch = /.+(?=\.)/.exec(entry.getId());
return namespaceMatch ? namespaceMatch[0] : '';
};
/**
* @param {!goog.tweak.BaseEntry} entry The entry.
* @return {string} Returns the part of the label after the last period, unless
* the label has been explicly set (it is different from the ID).
* @private
*/
goog.tweak.TweakUi.getNamespacedLabel_ = function(entry) {
'use strict';
var label = entry.label;
if (label == entry.getId()) {
label = label.substr(label.lastIndexOf('.') + 1);
}
return label;
};
/**
* @return {!Element} The root element. Must not be called before render().
*/
goog.tweak.TweakUi.prototype.getRootElement = function() {
'use strict';
goog.asserts.assert(
this.entriesPanel_, 'TweakUi.getRootElement called before render().');
return this.entriesPanel_.getRootElement();
};
/**
* Reloads the page with query parameters set by the UI.
* @private
*/
goog.tweak.TweakUi.prototype.restartWithAppliedTweaks_ = function() {
'use strict';
var queryString = this.registry_.makeUrlQuery();
var wnd = this.domHelper_.getWindow();
if (queryString != wnd.location.search) {
wnd.location.search = queryString;
} else {
wnd.location.reload();
}
};
/**
* Installs the required CSS styles.
* @private
*/
goog.tweak.TweakUi.prototype.installStyles_ = function() {
'use strict';
// Use an marker to install the styles only once per document.
// Styles are injected via JS instead of in a separate style sheet so that
// they are automatically excluded when tweaks are stripped out.
var doc = this.domHelper_.getDocument();
if (!(goog.tweak.TweakUi.STYLE_SHEET_INSTALLED_MARKER_ in doc)) {
goog.style.installSafeStyleSheet(goog.tweak.TweakUi.CSS_STYLES_, doc);
doc[goog.tweak.TweakUi.STYLE_SHEET_INSTALLED_MARKER_] = true;
}
};
/**
* Creates the element to display when the UI is visible.
* @return {!Element} The root element.
*/
goog.tweak.TweakUi.prototype.render = function() {
'use strict';
this.installStyles_();
var dh = this.domHelper_;
// The submit button
var submitButton = dh.createDom(
goog.dom.TagName.BUTTON, {style: 'font-weight:bold'}, 'Apply Tweaks');
submitButton.onclick = goog.bind(this.restartWithAppliedTweaks_, this);
var rootPanel = new goog.tweak.EntriesPanel([], dh);
var rootPanelDiv = rootPanel.render(submitButton);
rootPanelDiv.className += ' ' + goog.tweak.TweakUi.ROOT_PANEL_CLASS_;
this.entriesPanel_ = rootPanel;
var entries = this.registry_.extractEntries(
true /* excludeChildEntries */, false /* excludeNonSettings */);
for (var i = 0, entry; entry = entries[i]; i++) {
this.insertEntry_(entry);
}
return rootPanelDiv;
};
/**
* Updates the UI with the given entry.
* @param {!goog.tweak.BaseEntry} entry The newly registered entry.
* @private
*/
goog.tweak.TweakUi.prototype.onNewRegisteredEntry_ = function(entry) {
'use strict';
if (this.entriesPanel_) {
this.insertEntry_(entry);
}
};
/**
* Updates the UI with the given entry.
* @param {!goog.tweak.BaseEntry} entry The newly registered entry.
* @private
*/
goog.tweak.TweakUi.prototype.insertEntry_ = function(entry) {
'use strict';
var panel = this.entriesPanel_;
var namespace = goog.tweak.TweakUi.extractNamespace_(entry);
if (namespace) {
// Find the NamespaceEntry that the entry belongs to.
var namespaceEntryId = goog.tweak.NamespaceEntry_.ID_PREFIX + namespace;
var nsPanel = panel.childPanels[namespaceEntryId];
if (nsPanel) {
panel = nsPanel;
} else {
entry = new goog.tweak.NamespaceEntry_(namespace, [entry]);
}
}
if (entry instanceof goog.tweak.BooleanInGroupSetting) {
var group = entry.getGroup();
// BooleanGroup entries are always registered before their
// BooleanInGroupSettings.
panel = panel.childPanels[group.getId()];
}
goog.asserts.assert(panel, 'Missing panel for entry %s', entry.getId());
panel.insertEntry(entry);
};
/**
* The body of the tweaks UI and also used for BooleanGroup.
* @param {!Array<!goog.tweak.BaseEntry>} entries The entries to show in the
* panel.
* @param {goog.dom.DomHelper=} opt_domHelper The DomHelper to render with.
* @constructor
* @final
*/
goog.tweak.EntriesPanel = function(entries, opt_domHelper) {
'use strict';
/**
* The entries to show in the panel.
* @type {!Array<!goog.tweak.BaseEntry>} entries
* @private
*/
this.entries_ = entries;
var self = this;
/**
* The bound onclick handler for the help question marks.
* @this {Element}
* @private
*/
this.boundHelpOnClickHandler_ = function() {
'use strict';
self.onHelpClick_(this.parentNode);
};
/**
* The element that contains the UI.
* @type {Element}
* @private
*/
this.rootElem_;
/**
* The element that contains all of the settings and the endElement.
* @type {Element}
* @private
*/
this.mainPanel_;
/**
* Flips between true/false each time the "Toggle Descriptions" link is
* clicked.
* @type {boolean}
* @private
*/
this.showAllDescriptionsState_;
/**
* The DomHelper to render with.
* @type {!goog.dom.DomHelper}
* @private
*/
this.domHelper_ = opt_domHelper || goog.dom.getDomHelper();
/**
* Map of tweak ID -> EntriesPanel for child panels (BooleanGroups).
* @type {!Object<!goog.tweak.EntriesPanel>}
*/
this.childPanels = {};
};
/**
* @return {!Element} Returns the expanded element. Must not be called before
* render().
*/
goog.tweak.EntriesPanel.prototype.getRootElement = function() {
'use strict';
goog.asserts.assert(
this.rootElem_, 'EntriesPanel.getRootElement called before render().');
return /** @type {!Element} */ (this.rootElem_);
};
/**
* Creates and returns the expanded element.
* The markup looks like:
*
* <div>
* <a>Show Descriptions</a>
* <div>
* ...
* {endElement}
* </div>
* </div>
*
* @param {Element|DocumentFragment=} opt_endElement Element to insert after all
* tweak entries.
* @return {!Element} The root element for the panel.
*/
goog.tweak.EntriesPanel.prototype.render = function(opt_endElement) {
'use strict';
var dh = this.domHelper_;
var entries = this.entries_;
var ret = dh.createDom(goog.dom.TagName.DIV);
var showAllDescriptionsLink = dh.createDom(
goog.dom.TagName.A, {
href: 'javascript:;',
onclick: goog.bind(this.toggleAllDescriptions, this)
},
'Toggle all Descriptions');
ret.appendChild(showAllDescriptionsLink);
// Add all of the entries.
var mainPanel = dh.createElement(goog.dom.TagName.DIV);
this.mainPanel_ = mainPanel;
for (var i = 0, entry; entry = entries[i]; i++) {
mainPanel.appendChild(this.createEntryElem_(entry));
}
if (opt_endElement) {
mainPanel.appendChild(opt_endElement);
}
ret.appendChild(mainPanel);
this.rootElem_ = ret;
return /** @type {!Element} */ (ret);
};
/**
* Inserts the given entry into the panel.
* @param {!goog.tweak.BaseEntry} entry The entry to insert.
*/
goog.tweak.EntriesPanel.prototype.insertEntry = function(entry) {
'use strict';
var insertIndex =
-goog.array.binarySearch(
this.entries_, entry, goog.tweak.TweakUi.entryCompare_) -
1;
goog.asserts.assert(
insertIndex >= 0, 'insertEntry failed for %s', entry.getId());
goog.array.insertAt(this.entries_, entry, insertIndex);
this.mainPanel_.insertBefore(
this.createEntryElem_(entry),
// IE doesn't like 'undefined' here.
this.mainPanel_.childNodes[insertIndex] || null);
};
/**
* Creates and returns a form element for the given entry.
* @param {!goog.tweak.BaseEntry} entry The entry.
* @return {!Element} The root DOM element for the entry.
* @private
*/
goog.tweak.EntriesPanel.prototype.createEntryElem_ = function(entry) {
'use strict';
var dh = this.domHelper_;
var isGroupEntry = goog.tweak.TweakUi.isGroupEntry_(entry);
var classes = isGroupEntry ? goog.tweak.TweakUi.ENTRY_GROUP_CSS_CLASSES_ :
goog.tweak.TweakUi.ENTRY_CSS_CLASSES_;
// Containers should not use label tags or else all descendent inputs will be
// connected on desktop browsers.
var containerNodeName =
isGroupEntry ? goog.dom.TagName.SPAN : goog.dom.TagName.LABEL;
var ret = dh.createDom(
goog.dom.TagName.DIV, classes,
dh.createDom(
containerNodeName, {
// Make the hover text the description.
title: entry.description,
style: 'color:' + (entry.isRestartRequired() ? '' : 'blue')
},
this.createTweakEntryDom_(entry)),
// Add the expandable help question mark.
this.createHelpElem_(entry));
return ret;
};
/**
* Click handler for the help link.
* @param {Node} entryDiv The div that contains the tweak.
* @private
*/
goog.tweak.EntriesPanel.prototype.onHelpClick_ = function(entryDiv) {
'use strict';
this.showDescription_(entryDiv, !entryDiv.style.display);
};
/**
* Twiddle the DOM so that the entry within the given span is shown/hidden.
* @param {Node} entryDiv The div that contains the tweak.
* @param {boolean} show True to show, false to hide.
* @private
*/
goog.tweak.EntriesPanel.prototype.showDescription_ = function(entryDiv, show) {
'use strict';
var descriptionElem = entryDiv.lastChild.lastChild;
goog.style.setElementShown(/** @type {Element} */ (descriptionElem), show);
entryDiv.style.display = show ? 'block' : '';
};
/**
* Creates and returns a help element for the given entry.
* @param {goog.tweak.BaseEntry} entry The entry.
* @return {!Element} The root element of the created DOM.
* @private
*/
goog.tweak.EntriesPanel.prototype.createHelpElem_ = function(entry) {
'use strict';
// The markup looks like:
// <span onclick=...><b>?</b><span>{description}</span></span>
var ret = this.domHelper_.createElement(goog.dom.TagName.SPAN);
goog.dom.safe.setInnerHtml(
ret,
goog.html.SafeHtml.concat(
goog.html.SafeHtml.create(
'b', {'style': goog.string.Const.from('padding:0 1em 0 .5em')},
'?'),
goog.html.SafeHtml.create(
'span',
{'style': goog.string.Const.from('display:none;color:#666')})));
ret.onclick = this.boundHelpOnClickHandler_;
// IE<9 doesn't support lastElementChild.
var descriptionElem = /** @type {!Element} */ (ret.lastChild);
if (entry.isRestartRequired()) {
goog.dom.setTextContent(descriptionElem, entry.description);
} else {
goog.dom.safe.setInnerHtml(
descriptionElem,
goog.html.SafeHtml.concat(
goog.html.SafeHtml.htmlEscape(entry.description),
goog.html.SafeHtml.create(
'span', {'style': goog.string.Const.from('color: blue')},
'(no restart required)')));
}
return ret;
};
/**
* Show all entry descriptions (has the same effect as clicking on all ?'s).
*/
goog.tweak.EntriesPanel.prototype.toggleAllDescriptions = function() {
'use strict';
var show = !this.showAllDescriptionsState_;
this.showAllDescriptionsState_ = show;
var entryDivs = this.domHelper_.getElementsByTagNameAndClass(
goog.dom.TagName.DIV, goog.tweak.TweakUi.ENTRY_CSS_CLASS_,
this.rootElem_);
for (var i = 0, div; div = entryDivs[i]; i++) {
this.showDescription_(div, show);
}
};
/**
* Creates the DOM element to control the given enum setting.
* @param {!goog.tweak.StringSetting|!goog.tweak.NumericSetting} tweak The
* setting.
* @param {string} label The label for the entry.
* @param {!Function} onchangeFunc onchange event handler.
* @return {!DocumentFragment} The DOM element.
* @private
*/
goog.tweak.EntriesPanel.prototype.createComboBoxDom_ = function(
tweak, label, onchangeFunc) {
'use strict';
// The markup looks like:
// Label: <select><option></option></select>
var dh = this.domHelper_;
var ret = dh.getDocument().createDocumentFragment();
ret.appendChild(dh.createTextNode(label + ': '));
var selectElem = dh.createElement(goog.dom.TagName.SELECT);
var values = tweak.getValidValues();
for (var i = 0, il = values.length; i < il; ++i) {
var optionElem = dh.createElement(goog.dom.TagName.OPTION);
optionElem.text = String(values[i]);
// Setting the option tag's value is required for selectElem.value to work
// properly.
optionElem.value = String(values[i]);
selectElem.appendChild(optionElem);
}
ret.appendChild(selectElem);
// Set the value and add a callback.
selectElem.value = String(tweak.getNewValue());
selectElem.onchange = onchangeFunc;
tweak.addCallback(function() {
'use strict';
selectElem.value = String(tweak.getNewValue());
});
return ret;
};
/**
* Creates the DOM element to control the given boolean setting.
* @param {!goog.tweak.BooleanSetting} tweak The setting.
* @param {string} label The label for the entry.
* @return {!DocumentFragment} The DOM elements.
* @private
*/
goog.tweak.EntriesPanel.prototype.createBooleanSettingDom_ = function(
tweak, label) {
'use strict';
var dh = this.domHelper_;
var ret = dh.getDocument().createDocumentFragment();
var checkbox = dh.createDom(goog.dom.TagName.INPUT, {type: 'checkbox'});
ret.appendChild(checkbox);
ret.appendChild(dh.createTextNode(label));
// Needed on IE6 to ensure the textbox doesn't get cleared
// when added to the DOM.
checkbox.defaultChecked = tweak.getNewValue();
checkbox.checked = tweak.getNewValue();
checkbox.onchange = function() {
'use strict';
tweak.setValue(checkbox.checked);
};
tweak.addCallback(function() {
'use strict';
checkbox.checked = tweak.getNewValue();
});
return ret;
};
/**
* Creates the DOM for a BooleanGroup or NamespaceEntry.
* @param {!goog.tweak.BooleanGroup|!goog.tweak.NamespaceEntry_} entry The
* entry.
* @param {string} label The label for the entry.
* @param {!Array<goog.tweak.BaseEntry>} childEntries The child entries.
* @return {!DocumentFragment} The DOM element.
* @private
*/
goog.tweak.EntriesPanel.prototype.createSubPanelDom_ = function(
entry, label, childEntries) {
'use strict';
var dh = this.domHelper_;
var toggleLink =
dh.createDom(goog.dom.TagName.A, {href: 'javascript:;'}, label + ' \xBB');
var toggleLink2 =
dh.createDom(goog.dom.TagName.A, {href: 'javascript:;'}, '\xAB ' + label);
toggleLink2.style.marginRight = '10px';
var innerUi = new goog.tweak.EntriesPanel(childEntries, dh);
this.childPanels[entry.getId()] = innerUi;
var elem = innerUi.render();
// Move the toggle descriptions link into the legend.
var descriptionsLink = elem.firstChild;
var childrenElem = dh.createDom(
goog.dom.TagName.FIELDSET, goog.getCssName('goog-inline-block'),
dh.createDom(
goog.dom.TagName.LEGEND, null, toggleLink2, descriptionsLink),
elem);
new goog.ui.Zippy(
toggleLink, childrenElem, false /* expanded */, toggleLink2);
var ret = dh.getDocument().createDocumentFragment();
ret.appendChild(toggleLink);
ret.appendChild(childrenElem);
return ret;
};
/**
* Creates the DOM element to control the given string setting.
* @param {!goog.tweak.StringSetting|!goog.tweak.NumericSetting} tweak The
* setting.
* @param {string} label The label for the entry.
* @param {!Function} onchangeFunc onchange event handler.
* @return {!DocumentFragment} The DOM element.
* @private
*/
goog.tweak.EntriesPanel.prototype.createTextBoxDom_ = function(
tweak, label, onchangeFunc) {
'use strict';
var dh = this.domHelper_;
var ret = dh.getDocument().createDocumentFragment();
ret.appendChild(dh.createTextNode(label + ': '));
var textBox = dh.createDom(goog.dom.TagName.INPUT, {
value: String(tweak.getNewValue()),
// TODO(agrieve): Make size configurable or autogrow.
size: 5,
onblur: onchangeFunc
});
ret.appendChild(textBox);
tweak.addCallback(function() {
'use strict';
textBox.value = String(tweak.getNewValue());
});
return ret;
};
/**
* Creates the DOM element to control the given button action.
* @param {!goog.tweak.ButtonAction} tweak The action.
* @param {string} label The label for the entry.
* @return {!Element} The DOM element.
* @private
*/
goog.tweak.EntriesPanel.prototype.createButtonActionDom_ = function(
tweak, label) {
'use strict';
return this.domHelper_.createDom(
goog.dom.TagName.BUTTON, {onclick: goog.bind(tweak.fireCallbacks, tweak)},
label);
};
/**
* Creates the DOM element to control the given entry.
* @param {!goog.tweak.BaseEntry} entry The entry.
* @return {!Element|!DocumentFragment} The DOM element.
* @private
*/
goog.tweak.EntriesPanel.prototype.createTweakEntryDom_ = function(entry) {
'use strict';
var label = goog.tweak.TweakUi.getNamespacedLabel_(entry);
if (entry instanceof goog.tweak.BooleanSetting) {
return this.createBooleanSettingDom_(entry, label);
} else if (entry instanceof goog.tweak.BooleanGroup) {
var childEntries = goog.tweak.TweakUi.extractBooleanGroupEntries_(entry);
return this.createSubPanelDom_(entry, label, childEntries);
} else if (entry instanceof goog.tweak.StringSetting) {
/** @this {Element} */
var setValueFunc = function() {
'use strict';
entry.setValue(this.value);
};
return entry.getValidValues() ?
this.createComboBoxDom_(entry, label, setValueFunc) :
this.createTextBoxDom_(entry, label, setValueFunc);
} else if (entry instanceof goog.tweak.NumericSetting) {
/** @this {Element} */
setValueFunc = function() {
'use strict';
// Reset the value if it's not a number.
if (isNaN(this.value)) {
this.value = entry.getNewValue();
} else {
entry.setValue(+this.value);
}
};
return entry.getValidValues() ?
this.createComboBoxDom_(entry, label, setValueFunc) :
this.createTextBoxDom_(entry, label, setValueFunc);
} else if (entry instanceof goog.tweak.NamespaceEntry_) {
return this.createSubPanelDom_(entry, entry.label, entry.entries);
}
goog.asserts.assertInstanceof(
entry, goog.tweak.ButtonAction, 'invalid entry: %s', entry);
return this.createButtonActionDom_(
/** @type {!goog.tweak.ButtonAction} */ (entry), label);
};
/**
* Entries used to represent the collapsible namespace links. These entries are
* never registered with the TweakRegistry, but are contained within the
* collection of entries within TweakPanels.
* @param {string} namespace The namespace for the entry.
* @param {!Array<!goog.tweak.BaseEntry>} entries Entries within the namespace.
* @constructor
* @extends {goog.tweak.BaseEntry}
* @private
*/
goog.tweak.NamespaceEntry_ = function(namespace, entries) {
'use strict';
goog.tweak.BaseEntry.call(
this, goog.tweak.NamespaceEntry_.ID_PREFIX + namespace,
'Tweaks within the ' + namespace + ' namespace.');
/**
* Entries within this namespace.
* @type {!Array<!goog.tweak.BaseEntry>}
*/
this.entries = entries;
this.label = namespace;
};
goog.inherits(goog.tweak.NamespaceEntry_, goog.tweak.BaseEntry);
/**
* Prefix for the IDs of namespace entries used to ensure that they do not
* conflict with regular entries.
* @type {string}
*/
goog.tweak.NamespaceEntry_.ID_PREFIX = '!';