Adding mapbox-gl branch
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Author: agrieve@google.com (Andrew Grieve)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.tweak.BaseEntry
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.tweak.BaseEntryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
goog.provide('goog.tweak.BaseEntryTest');
|
||||
goog.setTestOnly('goog.tweak.BaseEntryTest');
|
||||
|
||||
goog.require('goog.testing.MockControl');
|
||||
goog.require('goog.testing.jsunit');
|
||||
/** @suppress {extraRequire} needed for createRegistryEntries. */
|
||||
goog.require('goog.tweak.testhelpers');
|
||||
|
||||
var mockControl;
|
||||
|
||||
function setUp() {
|
||||
mockControl = new goog.testing.MockControl();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.tweak.registry_ = null;
|
||||
mockControl.$verifyAll();
|
||||
}
|
||||
|
||||
function testGetValue_defaultValues() {
|
||||
createRegistryEntries('');
|
||||
assertFalse('wrong initial value for bool', boolEntry.getValue());
|
||||
assertEquals('wrong initial value for enum', 'A', strEnumEntry.getValue());
|
||||
assertEquals('wrong initial value for str', '', strEntry.getValue());
|
||||
|
||||
assertEquals('wrong initial value for bool2', true, boolEntry2.getValue());
|
||||
assertEquals('wrong initial value for enum2', 1, numEnumEntry.getValue());
|
||||
assertEquals('wrong initial value for str2', 'foo', strEntry2.getValue());
|
||||
|
||||
assertFalse('wrong initial value for BoolOne', boolOneEntry.getValue());
|
||||
assertTrue('wrong initial value for BoolTwo', boolTwoEntry.getValue());
|
||||
}
|
||||
|
||||
function testGetValue_nonDefaultValues() {
|
||||
createRegistryEntries('?bool=1&enum=C');
|
||||
// These have the restartRequired option set.
|
||||
boolEntry.setValue(false);
|
||||
strEntry.setValue('foo');
|
||||
numEntry.setValue(5);
|
||||
assertTrue('wrong value for boolean', boolEntry.getValue());
|
||||
assertEquals('wrong value for string', strEntry.getDefaultValue(),
|
||||
strEntry.getValue());
|
||||
assertEquals('wrong value for num', numEntry.getDefaultValue(),
|
||||
numEntry.getValue());
|
||||
|
||||
// These do not have the restartRequired option set.
|
||||
strEnumEntry.setValue('B');
|
||||
boolOneEntry.setValue(true);
|
||||
assertEquals('wrong value for strEnum', 'B', strEnumEntry.getValue());
|
||||
assertEquals('wrong value for boolOne', true, boolOneEntry.getValue());
|
||||
}
|
||||
|
||||
function testCallbacks() {
|
||||
createRegistryEntries('');
|
||||
var mockCallback = mockControl.createFunctionMock();
|
||||
boolEntry.addCallback(mockCallback);
|
||||
boolOneEntry.addCallback(mockCallback);
|
||||
strEnumEntry.addCallback(mockCallback);
|
||||
numEnumEntry.addCallback(mockCallback);
|
||||
|
||||
mockCallback(boolEntry);
|
||||
mockCallback(boolOneEntry);
|
||||
mockCallback(strEnumEntry);
|
||||
mockCallback(numEnumEntry);
|
||||
mockControl.$replayAll();
|
||||
|
||||
boolEntry.setValue(true);
|
||||
boolOneEntry.setValue(true);
|
||||
strEnumEntry.setValue('C');
|
||||
numEnumEntry.setValue(3);
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
// 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.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.log');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.tweak.BasePrimitiveSetting');
|
||||
goog.require('goog.tweak.BaseSetting');
|
||||
goog.require('goog.tweak.BooleanSetting');
|
||||
goog.require('goog.tweak.NumericSetting');
|
||||
goog.require('goog.tweak.StringSetting');
|
||||
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
|
||||
* @final
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
Author: agrieve@google.com (Andrew Grieve)
|
||||
-->
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.tweak.Registry
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.tweak.RegistryTest');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
goog.provide('goog.tweak.RegistryTest');
|
||||
goog.setTestOnly('goog.tweak.RegistryTest');
|
||||
|
||||
goog.require('goog.asserts.AssertionError');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.tweak');
|
||||
/** @suppress {extraRequire} needed for createRegistryEntries. */
|
||||
goog.require('goog.tweak.testhelpers');
|
||||
|
||||
var registry;
|
||||
|
||||
function setUp() {
|
||||
createRegistryEntries('');
|
||||
registry = goog.tweak.getRegistry();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.tweak.registry_ = null;
|
||||
}
|
||||
|
||||
function testGetBaseEntry() {
|
||||
// Initial values
|
||||
assertEquals('wrong bool1 object', boolEntry,
|
||||
registry.getBooleanSetting('Bool'));
|
||||
assertEquals('wrong string object', strEntry,
|
||||
registry.getStringSetting('Str'));
|
||||
assertEquals('wrong numeric object', numEntry,
|
||||
registry.getNumericSetting('Num'));
|
||||
assertEquals('wrong button object', buttonEntry,
|
||||
registry.getEntry('Button'));
|
||||
assertEquals('wrong button object', boolGroup,
|
||||
registry.getEntry('BoolGroup'));
|
||||
}
|
||||
|
||||
function testInitializeFromQueryParams() {
|
||||
var testCase = 0;
|
||||
function assertQuery(queryStr, boolValue, enumValue, strValue, subBoolValue,
|
||||
subBoolValue2) {
|
||||
createRegistryEntries(queryStr);
|
||||
assertEquals('Wrong bool value for query: ' + queryStr, boolValue,
|
||||
boolEntry.getValue());
|
||||
assertEquals('Wrong enum value for query: ' + queryStr, enumValue,
|
||||
strEnumEntry.getValue());
|
||||
assertEquals('Wrong str value for query: ' + queryStr, strValue,
|
||||
strEntry.getValue());
|
||||
assertEquals('Wrong BoolOne value for query: ' + queryStr, subBoolValue,
|
||||
boolOneEntry.getValue());
|
||||
assertEquals('Wrong BoolTwo value for query: ' + queryStr, subBoolValue2,
|
||||
boolTwoEntry.getValue());
|
||||
}
|
||||
assertQuery('?dummy=1&bool=&enum=&s=', false, '', '', false, true);
|
||||
assertQuery('?bool=0&enum=A&s=a', false, 'A', 'a', false, true);
|
||||
assertQuery('?bool=1&enum=A', true, 'A', '', false, true);
|
||||
assertQuery('?bool=true&enum=B&s=as+df', true, 'B', 'as df', false, true);
|
||||
assertQuery('?enum=C', false, 'C', '', false, true);
|
||||
assertQuery('?enum=C&boolgroup=-booltwo', false, 'C', '', false, false);
|
||||
assertQuery('?enum=C&boolgroup=b1,-booltwo', false, 'C', '', true, false);
|
||||
assertQuery('?enum=C&boolgroup=b1', false, 'C', '', true, true);
|
||||
assertQuery('?s=a+b%20c%26', false, 'A', 'a b c&', false, true);
|
||||
}
|
||||
|
||||
function testMakeUrlQuery() {
|
||||
assertEquals('All values are default.',
|
||||
'', registry.makeUrlQuery(''));
|
||||
assertEquals('All values are default - with existing params.',
|
||||
'?super=pudu', registry.makeUrlQuery('?super=pudu'));
|
||||
|
||||
boolEntry.setValue(true);
|
||||
numEnumEntry.setValue(2);
|
||||
strEntry.setValue('f o&o');
|
||||
assertEquals('Wrong query string 1.',
|
||||
'?bool=1&enum2=2&s=f+o%26o',
|
||||
registry.makeUrlQuery('?bool=1'));
|
||||
assertEquals('Wrong query string 1 - with existing params.',
|
||||
'?super=pudu&bool=1&enum2=2&s=f+o%26o',
|
||||
registry.makeUrlQuery('?bool=0&s=g&super=pudu'));
|
||||
|
||||
boolOneEntry.setValue(true);
|
||||
assertEquals('Wrong query string 2.',
|
||||
'?bool=1&boolgroup=B1&enum2=2&s=f+o%26o',
|
||||
registry.makeUrlQuery(''));
|
||||
|
||||
boolTwoEntry.setValue(false);
|
||||
assertEquals('Wrong query string 3.',
|
||||
'?bool=1&boolgroup=B1,-booltwo&enum2=2&s=f+o%26o',
|
||||
registry.makeUrlQuery(''));
|
||||
}
|
||||
|
||||
function testOverrideDefaultValue_calledBefore() {
|
||||
registry.overrideDefaultValue('b', false);
|
||||
registry.overrideDefaultValue('b', true);
|
||||
goog.tweak.registerBoolean('b', 'b desc');
|
||||
var bEntry = registry.getEntry('b');
|
||||
assertTrue('Default value should be true.', bEntry.getDefaultValue());
|
||||
assertTrue('Value should be true.', bEntry.getValue());
|
||||
}
|
||||
|
||||
function testOverrideDefaultValue_calledAfter() {
|
||||
var exception = assertThrows('Should assert.', function() {
|
||||
registry.overrideDefaultValue('Bool2', false);
|
||||
});
|
||||
assertTrue('Wrong exception',
|
||||
exception instanceof goog.asserts.AssertionError);
|
||||
}
|
||||
|
||||
function testCompilerOverrideDefaultValue() {
|
||||
createRegistryEntries('', { 'b': true });
|
||||
registry = goog.tweak.getRegistry();
|
||||
goog.tweak.registerBoolean('b', 'b desc');
|
||||
var bEntry = registry.getEntry('b');
|
||||
assertTrue('Default value should be true.', bEntry.getDefaultValue());
|
||||
assertTrue('Value should be true.', bEntry.getValue());
|
||||
}
|
||||
|
||||
function testCompilerAndJsOverrideDefaultValue() {
|
||||
createRegistryEntries('', { 'b': false });
|
||||
registry = goog.tweak.getRegistry();
|
||||
registry.overrideDefaultValue('b', true);
|
||||
goog.tweak.registerBoolean('b', 'b desc', true);
|
||||
var bEntry = registry.getEntry('b');
|
||||
assertFalse('Default value should be false.', bEntry.getDefaultValue());
|
||||
assertFalse('Value should be false.', bEntry.getValue());
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
// 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)
|
||||
* @package
|
||||
*/
|
||||
|
||||
goog.provide('goog.tweak.testhelpers');
|
||||
|
||||
goog.setTestOnly();
|
||||
|
||||
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.
|
||||
* @suppress {accessControls} Private state is accessed for test purposes.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,826 @@
|
||||
// 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');
|
||||
goog.require('goog.object');
|
||||
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');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
/**
|
||||
* 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
|
||||
* @final
|
||||
*/
|
||||
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 = '!';
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
Copyright 2010 The Closure Library Authors. All Rights Reserved.
|
||||
|
||||
Use of this source code is governed by the Apache License, Version 2.0.
|
||||
See the COPYING file for details.
|
||||
|
||||
Author: agrieve@google.com (Andrew Grieve)
|
||||
-->
|
||||
<head>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>
|
||||
Closure Unit Tests - goog.tweak.TweakUi
|
||||
</title>
|
||||
<script src="../base.js">
|
||||
</script>
|
||||
<script>
|
||||
goog.require('goog.tweak.TweakUiTest');
|
||||
</script>
|
||||
<link rel="stylesheet" type="text/css" href="../css/common.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
goog.provide('goog.tweak.TweakUiTest');
|
||||
goog.setTestOnly('goog.tweak.TweakUiTest');
|
||||
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.testing.jsunit');
|
||||
goog.require('goog.tweak');
|
||||
goog.require('goog.tweak.TweakUi');
|
||||
/** @suppress {extraRequire} needed for createRegistryEntries. */
|
||||
goog.require('goog.tweak.testhelpers');
|
||||
|
||||
var root;
|
||||
var registry;
|
||||
var EXPECTED_ENTRIES_COUNT = 14;
|
||||
|
||||
function setUp() {
|
||||
root = document.getElementById('root');
|
||||
// Make both test cases use the same entries in order to be able to test that
|
||||
// having two UIs on the same page does not cause trouble.
|
||||
createRegistryEntries('');
|
||||
registry = goog.tweak.getRegistry();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
goog.tweak.activeBooleanGroup_ = null;
|
||||
// When debugging a single test, don't clear out the DOM.
|
||||
if (window.location.search.indexOf('runTests') == -1) {
|
||||
root.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function tearDownPage() {
|
||||
// When debugging a single test, don't clear out the DOM.
|
||||
if (window.location.search.indexOf('runTests') != -1) {
|
||||
return;
|
||||
}
|
||||
// Create both registries for interactive testing.
|
||||
createRegistryEntries('');
|
||||
registry = goog.tweak.getRegistry();
|
||||
// Add an extra tweak for testing the creation of tweaks after the UI has
|
||||
// already been rendered.
|
||||
var entryCounter = 0;
|
||||
goog.tweak.registerButton('CreateNewTweak', 'Creates a new tweak. Meant ' +
|
||||
'to simulate a tweak being registered in a lazy-loaded module.',
|
||||
function() {
|
||||
goog.tweak.registerBoolean(
|
||||
'Lazy' + ++entryCounter, 'Lazy-loaded tweak.');
|
||||
});
|
||||
goog.tweak.registerButton('CreateNewTweakInNamespace1',
|
||||
'Creates a new tweak within a namespace. Meant to simulate a tweak ' +
|
||||
'being registered in a lazy-loaded module.', function() {
|
||||
goog.tweak.registerString(
|
||||
'foo.bar.Lazy' + ++entryCounter, 'Lazy-loaded tweak.');
|
||||
});
|
||||
goog.tweak.registerButton('CreateNewTweakInNamespace2',
|
||||
'Creates a new tweak within a namespace. Meant to simulate a tweak ' +
|
||||
'being registered in a lazy-loaded module.', function() {
|
||||
goog.tweak.registerNumber(
|
||||
'foo.bar.baz.Lazy' + ++entryCounter, 'Lazy combo', 3,
|
||||
{validValues: [1, 2, 3], label: 'Lazy!'});
|
||||
});
|
||||
|
||||
var label = document.createElement('h3');
|
||||
label.innerHTML = 'TweakUi:';
|
||||
root.appendChild(label);
|
||||
createUi(false);
|
||||
|
||||
label = document.createElement('h3');
|
||||
label.innerHTML = 'Collapsible:';
|
||||
root.appendChild(label);
|
||||
createUi(true);
|
||||
}
|
||||
|
||||
function createUi(collapsible) {
|
||||
var tweakUiElem = collapsible ? goog.tweak.TweakUi.createCollapsible() :
|
||||
goog.tweak.TweakUi.create();
|
||||
root.appendChild(tweakUiElem);
|
||||
}
|
||||
|
||||
function getAllEntryDivs() {
|
||||
return goog.dom.getElementsByTagNameAndClass('div',
|
||||
goog.tweak.TweakUi.ENTRY_CSS_CLASS_);
|
||||
}
|
||||
|
||||
function getEntryDiv(entry) {
|
||||
var label = goog.tweak.TweakUi.getNamespacedLabel_(entry);
|
||||
var allDivs = getAllEntryDivs();
|
||||
var ret;
|
||||
for (var i = 0, div; div = allDivs[i]; i++) {
|
||||
var divText = goog.dom.getTextContent(div);
|
||||
if (goog.string.startsWith(divText, label) &&
|
||||
goog.string.contains(divText, entry.description)) {
|
||||
assertFalse('Found multiple divs matching entry ' + entry.getId(), !!ret);
|
||||
ret = div;
|
||||
}
|
||||
}
|
||||
assertTrue('getEntryDiv failed for ' + entry.getId(), !!ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function getEntryInput(entry) {
|
||||
var div = getEntryDiv(entry);
|
||||
return div.getElementsByTagName('input')[0] ||
|
||||
div.getElementsByTagName('select')[0];
|
||||
}
|
||||
|
||||
function testCreate() {
|
||||
createUi(false);
|
||||
assertEquals('Wrong number of entry divs.', EXPECTED_ENTRIES_COUNT,
|
||||
getAllEntryDivs().length);
|
||||
|
||||
assertFalse('checkbox should not be checked 1',
|
||||
getEntryInput(boolEntry).checked);
|
||||
assertTrue('checkbox should be checked 2',
|
||||
getEntryInput(boolEntry2).checked);
|
||||
// Enusre custom labels are being used.
|
||||
var html = document.getElementsByTagName('button')[0].innerHTML;
|
||||
assertTrue('Button label is wrong', html.indexOf('<btn>') > -1);
|
||||
html = getEntryDiv(numEnumEntry).innerHTML;
|
||||
assertTrue('Enum2 label is wrong', html.indexOf('second&') > -1);
|
||||
}
|
||||
|
||||
function testToggleBooleanSetting() {
|
||||
boolEntry.setValue(true);
|
||||
createUi(false);
|
||||
|
||||
assertTrue('checkbox should be checked',
|
||||
getEntryInput(boolEntry).checked);
|
||||
|
||||
boolEntry.setValue(false);
|
||||
assertFalse('checkbox should not be checked 1',
|
||||
getEntryInput(boolEntry).checked);
|
||||
}
|
||||
|
||||
function testToggleStringSetting() {
|
||||
strEntry.setValue('val1');
|
||||
createUi(false);
|
||||
|
||||
assertEquals('Textbox has wrong value 1',
|
||||
'val1', getEntryInput(strEntry).value);
|
||||
|
||||
strEntry.setValue('val2');
|
||||
assertEquals('Textbox has wrong value 2',
|
||||
'val2', getEntryInput(strEntry).value);
|
||||
}
|
||||
|
||||
function testToggleStringEnumSetting() {
|
||||
strEnumEntry.setValue('B');
|
||||
createUi(false);
|
||||
|
||||
assertEquals('wrong value 1',
|
||||
'B', getEntryInput(strEnumEntry).value);
|
||||
|
||||
strEnumEntry.setValue('C');
|
||||
assertEquals('wrong value 2',
|
||||
'C', getEntryInput(strEnumEntry).value);
|
||||
}
|
||||
|
||||
|
||||
function testToggleNumericSetting() {
|
||||
numEntry.setValue(3);
|
||||
createUi(false);
|
||||
|
||||
assertEquals('wrong value 1',
|
||||
'3', getEntryInput(numEntry).value);
|
||||
|
||||
numEntry.setValue(4);
|
||||
assertEquals('wrong value 2',
|
||||
'4', getEntryInput(numEntry).value);
|
||||
}
|
||||
|
||||
function testToggleNumericEnumSetting() {
|
||||
numEnumEntry.setValue(2);
|
||||
createUi(false);
|
||||
|
||||
assertEquals('wrong value 1',
|
||||
'2', getEntryInput(numEnumEntry).value);
|
||||
|
||||
numEnumEntry.setValue(3);
|
||||
assertEquals('wrong value 2',
|
||||
'3', getEntryInput(numEnumEntry).value);
|
||||
}
|
||||
|
||||
function testClickBooleanSetting() {
|
||||
createUi(false);
|
||||
|
||||
var input = getEntryInput(boolEntry);
|
||||
input.checked = true;
|
||||
input.onchange();
|
||||
assertTrue('setting should be true', boolEntry.getNewValue());
|
||||
input.checked = false;
|
||||
input.onchange();
|
||||
assertFalse('setting should be false', boolEntry.getNewValue());
|
||||
}
|
||||
|
||||
function testToggleDescriptions() {
|
||||
createUi(false);
|
||||
var toggleLink = root.getElementsByTagName('a')[0];
|
||||
var heightBefore = root.offsetHeight;
|
||||
toggleLink.onclick();
|
||||
assertTrue('Expected div height to grow from toggle descriptions.',
|
||||
root.offsetHeight > heightBefore);
|
||||
toggleLink.onclick();
|
||||
assertEquals('Expected div height to revert from toggle descriptions.',
|
||||
heightBefore, root.offsetHeight);
|
||||
}
|
||||
|
||||
function assertEntryOrder(entryId1, entryId2) {
|
||||
var entry1 = registry.getEntry(entryId1);
|
||||
var entry2 = registry.getEntry(entryId2);
|
||||
var div1 = getEntryDiv(entry1);
|
||||
var div2 = getEntryDiv(entry2);
|
||||
var order = goog.dom.compareNodeOrder(div1, div2);
|
||||
assertTrue(entry1.getId() + ' should be before ' + entry2.getId(), order < 0);
|
||||
}
|
||||
|
||||
function testAddEntry() {
|
||||
createUi(false);
|
||||
goog.tweak.registerBoolean('Lazy1', 'Lazy-loaded tweak.');
|
||||
goog.tweak.registerBoolean('Lazy2', 'Lazy-loaded tweak.',
|
||||
/* defaultValue */ false, { restartRequired: false });
|
||||
goog.tweak.beginBooleanGroup('LazyGroup', 'Lazy-loaded tweak.');
|
||||
goog.tweak.registerBoolean('Lazy3', 'Lazy-loaded tweak.');
|
||||
goog.tweak.endBooleanGroup();
|
||||
|
||||
assertEquals('Wrong number of entry divs.', EXPECTED_ENTRIES_COUNT + 4,
|
||||
getAllEntryDivs().length);
|
||||
assertEntryOrder('Enum2', 'Lazy1');
|
||||
assertEntryOrder('Lazy1', 'Lazy2');
|
||||
assertEntryOrder('Lazy2', 'Num');
|
||||
assertEntryOrder('BoolGroup', 'Lazy3');
|
||||
}
|
||||
|
||||
function testAddNamespacedEntries() {
|
||||
createUi(false);
|
||||
goog.tweak.beginBooleanGroup('NS.LazyGroup', 'Lazy-loaded tweak.');
|
||||
goog.tweak.registerBoolean('NS.InGroup', 'Lazy-loaded tweak.');
|
||||
goog.tweak.endBooleanGroup();
|
||||
goog.tweak.registerBoolean('NS.Banana', 'Lazy-loaded tweak.');
|
||||
goog.tweak.registerBoolean('NS.Apple', 'Lazy-loaded tweak.');
|
||||
|
||||
assertEquals('Wrong number of entry divs.', EXPECTED_ENTRIES_COUNT + 5,
|
||||
getAllEntryDivs().length);
|
||||
assertEntryOrder('Enum2', 'NS.Apple');
|
||||
assertEntryOrder('NS.Apple', 'NS.Banana');
|
||||
assertEntryOrder('NS.Banana', 'NS.InGroup');
|
||||
}
|
||||
|
||||
function testCollapsibleIsLazy() {
|
||||
if (document.createEvent) {
|
||||
createUi(true);
|
||||
assertEquals('Expected no entry divs.', 0, getAllEntryDivs().length);
|
||||
var showLink = root.getElementsByTagName('a')[0];
|
||||
var event = document.createEvent('MouseEvents');
|
||||
event.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false,
|
||||
false, false, false, 0, null);
|
||||
showLink.dispatchEvent(event);
|
||||
assertEquals('Wrong number of entry divs.', EXPECTED_ENTRIES_COUNT,
|
||||
getAllEntryDivs().length);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user