registry.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. // Copyright 2009 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition for goog.tweak.Registry.
  16. * Most clients should not use this class directly, but instead use the API
  17. * defined in tweak.js. One possible use case for directly using TweakRegistry
  18. * is to register tweaks that are not known at compile time.
  19. *
  20. * @author agrieve@google.com (Andrew Grieve)
  21. */
  22. goog.provide('goog.tweak.Registry');
  23. goog.require('goog.array');
  24. goog.require('goog.asserts');
  25. goog.require('goog.log');
  26. goog.require('goog.string');
  27. goog.require('goog.tweak.BasePrimitiveSetting');
  28. goog.require('goog.tweak.BaseSetting');
  29. goog.require('goog.tweak.BooleanSetting');
  30. goog.require('goog.tweak.NumericSetting');
  31. goog.require('goog.tweak.StringSetting');
  32. goog.require('goog.uri.utils');
  33. /**
  34. * Singleton that manages all tweaks. This should be instantiated only from
  35. * goog.tweak.getRegistry().
  36. * @param {string} queryParams Value of window.location.search.
  37. * @param {!Object<string|number|boolean>} compilerOverrides Default value
  38. * overrides set by the compiler.
  39. * @constructor
  40. * @final
  41. */
  42. goog.tweak.Registry = function(queryParams, compilerOverrides) {
  43. /**
  44. * A map of entry id -> entry object
  45. * @type {!Object<!goog.tweak.BaseEntry>}
  46. * @private
  47. */
  48. this.entryMap_ = {};
  49. /**
  50. * The map of query params to use when initializing entry settings.
  51. * @type {!Object<string>}
  52. * @private
  53. */
  54. this.parsedQueryParams_ = goog.tweak.Registry.parseQueryParams(queryParams);
  55. /**
  56. * List of callbacks to call when a new entry is registered.
  57. * @type {!Array<!Function>}
  58. * @private
  59. */
  60. this.onRegisterListeners_ = [];
  61. /**
  62. * A map of entry ID -> default value override for overrides set by the
  63. * compiler.
  64. * @type {!Object<string|number|boolean>}
  65. * @private
  66. */
  67. this.compilerDefaultValueOverrides_ = compilerOverrides;
  68. /**
  69. * A map of entry ID -> default value override for overrides set by
  70. * goog.tweak.overrideDefaultValue().
  71. * @type {!Object<string|number|boolean>}
  72. * @private
  73. */
  74. this.defaultValueOverrides_ = {};
  75. };
  76. /**
  77. * The logger for this class.
  78. * @type {goog.log.Logger}
  79. * @private
  80. */
  81. goog.tweak.Registry.prototype.logger_ =
  82. goog.log.getLogger('goog.tweak.Registry');
  83. /**
  84. * Simple parser for query params. Makes all keys lower-case.
  85. * @param {string} queryParams The part of the url between the ? and the #.
  86. * @return {!Object<string>} map of key->value.
  87. */
  88. goog.tweak.Registry.parseQueryParams = function(queryParams) {
  89. // Strip off the leading ? and split on &.
  90. var parts = queryParams.substr(1).split('&');
  91. var ret = {};
  92. for (var i = 0, il = parts.length; i < il; ++i) {
  93. var entry = parts[i].split('=');
  94. if (entry[0]) {
  95. ret[goog.string.urlDecode(entry[0]).toLowerCase()] =
  96. goog.string.urlDecode(entry[1] || '');
  97. }
  98. }
  99. return ret;
  100. };
  101. /**
  102. * Registers the given tweak setting/action.
  103. * @param {goog.tweak.BaseEntry} entry The entry.
  104. */
  105. goog.tweak.Registry.prototype.register = function(entry) {
  106. var id = entry.getId();
  107. var oldBaseEntry = this.entryMap_[id];
  108. if (oldBaseEntry) {
  109. if (oldBaseEntry == entry) {
  110. goog.log.warning(this.logger_, 'Tweak entry registered twice: ' + id);
  111. return;
  112. }
  113. goog.asserts.fail(
  114. 'Tweak entry registered twice and with different types: ' + id);
  115. }
  116. // Check for a default value override, either from compiler flags or from a
  117. // call to overrideDefaultValue().
  118. var defaultValueOverride = (id in this.compilerDefaultValueOverrides_) ?
  119. this.compilerDefaultValueOverrides_[id] :
  120. this.defaultValueOverrides_[id];
  121. if (goog.isDef(defaultValueOverride)) {
  122. goog.asserts.assertInstanceof(
  123. entry, goog.tweak.BasePrimitiveSetting,
  124. 'Cannot set the default value of non-primitive setting %s',
  125. entry.label);
  126. entry.setDefaultValue(defaultValueOverride);
  127. }
  128. // Set its value from the query params.
  129. if (entry instanceof goog.tweak.BaseSetting) {
  130. if (entry.getParamName()) {
  131. entry.setInitialQueryParamValue(
  132. this.parsedQueryParams_[entry.getParamName()]);
  133. }
  134. }
  135. this.entryMap_[id] = entry;
  136. // Call all listeners.
  137. for (var i = 0, callback; callback = this.onRegisterListeners_[i]; ++i) {
  138. callback(entry);
  139. }
  140. };
  141. /**
  142. * Adds a callback to be called whenever a new tweak is added.
  143. * @param {!Function} func The callback.
  144. */
  145. goog.tweak.Registry.prototype.addOnRegisterListener = function(func) {
  146. this.onRegisterListeners_.push(func);
  147. };
  148. /**
  149. * @param {string} id The unique string that identifies this entry.
  150. * @return {boolean} Whether a tweak with the given ID is registered.
  151. */
  152. goog.tweak.Registry.prototype.hasEntry = function(id) {
  153. return id in this.entryMap_;
  154. };
  155. /**
  156. * Returns the BaseEntry with the given ID. Asserts if it does not exists.
  157. * @param {string} id The unique string that identifies this entry.
  158. * @return {!goog.tweak.BaseEntry} The entry.
  159. */
  160. goog.tweak.Registry.prototype.getEntry = function(id) {
  161. var ret = this.entryMap_[id];
  162. goog.asserts.assert(ret, 'Tweak not registered: %s', id);
  163. return ret;
  164. };
  165. /**
  166. * Returns the boolean setting with the given ID. Asserts if the ID does not
  167. * refer to a registered entry or if it refers to one of the wrong type.
  168. * @param {string} id The unique string that identifies this entry.
  169. * @return {!goog.tweak.BooleanSetting} The entry.
  170. */
  171. goog.tweak.Registry.prototype.getBooleanSetting = function(id) {
  172. var entry = this.getEntry(id);
  173. goog.asserts.assertInstanceof(
  174. entry, goog.tweak.BooleanSetting,
  175. 'getBooleanSetting called on wrong type of BaseSetting');
  176. return /** @type {!goog.tweak.BooleanSetting} */ (entry);
  177. };
  178. /**
  179. * Returns the string setting with the given ID. Asserts if the ID does not
  180. * refer to a registered entry or if it refers to one of the wrong type.
  181. * @param {string} id The unique string that identifies this entry.
  182. * @return {!goog.tweak.StringSetting} The entry.
  183. */
  184. goog.tweak.Registry.prototype.getStringSetting = function(id) {
  185. var entry = this.getEntry(id);
  186. goog.asserts.assertInstanceof(
  187. entry, goog.tweak.StringSetting,
  188. 'getStringSetting called on wrong type of BaseSetting');
  189. return /** @type {!goog.tweak.StringSetting} */ (entry);
  190. };
  191. /**
  192. * Returns the numeric setting with the given ID. Asserts if the ID does not
  193. * refer to a registered entry or if it refers to one of the wrong type.
  194. * @param {string} id The unique string that identifies this entry.
  195. * @return {!goog.tweak.NumericSetting} The entry.
  196. */
  197. goog.tweak.Registry.prototype.getNumericSetting = function(id) {
  198. var entry = this.getEntry(id);
  199. goog.asserts.assertInstanceof(
  200. entry, goog.tweak.NumericSetting,
  201. 'getNumericSetting called on wrong type of BaseSetting');
  202. return /** @type {!goog.tweak.NumericSetting} */ (entry);
  203. };
  204. /**
  205. * Creates and returns an array of all BaseSetting objects with an associted
  206. * query parameter.
  207. * @param {boolean} excludeChildEntries Exclude BooleanInGroupSettings.
  208. * @param {boolean} excludeNonSettings Exclude entries that are not subclasses
  209. * of BaseSetting.
  210. * @return {!Array<!goog.tweak.BaseSetting>} The settings.
  211. */
  212. goog.tweak.Registry.prototype.extractEntries = function(
  213. excludeChildEntries, excludeNonSettings) {
  214. var entries = [];
  215. for (var id in this.entryMap_) {
  216. var entry = this.entryMap_[id];
  217. if (entry instanceof goog.tweak.BaseSetting) {
  218. if (excludeChildEntries && !entry.getParamName()) {
  219. continue;
  220. }
  221. } else if (excludeNonSettings) {
  222. continue;
  223. }
  224. entries.push(entry);
  225. }
  226. return entries;
  227. };
  228. /**
  229. * Returns the query part of the URL that will apply all set tweaks.
  230. * @param {string=} opt_existingSearchStr The part of the url between the ? and
  231. * the #. Uses window.location.search if not given.
  232. * @return {string} The query string.
  233. */
  234. goog.tweak.Registry.prototype.makeUrlQuery = function(opt_existingSearchStr) {
  235. var existingParams = opt_existingSearchStr == undefined ?
  236. window.location.search :
  237. opt_existingSearchStr;
  238. var sortedEntries = this.extractEntries(
  239. true /* excludeChildEntries */, true /* excludeNonSettings */);
  240. // Sort the params so that the urlQuery has stable ordering.
  241. sortedEntries.sort(function(a, b) {
  242. return goog.array.defaultCompare(a.getParamName(), b.getParamName());
  243. });
  244. // Add all values that are not set to their defaults.
  245. var keysAndValues = [];
  246. for (var i = 0, entry; entry = sortedEntries[i]; ++i) {
  247. var encodedValue = entry.getNewValueEncoded();
  248. if (encodedValue != null) {
  249. keysAndValues.push(entry.getParamName(), encodedValue);
  250. }
  251. // Strip all tweak query params from the existing query string. This will
  252. // make the final query string contain only the tweak settings that are set
  253. // to their non-default values and also maintain non-tweak related query
  254. // parameters.
  255. existingParams = goog.uri.utils.removeParam(
  256. existingParams,
  257. encodeURIComponent(/** @type {string} */ (entry.getParamName())));
  258. }
  259. var tweakParams = goog.uri.utils.buildQueryData(keysAndValues);
  260. // Decode spaces and commas in order to make the URL more readable.
  261. tweakParams = tweakParams.replace(/%2C/g, ',').replace(/%20/g, '+');
  262. return !tweakParams ? existingParams : existingParams ?
  263. existingParams + '&' + tweakParams :
  264. '?' + tweakParams;
  265. };
  266. /**
  267. * Sets a default value to use for the given tweak instead of the one passed
  268. * to the register* function. This function must be called before the tweak is
  269. * registered.
  270. * @param {string} id The unique string that identifies the entry.
  271. * @param {string|number|boolean} value The replacement value to be used as the
  272. * default value for the setting.
  273. */
  274. goog.tweak.Registry.prototype.overrideDefaultValue = function(id, value) {
  275. goog.asserts.assert(
  276. !this.hasEntry(id),
  277. 'goog.tweak.overrideDefaultValue must be called before the tweak is ' +
  278. 'registered. Tweak: %s',
  279. id);
  280. this.defaultValueOverrides_[id] = value;
  281. };