Update wmts-hidpi, add nicer-api-docs

This commit is contained in:
Andreas Hocevar
2014-05-06 13:02:46 -05:00
parent b3ac1afd00
commit 1e25fc5585
2239 changed files with 3726515 additions and 37010 deletions

View File

@@ -0,0 +1,997 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definitions for all tweak entries.
* The class hierarchy is as follows (abstract entries are denoted with a *):
* BaseEntry(id, description) *
* -> ButtonAction(buttons in the UI)
* -> BaseSetting(query parameter) *
* -> BooleanGroup(child booleans)
* -> BasePrimitiveSetting(value, defaultValue) *
* -> BooleanSetting
* -> StringSetting
* -> NumericSetting
* -> BooleanInGroupSetting(token)
* Most clients should not use these classes directly, but instead use the API
* defined in tweak.js. One possible use case for directly using them is to
* register tweaks that are not known at compile time.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.tweak.BaseEntry');
goog.provide('goog.tweak.BasePrimitiveSetting');
goog.provide('goog.tweak.BaseSetting');
goog.provide('goog.tweak.BooleanGroup');
goog.provide('goog.tweak.BooleanInGroupSetting');
goog.provide('goog.tweak.BooleanSetting');
goog.provide('goog.tweak.ButtonAction');
goog.provide('goog.tweak.NumericSetting');
goog.provide('goog.tweak.StringSetting');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.object');
/**
* Base class for all Registry entries.
* @param {string} id The ID for the entry. Must contain only letters,
* numbers, underscores and periods.
* @param {string} description A description of what the entry does.
* @constructor
*/
goog.tweak.BaseEntry = function(id, description) {
/**
* An ID to uniquely identify the entry.
* @type {string}
* @private
*/
this.id_ = id;
/**
* A descriptive label for the entry.
* @type {string}
*/
this.label = id;
/**
* A description of what this entry does.
* @type {string}
*/
this.description = description;
/**
* Functions to be called whenever a setting is changed or a button is
* clicked.
* @type {!Array.<!Function>}
* @private
*/
this.callbacks_ = [];
};
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
*/
goog.tweak.BaseEntry.prototype.logger =
goog.log.getLogger('goog.tweak.BaseEntry');
/**
* Whether a restart is required for changes to the setting to take effect.
* @type {boolean}
* @private
*/
goog.tweak.BaseEntry.prototype.restartRequired_ = true;
/**
* @return {string} Returns the entry's ID.
*/
goog.tweak.BaseEntry.prototype.getId = function() {
return this.id_;
};
/**
* Returns whether a restart is required for changes to the setting to take
* effect.
* @return {boolean} The value.
*/
goog.tweak.BaseEntry.prototype.isRestartRequired = function() {
return this.restartRequired_;
};
/**
* Sets whether a restart is required for changes to the setting to take
* effect.
* @param {boolean} value The new value.
*/
goog.tweak.BaseEntry.prototype.setRestartRequired = function(value) {
this.restartRequired_ = value;
};
/**
* Adds a callback that should be called when the setting has changed (or when
* an action has been clicked).
* @param {!Function} callback The callback to add.
*/
goog.tweak.BaseEntry.prototype.addCallback = function(callback) {
this.callbacks_.push(callback);
};
/**
* Removes a callback that was added by addCallback.
* @param {!Function} callback The callback to add.
*/
goog.tweak.BaseEntry.prototype.removeCallback = function(callback) {
goog.array.remove(this.callbacks_, callback);
};
/**
* Calls all registered callbacks.
*/
goog.tweak.BaseEntry.prototype.fireCallbacks = function() {
for (var i = 0, callback; callback = this.callbacks_[i]; ++i) {
callback(this);
}
};
/**
* Base class for all tweak entries that are settings. Settings are entries
* that are associated with a query parameter.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @constructor
* @extends {goog.tweak.BaseEntry}
*/
goog.tweak.BaseSetting = function(id, description) {
goog.tweak.BaseEntry.call(this, id, description);
// Apply this restriction for settings since they turn in to query
// parameters. For buttons, it's not really important.
goog.asserts.assert(!/[^A-Za-z0-9._]/.test(id),
'Tweak id contains illegal characters: ', id);
/**
* The value of this setting's query parameter.
* @type {string|undefined}
* @protected
*/
this.initialQueryParamValue;
/**
* The query parameter that controls this setting.
* @type {?string}
* @private
*/
this.paramName_ = this.getId().toLowerCase();
};
goog.inherits(goog.tweak.BaseSetting, goog.tweak.BaseEntry);
/**
* States of initialization. Entries are initialized lazily in order to allow
* their initialization to happen in multiple statements.
* @enum {number}
* @private
*/
goog.tweak.BaseSetting.InitializeState_ = {
// The start state for all settings.
NOT_INITIALIZED: 0,
// This is used to allow concrete classes to call assertNotInitialized()
// during their initialize() function.
INITIALIZING: 1,
// One a setting is initialized, it may no longer change its configuration
// settings (associated query parameter, token, etc).
INITIALIZED: 2
};
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.BaseSetting.prototype.logger =
goog.log.getLogger('goog.tweak.BaseSetting');
/**
* Whether initialize() has been called (or is in the middle of being called).
* @type {goog.tweak.BaseSetting.InitializeState_}
* @private
*/
goog.tweak.BaseSetting.prototype.initializeState_ =
goog.tweak.BaseSetting.InitializeState_.NOT_INITIALIZED;
/**
* Sets the value of the entry based on the value of the query parameter. Once
* this is called, configuration settings (associated query parameter, token,
* etc) may not be changed.
* @param {?string} value The part of the query param for this setting after
* the '='. Null if it is not present.
* @protected
*/
goog.tweak.BaseSetting.prototype.initialize = goog.abstractMethod;
/**
* Returns the value to be used in the query parameter for this tweak.
* @return {?string} The encoded value. Null if the value is set to its
* default.
*/
goog.tweak.BaseSetting.prototype.getNewValueEncoded = goog.abstractMethod;
/**
* Asserts that this tweak has not been initialized yet.
* @param {string} funcName Function name to use in the assertion message.
* @protected
*/
goog.tweak.BaseSetting.prototype.assertNotInitialized = function(funcName) {
goog.asserts.assert(this.initializeState_ !=
goog.tweak.BaseSetting.InitializeState_.INITIALIZED,
'Cannot call ' + funcName + ' after the tweak as been initialized.');
};
/**
* Returns whether the setting is currently being initialized.
* @return {boolean} Whether the setting is currently being initialized.
* @protected
*/
goog.tweak.BaseSetting.prototype.isInitializing = function() {
return this.initializeState_ ==
goog.tweak.BaseSetting.InitializeState_.INITIALIZING;
};
/**
* Sets the initial query parameter value for this setting. May not be called
* after the setting has been initialized.
* @param {string} value The inital query parameter value for this setting.
*/
goog.tweak.BaseSetting.prototype.setInitialQueryParamValue = function(value) {
this.assertNotInitialized('setInitialQueryParamValue');
this.initialQueryParamValue = value;
};
/**
* Returns the name of the query parameter used for this setting.
* @return {?string} The param name. Null if no query parameter is directly
* associated with the setting.
*/
goog.tweak.BaseSetting.prototype.getParamName = function() {
return this.paramName_;
};
/**
* Sets the name of the query parameter used for this setting. If null is
* passed the the setting will not appear in the top-level query string.
* @param {?string} value The new value.
*/
goog.tweak.BaseSetting.prototype.setParamName = function(value) {
this.assertNotInitialized('setParamName');
this.paramName_ = value;
};
/**
* Applies the default value or query param value if this is the first time
* that the function has been called.
* @protected
*/
goog.tweak.BaseSetting.prototype.ensureInitialized = function() {
if (this.initializeState_ ==
goog.tweak.BaseSetting.InitializeState_.NOT_INITIALIZED) {
// Instead of having only initialized / not initialized, there is a
// separate in-between state so that functions that call
// assertNotInitialized() will not fail when called inside of the
// initialize().
this.initializeState_ =
goog.tweak.BaseSetting.InitializeState_.INITIALIZING;
var value = this.initialQueryParamValue == undefined ? null :
this.initialQueryParamValue;
this.initialize(value);
this.initializeState_ =
goog.tweak.BaseSetting.InitializeState_.INITIALIZED;
}
};
/**
* Base class for all settings that wrap primitive values.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @param {*} defaultValue The default value for this setting.
* @constructor
* @extends {goog.tweak.BaseSetting}
*/
goog.tweak.BasePrimitiveSetting = function(id, description, defaultValue) {
goog.tweak.BaseSetting.call(this, id, description);
/**
* The default value of the setting.
* @type {*}
* @private
*/
this.defaultValue_ = defaultValue;
/**
* The value of the tweak.
* @type {*}
* @private
*/
this.value_;
/**
* The value of the tweak once "Apply Tweaks" is pressed.
* @type {*}
* @private
*/
this.newValue_;
};
goog.inherits(goog.tweak.BasePrimitiveSetting, goog.tweak.BaseSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.BasePrimitiveSetting.prototype.logger =
goog.log.getLogger('goog.tweak.BasePrimitiveSetting');
/**
* Returns the query param encoded representation of the setting's value.
* @return {string} The encoded value.
* @protected
*/
goog.tweak.BasePrimitiveSetting.prototype.encodeNewValue =
goog.abstractMethod;
/**
* If the setting has the restartRequired option, then returns its inital
* value. Otherwise, returns its current value.
* @return {*} The value.
*/
goog.tweak.BasePrimitiveSetting.prototype.getValue = function() {
this.ensureInitialized();
return this.value_;
};
/**
* Returns the value of the setting to use once "Apply Tweaks" is clicked.
* @return {*} The value.
*/
goog.tweak.BasePrimitiveSetting.prototype.getNewValue = function() {
this.ensureInitialized();
return this.newValue_;
};
/**
* Sets the value of the setting. If the setting has the restartRequired
* option, then the value will not be changed until the "Apply Tweaks" button
* is clicked. If it does not have the option, the value will be update
* immediately and all registered callbacks will be called.
* @param {*} value The value.
*/
goog.tweak.BasePrimitiveSetting.prototype.setValue = function(value) {
this.ensureInitialized();
var changed = this.newValue_ != value;
this.newValue_ = value;
// Don't fire callbacks if we are currently in the initialize() method.
if (this.isInitializing()) {
this.value_ = value;
} else {
if (!this.isRestartRequired()) {
// Update the current value only if the tweak has been marked as not
// needing a restart.
this.value_ = value;
}
if (changed) {
this.fireCallbacks();
}
}
};
/**
* Returns the default value for this setting.
* @return {*} The default value.
*/
goog.tweak.BasePrimitiveSetting.prototype.getDefaultValue = function() {
return this.defaultValue_;
};
/**
* Sets the default value for the tweak.
* @param {*} value The new value.
*/
goog.tweak.BasePrimitiveSetting.prototype.setDefaultValue =
function(value) {
this.assertNotInitialized('setDefaultValue');
this.defaultValue_ = value;
};
/**
* @override
*/
goog.tweak.BasePrimitiveSetting.prototype.getNewValueEncoded = function() {
this.ensureInitialized();
return this.newValue_ == this.defaultValue_ ? null : this.encodeNewValue();
};
/**
* A registry setting for string values.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @constructor
* @extends {goog.tweak.BasePrimitiveSetting}
*/
goog.tweak.StringSetting = function(id, description) {
goog.tweak.BasePrimitiveSetting.call(this, id, description, '');
/**
* Valid values for the setting.
* @type {Array.<string>|undefined}
*/
this.validValues_;
};
goog.inherits(goog.tweak.StringSetting, goog.tweak.BasePrimitiveSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.StringSetting.prototype.logger =
goog.log.getLogger('goog.tweak.StringSetting');
/**
* @override
* @return {string} The tweaks's value.
*/
goog.tweak.StringSetting.prototype.getValue;
/**
* @override
* @return {string} The tweaks's new value.
*/
goog.tweak.StringSetting.prototype.getNewValue;
/**
* @override
* @param {string} value The tweaks's value.
*/
goog.tweak.StringSetting.prototype.setValue;
/**
* @override
* @param {string} value The default value.
*/
goog.tweak.StringSetting.prototype.setDefaultValue;
/**
* @override
* @return {string} The default value.
*/
goog.tweak.StringSetting.prototype.getDefaultValue;
/**
* @override
*/
goog.tweak.StringSetting.prototype.encodeNewValue = function() {
return this.getNewValue();
};
/**
* Sets the valid values for the setting.
* @param {Array.<string>|undefined} values Valid values.
*/
goog.tweak.StringSetting.prototype.setValidValues = function(values) {
this.assertNotInitialized('setValidValues');
this.validValues_ = values;
// Set the default value to the first value in the list if the current
// default value is not within it.
if (values && !goog.array.contains(values, this.getDefaultValue())) {
this.setDefaultValue(values[0]);
}
};
/**
* Returns the valid values for the setting.
* @return {Array.<string>|undefined} Valid values.
*/
goog.tweak.StringSetting.prototype.getValidValues = function() {
return this.validValues_;
};
/**
* @override
*/
goog.tweak.StringSetting.prototype.initialize = function(value) {
if (value == null) {
this.setValue(this.getDefaultValue());
} else {
var validValues = this.validValues_;
if (validValues) {
// Make the query parameter values case-insensitive since users might
// type them by hand. Make the capitalization that is actual used come
// from the list of valid values.
value = value.toLowerCase();
for (var i = 0, il = validValues.length; i < il; ++i) {
if (value == validValues[i].toLowerCase()) {
this.setValue(validValues[i]);
return;
}
}
// Warn if the value is not in the list of allowed values.
goog.log.warning(this.logger, 'Tweak ' + this.getId() +
' has value outside of expected range:' + value);
}
this.setValue(value);
}
};
/**
* A registry setting for numeric values.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @constructor
* @extends {goog.tweak.BasePrimitiveSetting}
*/
goog.tweak.NumericSetting = function(id, description) {
goog.tweak.BasePrimitiveSetting.call(this, id, description, 0);
/**
* Valid values for the setting.
* @type {Array.<number>|undefined}
*/
this.validValues_;
};
goog.inherits(goog.tweak.NumericSetting, goog.tweak.BasePrimitiveSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.NumericSetting.prototype.logger =
goog.log.getLogger('goog.tweak.NumericSetting');
/**
* @override
* @return {number} The tweaks's value.
*/
goog.tweak.NumericSetting.prototype.getValue;
/**
* @override
* @return {number} The tweaks's new value.
*/
goog.tweak.NumericSetting.prototype.getNewValue;
/**
* @override
* @param {number} value The tweaks's value.
*/
goog.tweak.NumericSetting.prototype.setValue;
/**
* @override
* @param {number} value The default value.
*/
goog.tweak.NumericSetting.prototype.setDefaultValue;
/**
* @override
* @return {number} The default value.
*/
goog.tweak.NumericSetting.prototype.getDefaultValue;
/**
* @override
*/
goog.tweak.NumericSetting.prototype.encodeNewValue = function() {
return '' + this.getNewValue();
};
/**
* Sets the valid values for the setting.
* @param {Array.<number>|undefined} values Valid values.
*/
goog.tweak.NumericSetting.prototype.setValidValues =
function(values) {
this.assertNotInitialized('setValidValues');
this.validValues_ = values;
// Set the default value to the first value in the list if the current
// default value is not within it.
if (values && !goog.array.contains(values, this.getDefaultValue())) {
this.setDefaultValue(values[0]);
}
};
/**
* Returns the valid values for the setting.
* @return {Array.<number>|undefined} Valid values.
*/
goog.tweak.NumericSetting.prototype.getValidValues = function() {
return this.validValues_;
};
/**
* @override
*/
goog.tweak.NumericSetting.prototype.initialize = function(value) {
if (value == null) {
this.setValue(this.getDefaultValue());
} else {
var coercedValue = +value;
// Warn if the value is not in the list of allowed values.
if (this.validValues_ &&
!goog.array.contains(this.validValues_, coercedValue)) {
goog.log.warning(this.logger, 'Tweak ' + this.getId() +
' has value outside of expected range: ' + value);
}
if (isNaN(coercedValue)) {
goog.log.warning(this.logger, 'Tweak ' + this.getId() +
' has value of NaN, resetting to ' + this.getDefaultValue());
this.setValue(this.getDefaultValue());
} else {
this.setValue(coercedValue);
}
}
};
/**
* A registry setting that can be either true of false.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @constructor
* @extends {goog.tweak.BasePrimitiveSetting}
*/
goog.tweak.BooleanSetting = function(id, description) {
goog.tweak.BasePrimitiveSetting.call(this, id, description, false);
};
goog.inherits(goog.tweak.BooleanSetting, goog.tweak.BasePrimitiveSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.BooleanSetting.prototype.logger =
goog.log.getLogger('goog.tweak.BooleanSetting');
/**
* @override
* @return {boolean} The tweaks's value.
*/
goog.tweak.BooleanSetting.prototype.getValue;
/**
* @override
* @return {boolean} The tweaks's new value.
*/
goog.tweak.BooleanSetting.prototype.getNewValue;
/**
* @override
* @param {boolean} value The tweaks's value.
*/
goog.tweak.BooleanSetting.prototype.setValue;
/**
* @override
* @param {boolean} value The default value.
*/
goog.tweak.BooleanSetting.prototype.setDefaultValue;
/**
* @override
* @return {boolean} The default value.
*/
goog.tweak.BooleanSetting.prototype.getDefaultValue;
/**
* @override
*/
goog.tweak.BooleanSetting.prototype.encodeNewValue = function() {
return this.getNewValue() ? '1' : '0';
};
/**
* @override
*/
goog.tweak.BooleanSetting.prototype.initialize = function(value) {
if (value == null) {
this.setValue(this.getDefaultValue());
} else {
value = value.toLowerCase();
this.setValue(value == 'true' || value == '1');
}
};
/**
* An entry in a BooleanGroup.
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @param {!goog.tweak.BooleanGroup} group The group that this entry belongs
* to.
* @constructor
* @extends {goog.tweak.BooleanSetting}
*/
goog.tweak.BooleanInGroupSetting = function(id, description, group) {
goog.tweak.BooleanSetting.call(this, id, description);
/**
* The token to use in the query parameter.
* @type {string}
* @private
*/
this.token_ = this.getId().toLowerCase();
/**
* The BooleanGroup that this setting belongs to.
* @type {!goog.tweak.BooleanGroup}
* @private
*/
this.group_ = group;
// Take setting out of top-level query parameter list.
goog.tweak.BooleanInGroupSetting.superClass_.setParamName.call(this,
null);
};
goog.inherits(goog.tweak.BooleanInGroupSetting, goog.tweak.BooleanSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.BooleanInGroupSetting.prototype.logger =
goog.log.getLogger('goog.tweak.BooleanInGroupSetting');
/**
* @override
*/
goog.tweak.BooleanInGroupSetting.prototype.setParamName = function(value) {
goog.asserts.fail('Use setToken() for BooleanInGroupSetting.');
};
/**
* Sets the token to use in the query parameter.
* @param {string} value The value.
*/
goog.tweak.BooleanInGroupSetting.prototype.setToken = function(value) {
this.token_ = value;
};
/**
* Returns the token to use in the query parameter.
* @return {string} The value.
*/
goog.tweak.BooleanInGroupSetting.prototype.getToken = function() {
return this.token_;
};
/**
* Returns the BooleanGroup that this setting belongs to.
* @return {!goog.tweak.BooleanGroup} The BooleanGroup that this setting
* belongs to.
*/
goog.tweak.BooleanInGroupSetting.prototype.getGroup = function() {
return this.group_;
};
/**
* A registry setting that contains a group of boolean subfield, where all
* entries modify the same query parameter. For example:
* ?foo=setting1,-setting2
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @constructor
* @extends {goog.tweak.BaseSetting}
*/
goog.tweak.BooleanGroup = function(id, description) {
goog.tweak.BaseSetting.call(this, id, description);
/**
* A map of token->child entry.
* @type {!Object.<!goog.tweak.BooleanSetting>}
* @private
*/
this.entriesByToken_ = {};
/**
* A map of token->true/false for all tokens that appeared in the query
* parameter.
* @type {!Object.<boolean>}
* @private
*/
this.queryParamValues_ = {};
};
goog.inherits(goog.tweak.BooleanGroup, goog.tweak.BaseSetting);
/**
* The logger for this class.
* @type {goog.log.Logger}
* @protected
* @override
*/
goog.tweak.BooleanGroup.prototype.logger =
goog.log.getLogger('goog.tweak.BooleanGroup');
/**
* Returns the map of token->boolean settings.
* @return {!Object.<!goog.tweak.BooleanSetting>} The child settings.
*/
goog.tweak.BooleanGroup.prototype.getChildEntries = function() {
return this.entriesByToken_;
};
/**
* Adds the given BooleanSetting to the group.
* @param {goog.tweak.BooleanInGroupSetting} boolEntry The entry.
*/
goog.tweak.BooleanGroup.prototype.addChild = function(boolEntry) {
this.ensureInitialized();
var token = boolEntry.getToken();
var lcToken = token.toLowerCase();
goog.asserts.assert(!this.entriesByToken_[lcToken],
'Multiple bools registered with token "%s" in group: %s', token,
this.getId());
this.entriesByToken_[lcToken] = boolEntry;
// Initialize from query param.
var value = this.queryParamValues_[lcToken];
if (value != undefined) {
boolEntry.initialQueryParamValue = value ? '1' : '0';
}
};
/**
* @override
*/
goog.tweak.BooleanGroup.prototype.initialize = function(value) {
var queryParamValues = {};
if (value) {
var tokens = value.split(/\s*,\s*/);
for (var i = 0; i < tokens.length; ++i) {
var token = tokens[i].toLowerCase();
var negative = token.charAt(0) == '-';
if (negative) {
token = token.substr(1);
}
queryParamValues[token] = !negative;
}
}
this.queryParamValues_ = queryParamValues;
};
/**
* @override
*/
goog.tweak.BooleanGroup.prototype.getNewValueEncoded = function() {
this.ensureInitialized();
var nonDefaultValues = [];
// Sort the keys so that the generate value is stable.
var keys = goog.object.getKeys(this.entriesByToken_);
keys.sort();
for (var i = 0, entry; entry = this.entriesByToken_[keys[i]]; ++i) {
var encodedValue = entry.getNewValueEncoded();
if (encodedValue != null) {
nonDefaultValues.push((entry.getNewValue() ? '' : '-') +
entry.getToken());
}
}
return nonDefaultValues.length ? nonDefaultValues.join(',') : null;
};
/**
* A registry action (a button).
* @param {string} id The ID for the setting.
* @param {string} description A description of what the setting does.
* @param {!Function} callback Function to call when the button is clicked.
* @constructor
* @extends {goog.tweak.BaseEntry}
*/
goog.tweak.ButtonAction = function(id, description, callback) {
goog.tweak.BaseEntry.call(this, id, description);
this.addCallback(callback);
this.setRestartRequired(false);
};
goog.inherits(goog.tweak.ButtonAction, goog.tweak.BaseEntry);

View File

@@ -0,0 +1,310 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Definition for goog.tweak.Registry.
* Most clients should not use this class directly, but instead use the API
* defined in tweak.js. One possible use case for directly using TweakRegistry
* is to register tweaks that are not known at compile time.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.tweak.Registry');
goog.require('goog.asserts');
goog.require('goog.log');
goog.require('goog.object');
goog.require('goog.string');
goog.require('goog.tweak.BaseEntry');
goog.require('goog.uri.utils');
/**
* Singleton that manages all tweaks. This should be instantiated only from
* goog.tweak.getRegistry().
* @param {string} queryParams Value of window.location.search.
* @param {!Object.<string|number|boolean>} compilerOverrides Default value
* overrides set by the compiler.
* @constructor
*/
goog.tweak.Registry = function(queryParams, compilerOverrides) {
/**
* A map of entry id -> entry object
* @type {!Object.<!goog.tweak.BaseEntry>}
* @private
*/
this.entryMap_ = {};
/**
* The map of query params to use when initializing entry settings.
* @type {!Object.<string>}
* @private
*/
this.parsedQueryParams_ = goog.tweak.Registry.parseQueryParams(queryParams);
/**
* List of callbacks to call when a new entry is registered.
* @type {!Array.<!Function>}
* @private
*/
this.onRegisterListeners_ = [];
/**
* A map of entry ID -> default value override for overrides set by the
* compiler.
* @type {!Object.<string|number|boolean>}
* @private
*/
this.compilerDefaultValueOverrides_ = compilerOverrides;
/**
* A map of entry ID -> default value override for overrides set by
* goog.tweak.overrideDefaultValue().
* @type {!Object.<string|number|boolean>}
* @private
*/
this.defaultValueOverrides_ = {};
};
/**
* The logger for this class.
* @type {goog.log.Logger}
* @private
*/
goog.tweak.Registry.prototype.logger_ =
goog.log.getLogger('goog.tweak.Registry');
/**
* Simple parser for query params. Makes all keys lower-case.
* @param {string} queryParams The part of the url between the ? and the #.
* @return {!Object.<string>} map of key->value.
*/
goog.tweak.Registry.parseQueryParams = function(queryParams) {
// Strip off the leading ? and split on &.
var parts = queryParams.substr(1).split('&');
var ret = {};
for (var i = 0, il = parts.length; i < il; ++i) {
var entry = parts[i].split('=');
if (entry[0]) {
ret[goog.string.urlDecode(entry[0]).toLowerCase()] =
goog.string.urlDecode(entry[1] || '');
}
}
return ret;
};
/**
* Registers the given tweak setting/action.
* @param {goog.tweak.BaseEntry} entry The entry.
*/
goog.tweak.Registry.prototype.register = function(entry) {
var id = entry.getId();
var oldBaseEntry = this.entryMap_[id];
if (oldBaseEntry) {
if (oldBaseEntry == entry) {
goog.log.warning(this.logger_, 'Tweak entry registered twice: ' + id);
return;
}
goog.asserts.fail(
'Tweak entry registered twice and with different types: ' + id);
}
// Check for a default value override, either from compiler flags or from a
// call to overrideDefaultValue().
var defaultValueOverride = (id in this.compilerDefaultValueOverrides_) ?
this.compilerDefaultValueOverrides_[id] : this.defaultValueOverrides_[id];
if (goog.isDef(defaultValueOverride)) {
goog.asserts.assertInstanceof(entry, goog.tweak.BasePrimitiveSetting,
'Cannot set the default value of non-primitive setting %s',
entry.label);
entry.setDefaultValue(defaultValueOverride);
}
// Set its value from the query params.
if (entry instanceof goog.tweak.BaseSetting) {
if (entry.getParamName()) {
entry.setInitialQueryParamValue(
this.parsedQueryParams_[entry.getParamName()]);
}
}
this.entryMap_[id] = entry;
// Call all listeners.
for (var i = 0, callback; callback = this.onRegisterListeners_[i]; ++i) {
callback(entry);
}
};
/**
* Adds a callback to be called whenever a new tweak is added.
* @param {!Function} func The callback.
*/
goog.tweak.Registry.prototype.addOnRegisterListener = function(func) {
this.onRegisterListeners_.push(func);
};
/**
* @param {string} id The unique string that identifies this entry.
* @return {boolean} Whether a tweak with the given ID is registered.
*/
goog.tweak.Registry.prototype.hasEntry = function(id) {
return id in this.entryMap_;
};
/**
* Returns the BaseEntry with the given ID. Asserts if it does not exists.
* @param {string} id The unique string that identifies this entry.
* @return {!goog.tweak.BaseEntry} The entry.
*/
goog.tweak.Registry.prototype.getEntry = function(id) {
var ret = this.entryMap_[id];
goog.asserts.assert(ret, 'Tweak not registered: %s', id);
return ret;
};
/**
* Returns the boolean setting with the given ID. Asserts if the ID does not
* refer to a registered entry or if it refers to one of the wrong type.
* @param {string} id The unique string that identifies this entry.
* @return {!goog.tweak.BooleanSetting} The entry.
*/
goog.tweak.Registry.prototype.getBooleanSetting = function(id) {
var entry = this.getEntry(id);
goog.asserts.assertInstanceof(entry, goog.tweak.BooleanSetting,
'getBooleanSetting called on wrong type of BaseSetting');
return /** @type {!goog.tweak.BooleanSetting} */ (entry);
};
/**
* Returns the string setting with the given ID. Asserts if the ID does not
* refer to a registered entry or if it refers to one of the wrong type.
* @param {string} id The unique string that identifies this entry.
* @return {!goog.tweak.StringSetting} The entry.
*/
goog.tweak.Registry.prototype.getStringSetting = function(id) {
var entry = this.getEntry(id);
goog.asserts.assertInstanceof(entry, goog.tweak.StringSetting,
'getStringSetting called on wrong type of BaseSetting');
return /** @type {!goog.tweak.StringSetting} */ (entry);
};
/**
* Returns the numeric setting with the given ID. Asserts if the ID does not
* refer to a registered entry or if it refers to one of the wrong type.
* @param {string} id The unique string that identifies this entry.
* @return {!goog.tweak.NumericSetting} The entry.
*/
goog.tweak.Registry.prototype.getNumericSetting = function(id) {
var entry = this.getEntry(id);
goog.asserts.assertInstanceof(entry, goog.tweak.NumericSetting,
'getNumericSetting called on wrong type of BaseSetting');
return /** @type {!goog.tweak.NumericSetting} */ (entry);
};
/**
* Creates and returns an array of all BaseSetting objects with an associted
* query parameter.
* @param {boolean} excludeChildEntries Exclude BooleanInGroupSettings.
* @param {boolean} excludeNonSettings Exclude entries that are not subclasses
* of BaseSetting.
* @return {!Array.<!goog.tweak.BaseSetting>} The settings.
*/
goog.tweak.Registry.prototype.extractEntries =
function(excludeChildEntries, excludeNonSettings) {
var entries = [];
for (var id in this.entryMap_) {
var entry = this.entryMap_[id];
if (entry instanceof goog.tweak.BaseSetting) {
if (excludeChildEntries && !entry.getParamName()) {
continue;
}
} else if (excludeNonSettings) {
continue;
}
entries.push(entry);
}
return entries;
};
/**
* Returns the query part of the URL that will apply all set tweaks.
* @param {string=} opt_existingSearchStr The part of the url between the ? and
* the #. Uses window.location.search if not given.
* @return {string} The query string.
*/
goog.tweak.Registry.prototype.makeUrlQuery =
function(opt_existingSearchStr) {
var existingParams = opt_existingSearchStr == undefined ?
window.location.search : opt_existingSearchStr;
var sortedEntries = this.extractEntries(true /* excludeChildEntries */,
true /* excludeNonSettings */);
// Sort the params so that the urlQuery has stable ordering.
sortedEntries.sort(function(a, b) {
return goog.array.defaultCompare(a.getParamName(), b.getParamName());
});
// Add all values that are not set to their defaults.
var keysAndValues = [];
for (var i = 0, entry; entry = sortedEntries[i]; ++i) {
var encodedValue = entry.getNewValueEncoded();
if (encodedValue != null) {
keysAndValues.push(entry.getParamName(), encodedValue);
}
// Strip all tweak query params from the existing query string. This will
// make the final query string contain only the tweak settings that are set
// to their non-default values and also maintain non-tweak related query
// parameters.
existingParams = goog.uri.utils.removeParam(existingParams,
encodeURIComponent(/** @type {string} */ (entry.getParamName())));
}
var tweakParams = goog.uri.utils.buildQueryData(keysAndValues);
// Decode spaces and commas in order to make the URL more readable.
tweakParams = tweakParams.replace(/%2C/g, ',').replace(/%20/g, '+');
return !tweakParams ? existingParams :
existingParams ? existingParams + '&' + tweakParams :
'?' + tweakParams;
};
/**
* Sets a default value to use for the given tweak instead of the one passed
* to the register* function. This function must be called before the tweak is
* registered.
* @param {string} id The unique string that identifies the entry.
* @param {string|number|boolean} value The replacement value to be used as the
* default value for the setting.
*/
goog.tweak.Registry.prototype.overrideDefaultValue = function(id, value) {
goog.asserts.assert(!this.hasEntry(id),
'goog.tweak.overrideDefaultValue must be called before the tweak is ' +
'registered. Tweak: %s', id);
this.defaultValueOverrides_[id] = value;
};

View File

@@ -0,0 +1,119 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Common test functions for tweak unit tests.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.tweak.testhelpers');
goog.require('goog.tweak');
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.Registry');
goog.require('goog.tweak.StringSetting');
var boolEntry;
var boolEntry2;
var strEntry;
var strEntry2;
var strEnumEntry;
var numEntry;
var numEnumEntry;
var boolGroup;
var boolOneEntry;
var boolTwoEntry;
var buttonEntry;
/**
* Creates a registry with some entries in it.
* @param {string} queryParams The query parameter string to use for the
* registry.
* @param {!Object.<string|number|boolean>=} opt_compilerOverrides Compiler
* overrides.
*/
function createRegistryEntries(queryParams, opt_compilerOverrides) {
// Initialize the registry with the given query string.
var registry =
new goog.tweak.Registry(queryParams, opt_compilerOverrides || {});
goog.tweak.registry_ = registry;
boolEntry = new goog.tweak.BooleanSetting('Bool', 'The bool1');
registry.register(boolEntry);
boolEntry2 = new goog.tweak.BooleanSetting('Bool2', 'The bool2');
boolEntry2.setDefaultValue(true);
registry.register(boolEntry2);
strEntry = new goog.tweak.StringSetting('Str', 'The str1');
strEntry.setParamName('s');
registry.register(strEntry);
strEntry2 = new goog.tweak.StringSetting('Str2', 'The str2');
strEntry2.setDefaultValue('foo');
registry.register(strEntry2);
strEnumEntry = new goog.tweak.StringSetting('Enum', 'The enum');
strEnumEntry.setValidValues(['A', 'B', 'C']);
strEnumEntry.setRestartRequired(false);
registry.register(strEnumEntry);
numEntry = new goog.tweak.NumericSetting('Num', 'The num');
numEntry.setDefaultValue(99);
registry.register(numEntry);
numEnumEntry = new goog.tweak.NumericSetting('Enum2', 'The 2nd enum');
numEnumEntry.setValidValues([1, 2, 3]);
numEnumEntry.setRestartRequired(false);
numEnumEntry.label = 'Enum the second&';
registry.register(numEnumEntry);
boolGroup = new goog.tweak.BooleanGroup('BoolGroup', 'The bool group');
registry.register(boolGroup);
boolOneEntry = new goog.tweak.BooleanInGroupSetting('BoolOne', 'Desc for 1',
boolGroup);
boolOneEntry.setToken('B1');
boolOneEntry.setRestartRequired(false);
boolGroup.addChild(boolOneEntry);
registry.register(boolOneEntry);
boolTwoEntry = new goog.tweak.BooleanInGroupSetting('BoolTwo', 'Desc for 2',
boolGroup);
boolTwoEntry.setDefaultValue(true);
boolGroup.addChild(boolTwoEntry);
registry.register(boolTwoEntry);
buttonEntry = new goog.tweak.ButtonAction('Button', 'The Btn',
goog.nullFunction);
buttonEntry.label = '<btn>';
registry.register(buttonEntry);
var nsBoolGroup = new goog.tweak.BooleanGroup('foo.bar.BoolGroup',
'Namespaced Bool Group');
registry.register(nsBoolGroup);
var nsBool = new goog.tweak.BooleanInGroupSetting('foo.bar.BoolOne',
'Desc for Namespaced 1', nsBoolGroup);
nsBoolGroup.addChild(nsBool);
registry.register(nsBool);
}

View File

@@ -0,0 +1,301 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Provides facilities for creating and querying tweaks.
* @see http://code.google.com/p/closure-library/wiki/UsingTweaks
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.tweak');
goog.provide('goog.tweak.ConfigParams');
goog.require('goog.asserts');
goog.require('goog.tweak.BaseSetting');
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.Registry');
goog.require('goog.tweak.StringSetting');
/**
* Calls to this function are overridden by the compiler by the processTweaks
* pass. It returns the overrides to default values for tweaks set by compiler
* options.
* @return {!Object.<number|string|boolean>} A map of tweakId -> defaultValue.
* @private
*/
goog.tweak.getCompilerOverrides_ = function() {
return {};
};
/**
* The global reference to the registry, if it exists.
* @type {goog.tweak.Registry}
* @private
*/
goog.tweak.registry_ = null;
/**
* The boolean group set by beginBooleanGroup and cleared by endBooleanGroup.
* @type {goog.tweak.BooleanGroup}
* @private
*/
goog.tweak.activeBooleanGroup_ = null;
/**
* Returns/creates the registry singleton.
* @return {!goog.tweak.Registry} The tweak registry.
*/
goog.tweak.getRegistry = function() {
if (!goog.tweak.registry_) {
var queryString = window.location.search;
var overrides = goog.tweak.getCompilerOverrides_();
goog.tweak.registry_ = new goog.tweak.Registry(queryString, overrides);
}
return goog.tweak.registry_;
};
/**
* Type for configParams.
* TODO(agrieve): Remove |Object when optional fields in struct types are
* implemented.
* @typedef {{
* label:(string|undefined),
* validValues:(!Array.<string>|!Array.<number>|undefined),
* paramName:(string|undefined),
* restartRequired:(boolean|undefined),
* callback:(Function|undefined),
* token:(string|undefined)
* }|!Object}
*/
goog.tweak.ConfigParams;
/**
* Applies all extra configuration parameters in configParams.
* @param {!goog.tweak.BaseEntry} entry The entry to apply them to.
* @param {!goog.tweak.ConfigParams} configParams Extra configuration
* parameters.
* @private
*/
goog.tweak.applyConfigParams_ = function(entry, configParams) {
if (configParams.label) {
entry.label = configParams.label;
delete configParams.label;
}
if (configParams.validValues) {
goog.asserts.assert(entry instanceof goog.tweak.StringSetting ||
entry instanceof goog.tweak.NumericSetting,
'Cannot set validValues on tweak: %s', entry.getId());
entry.setValidValues(configParams.validValues);
delete configParams.validValues;
}
if (goog.isDef(configParams.paramName)) {
goog.asserts.assertInstanceof(entry, goog.tweak.BaseSetting,
'Cannot set paramName on tweak: %s', entry.getId());
entry.setParamName(configParams.paramName);
delete configParams.paramName;
}
if (goog.isDef(configParams.restartRequired)) {
entry.setRestartRequired(configParams.restartRequired);
delete configParams.restartRequired;
}
if (configParams.callback) {
entry.addCallback(configParams.callback);
delete configParams.callback;
goog.asserts.assert(
!entry.isRestartRequired() || (configParams.restartRequired == false),
'Tweak %s should set restartRequired: false, when adding a callback.',
entry.getId());
}
if (configParams.token) {
goog.asserts.assertInstanceof(entry, goog.tweak.BooleanInGroupSetting,
'Cannot set token on tweak: %s', entry.getId());
entry.setToken(configParams.token);
delete configParams.token;
}
for (var key in configParams) {
goog.asserts.fail('Unknown config options (' + key + '=' +
configParams[key] + ') for tweak ' + entry.getId());
}
};
/**
* Registers a tweak using the given factoryFunc.
* @param {!goog.tweak.BaseEntry} entry The entry to register.
* @param {boolean|string|number=} opt_defaultValue Default value.
* @param {goog.tweak.ConfigParams=} opt_configParams Extra
* configuration parameters.
* @private
*/
goog.tweak.doRegister_ = function(entry, opt_defaultValue, opt_configParams) {
if (opt_configParams) {
goog.tweak.applyConfigParams_(entry, opt_configParams);
}
if (opt_defaultValue != undefined) {
entry.setDefaultValue(opt_defaultValue);
}
if (goog.tweak.activeBooleanGroup_) {
goog.asserts.assertInstanceof(entry, goog.tweak.BooleanInGroupSetting,
'Forgot to end Boolean Group: %s',
goog.tweak.activeBooleanGroup_.getId());
goog.tweak.activeBooleanGroup_.addChild(
/** @type {!goog.tweak.BooleanInGroupSetting} */ (entry));
}
goog.tweak.getRegistry().register(entry);
};
/**
* Creates and registers a group of BooleanSettings that are all set by a
* single query parameter. A call to goog.tweak.endBooleanGroup() must be used
* to close this group. Only goog.tweak.registerBoolean() calls are allowed with
* the beginBooleanGroup()/endBooleanGroup().
* @param {string} id The unique ID for the setting.
* @param {string} description A description of what the setting does.
* @param {goog.tweak.ConfigParams=} opt_configParams Extra configuration
* parameters.
*/
goog.tweak.beginBooleanGroup = function(id, description, opt_configParams) {
var entry = new goog.tweak.BooleanGroup(id, description);
goog.tweak.doRegister_(entry, undefined, opt_configParams);
goog.tweak.activeBooleanGroup_ = entry;
};
/**
* Stops adding boolean entries to the active boolean group.
*/
goog.tweak.endBooleanGroup = function() {
goog.tweak.activeBooleanGroup_ = null;
};
/**
* Creates and registers a BooleanSetting.
* @param {string} id The unique ID for the setting.
* @param {string} description A description of what the setting does.
* @param {boolean=} opt_defaultValue The default value for the setting.
* @param {goog.tweak.ConfigParams=} opt_configParams Extra configuration
* parameters.
*/
goog.tweak.registerBoolean =
function(id, description, opt_defaultValue, opt_configParams) {
// TODO(agrieve): There is a bug in the compiler that causes these calls not
// to be stripped without this outer if. Might be Issue #90.
if (goog.tweak.activeBooleanGroup_) {
var entry = new goog.tweak.BooleanInGroupSetting(id, description,
goog.tweak.activeBooleanGroup_);
} else {
entry = new goog.tweak.BooleanSetting(id, description);
}
goog.tweak.doRegister_(entry, opt_defaultValue, opt_configParams);
};
/**
* Creates and registers a StringSetting.
* @param {string} id The unique ID for the setting.
* @param {string} description A description of what the setting does.
* @param {string=} opt_defaultValue The default value for the setting.
* @param {goog.tweak.ConfigParams=} opt_configParams Extra configuration
* parameters.
*/
goog.tweak.registerString =
function(id, description, opt_defaultValue, opt_configParams) {
goog.tweak.doRegister_(new goog.tweak.StringSetting(id, description),
opt_defaultValue, opt_configParams);
};
/**
* Creates and registers a NumericSetting.
* @param {string} id The unique ID for the setting.
* @param {string} description A description of what the setting does.
* @param {number=} opt_defaultValue The default value for the setting.
* @param {goog.tweak.ConfigParams=} opt_configParams Extra configuration
* parameters.
*/
goog.tweak.registerNumber =
function(id, description, opt_defaultValue, opt_configParams) {
goog.tweak.doRegister_(new goog.tweak.NumericSetting(id, description),
opt_defaultValue, opt_configParams);
};
/**
* Creates and registers a ButtonAction.
* @param {string} id The unique ID for the setting.
* @param {string} description A description of what the action does.
* @param {!Function} callback Function to call when the button is clicked.
* @param {string=} opt_label The button text (instead of the ID).
*/
goog.tweak.registerButton = function(id, description, callback, opt_label) {
var tweak = new goog.tweak.ButtonAction(id, description, callback);
tweak.label = opt_label || tweak.label;
goog.tweak.doRegister_(tweak);
};
/**
* Sets a default value to use for the given tweak instead of the one passed
* to the register* function. This function must be called before the tweak is
* registered.
* @param {string} id The unique string that identifies the entry.
* @param {string|number|boolean} value The new default value for the tweak.
*/
goog.tweak.overrideDefaultValue = function(id, value) {
goog.tweak.getRegistry().overrideDefaultValue(id, value);
};
/**
* Returns the value of the boolean setting with the given ID.
* @param {string} id The unique string that identifies this entry.
* @return {boolean} The value of the tweak.
*/
goog.tweak.getBoolean = function(id) {
return goog.tweak.getRegistry().getBooleanSetting(id).getValue();
};
/**
* Returns the value of the string setting with the given ID,
* @param {string} id The unique string that identifies this entry.
* @return {string} The value of the tweak.
*/
goog.tweak.getString = function(id) {
return goog.tweak.getRegistry().getStringSetting(id).getValue();
};
/**
* Returns the value of the numeric setting with the given ID.
* @param {string} id The unique string that identifies this entry.
* @return {number} The value of the tweak.
*/
goog.tweak.getNumber = function(id) {
return goog.tweak.getRegistry().getNumericSetting(id).getValue();
};

View File

@@ -0,0 +1,817 @@
// Copyright 2009 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview A UI for editing tweak settings / clicking tweak actions.
*
* @author agrieve@google.com (Andrew Grieve)
*/
goog.provide('goog.tweak.EntriesPanel');
goog.provide('goog.tweak.TweakUi');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom.DomHelper');
goog.require('goog.object');
goog.require('goog.style');
goog.require('goog.tweak');
goog.require('goog.ui.Zippy');
goog.require('goog.userAgent');
/**
* 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
*/
goog.tweak.TweakUi = function(registry, opt_domHelper) {
/**
* 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 {string}
* @private
*/
goog.tweak.TweakUi.CSS_STYLES_ = (function() {
var MOBILE = goog.userAgent.MOBILE;
var IE = goog.userAgent.IE;
var ENTRY_CLASS = '.' + goog.tweak.TweakUi.ENTRY_CSS_CLASS_;
var ROOT_PANEL_CLASS = '.' + goog.tweak.TweakUi.ROOT_PANEL_CLASS_;
var GOOG_INLINE_BLOCK_CLASS = '.' + goog.getCssName('goog-inline-block');
var ret = ROOT_PANEL_CLASS + '{background:#ffc; padding:0 4px}';
// Make this work even if the user hasn't included common.css.
if (!IE) {
ret += GOOG_INLINE_BLOCK_CLASS + '{display:inline-block}';
}
// Space things out vertically for touch UIs.
if (MOBILE) {
ret += ROOT_PANEL_CLASS + ',' + ROOT_PANEL_CLASS + ' fieldset{' +
'line-height:2em;' + '}';
}
return 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) {
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) {
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('a', {href: 'javascript:;'}, 'Show Tweaks');
var hideLink = dh.createDom('a', {href: 'javascript:;'}, 'Hide Tweaks');
var ret = dh.createDom('div', null, showLink);
var lazyCreate = function() {
// 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) {
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) {
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) {
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) {
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) {
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() {
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() {
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 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.installStyles(
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() {
this.installStyles_();
var dh = this.domHelper_;
// The submit button
var submitButton = dh.createDom('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) {
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) {
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
*/
goog.tweak.EntriesPanel = function(entries, opt_domHelper) {
/**
* 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() {
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() {
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) {
var dh = this.domHelper_;
var entries = this.entries_;
var ret = dh.createDom('div');
var showAllDescriptionsLink = dh.createDom('a', {
href: 'javascript:;',
onclick: goog.bind(this.toggleAllDescriptions, this)
}, 'Toggle all Descriptions');
ret.appendChild(showAllDescriptionsLink);
// Add all of the entries.
var mainPanel = dh.createElement('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) {
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) {
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 ? 'span' : 'label';
var ret = dh.createDom('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) {
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) {
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) {
// The markup looks like:
// <span onclick=...><b>?</b><span>{description}</span></span>
var ret = this.domHelper_.createElement('span');
ret.innerHTML = '<b style="padding:0 1em 0 .5em">?</b>' +
'<span style="display:none;color:#666"></span>';
ret.onclick = this.boundHelpOnClickHandler_;
var descriptionElem = ret.lastChild;
goog.dom.setTextContent(/** @type {Element} */ (descriptionElem),
entry.description);
if (!entry.isRestartRequired()) {
descriptionElem.innerHTML +=
' <span style="color:blue">(no restart required)</span>';
}
return ret;
};
/**
* Show all entry descriptions (has the same effect as clicking on all ?'s).
*/
goog.tweak.EntriesPanel.prototype.toggleAllDescriptions = function() {
var show = !this.showAllDescriptionsState_;
this.showAllDescriptionsState_ = show;
var entryDivs = this.domHelper_.getElementsByTagNameAndClass('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) {
// 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('select');
var values = tweak.getValidValues();
for (var i = 0, il = values.length; i < il; ++i) {
var optionElem = dh.createElement('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 = tweak.getNewValue();
selectElem.onchange = onchangeFunc;
tweak.addCallback(function() {
selectElem.value = 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) {
var dh = this.domHelper_;
var ret = dh.getDocument().createDocumentFragment();
var checkbox = dh.createDom('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() {
tweak.setValue(checkbox.checked);
};
tweak.addCallback(function() {
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) {
var dh = this.domHelper_;
var toggleLink = dh.createDom('a', {href: 'javascript:;'},
label + ' \xBB');
var toggleLink2 = dh.createDom('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('fieldset',
goog.getCssName('goog-inline-block'),
dh.createDom('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) {
var dh = this.domHelper_;
var ret = dh.getDocument().createDocumentFragment();
ret.appendChild(dh.createTextNode(label + ': '));
var textBox = dh.createDom('input', {
value: tweak.getNewValue(),
// TODO(agrieve): Make size configurable or autogrow.
size: 5,
onblur: onchangeFunc
});
ret.appendChild(textBox);
tweak.addCallback(function() {
textBox.value = 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) {
return this.domHelper_.createDom('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) {
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() {
entry.setValue(this.value);
};
return entry.getValidValues() ?
this.createComboBoxDom_(entry, label, setValueFunc) :
this.createTextBoxDom_(entry, label, setValueFunc);
} else if (entry instanceof goog.tweak.NumericSetting) {
setValueFunc = function() {
// 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) {
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 = '!';