fancywindow.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. // Copyright 2006 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 of the FancyWindow class. Please minimize
  16. * dependencies this file has on other closure classes as any dependency it
  17. * takes won't be able to use the logging infrastructure.
  18. *
  19. * This is a pretty hacky implementation, aimed at making debugging of large
  20. * applications more manageable.
  21. *
  22. * @see ../demos/debug.html
  23. */
  24. goog.provide('goog.debug.FancyWindow');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.debug.DebugWindow');
  28. goog.require('goog.debug.LogManager');
  29. goog.require('goog.debug.Logger');
  30. goog.require('goog.dom.DomHelper');
  31. goog.require('goog.dom.TagName');
  32. goog.require('goog.dom.safe');
  33. goog.require('goog.html.SafeHtml');
  34. goog.require('goog.html.SafeStyleSheet');
  35. goog.require('goog.object');
  36. goog.require('goog.string');
  37. goog.require('goog.string.Const');
  38. goog.require('goog.userAgent');
  39. // TODO(mlourenco): Introduce goog.scope for goog.html.SafeHtml once b/12014412
  40. // is fixed.
  41. /**
  42. * Provides a Fancy extension to the DebugWindow class. Allows filtering based
  43. * on loggers and levels.
  44. *
  45. * @param {string=} opt_identifier Idenitifier for this logging class.
  46. * @param {string=} opt_prefix Prefix pre-pended to messages.
  47. * @constructor
  48. * @extends {goog.debug.DebugWindow}
  49. */
  50. goog.debug.FancyWindow = function(opt_identifier, opt_prefix) {
  51. this.readOptionsFromLocalStorage_();
  52. goog.debug.FancyWindow.base(this, 'constructor', opt_identifier, opt_prefix);
  53. /** @private {goog.dom.DomHelper} */
  54. this.dh_ = null;
  55. };
  56. goog.inherits(goog.debug.FancyWindow, goog.debug.DebugWindow);
  57. /**
  58. * Constant indicating if we are able to use localStorage to persist filters
  59. * @type {boolean}
  60. */
  61. goog.debug.FancyWindow.HAS_LOCAL_STORE = (function() {
  62. try {
  63. return !!window['localStorage'].getItem;
  64. } catch (e) {
  65. }
  66. return false;
  67. })();
  68. /**
  69. * Constant defining the prefix to use when storing log levels
  70. * @type {string}
  71. */
  72. goog.debug.FancyWindow.LOCAL_STORE_PREFIX = 'fancywindow.sel.';
  73. /** @override */
  74. goog.debug.FancyWindow.prototype.writeBufferToLog = function() {
  75. this.lastCall = goog.now();
  76. if (this.hasActiveWindow()) {
  77. var logel = /** @type {!HTMLElement} */ (this.dh_.getElement('log'));
  78. // Work out if scrolling is needed before we add the content
  79. var scroll =
  80. logel.scrollHeight - (logel.scrollTop + logel.offsetHeight) <= 100;
  81. for (var i = 0; i < this.outputBuffer.length; i++) {
  82. var div = this.dh_.createDom(goog.dom.TagName.DIV, 'logmsg');
  83. goog.dom.safe.setInnerHtml(div, this.outputBuffer[i]);
  84. logel.appendChild(div);
  85. }
  86. this.outputBuffer.length = 0;
  87. this.resizeStuff_();
  88. if (scroll) {
  89. logel.scrollTop = logel.scrollHeight;
  90. }
  91. }
  92. };
  93. /** @override */
  94. goog.debug.FancyWindow.prototype.writeInitialDocument = function() {
  95. if (!this.hasActiveWindow()) {
  96. return;
  97. }
  98. var doc = this.win.document;
  99. doc.open();
  100. goog.dom.safe.documentWrite(doc, this.getHtml_());
  101. doc.close();
  102. (goog.userAgent.IE ? doc.body : this.win).onresize =
  103. goog.bind(this.resizeStuff_, this);
  104. // Create a dom helper for the logging window
  105. this.dh_ = new goog.dom.DomHelper(doc);
  106. // Don't use events system to reduce dependencies
  107. this.dh_.getElement('openbutton').onclick =
  108. goog.bind(this.openOptions_, this);
  109. this.dh_.getElement('closebutton').onclick =
  110. goog.bind(this.closeOptions_, this);
  111. this.dh_.getElement('clearbutton').onclick = goog.bind(this.clear, this);
  112. this.dh_.getElement('exitbutton').onclick = goog.bind(this.exit_, this);
  113. this.writeSavedMessages();
  114. };
  115. /**
  116. * Show the options menu.
  117. * @return {boolean} false.
  118. * @private
  119. */
  120. goog.debug.FancyWindow.prototype.openOptions_ = function() {
  121. var el = goog.asserts.assert(this.dh_.getElement('optionsarea'));
  122. goog.dom.safe.setInnerHtml(el, goog.html.SafeHtml.EMPTY);
  123. var loggers = goog.debug.FancyWindow.getLoggers_();
  124. var dh = this.dh_;
  125. for (var i = 0; i < loggers.length; i++) {
  126. var logger = loggers[i];
  127. var curlevel = logger.getLevel() ? logger.getLevel().name : 'INHERIT';
  128. var div = dh.createDom(
  129. goog.dom.TagName.DIV, {},
  130. this.getDropDown_('sel' + logger.getName(), curlevel),
  131. dh.createDom(goog.dom.TagName.SPAN, {}, logger.getName() || '(root)'));
  132. el.appendChild(div);
  133. }
  134. this.dh_.getElement('options').style.display = 'block';
  135. return false;
  136. };
  137. /**
  138. * Make a drop down for the log levels.
  139. * @param {string} id Logger id.
  140. * @param {string} selected What log level is currently selected.
  141. * @return {Element} The newly created 'select' DOM element.
  142. * @private
  143. */
  144. goog.debug.FancyWindow.prototype.getDropDown_ = function(id, selected) {
  145. var dh = this.dh_;
  146. var sel = dh.createDom(goog.dom.TagName.SELECT, {'id': id});
  147. var levels = goog.debug.Logger.Level.PREDEFINED_LEVELS;
  148. for (var i = 0; i < levels.length; i++) {
  149. var level = levels[i];
  150. var option = dh.createDom(goog.dom.TagName.OPTION, {}, level.name);
  151. if (selected == level.name) {
  152. option.selected = true;
  153. }
  154. sel.appendChild(option);
  155. }
  156. sel.appendChild(
  157. dh.createDom(
  158. goog.dom.TagName.OPTION, {'selected': selected == 'INHERIT'},
  159. 'INHERIT'));
  160. return sel;
  161. };
  162. /**
  163. * Close the options menu.
  164. * @return {boolean} The value false.
  165. * @private
  166. */
  167. goog.debug.FancyWindow.prototype.closeOptions_ = function() {
  168. this.dh_.getElement('options').style.display = 'none';
  169. var loggers = goog.debug.FancyWindow.getLoggers_();
  170. var dh = this.dh_;
  171. for (var i = 0; i < loggers.length; i++) {
  172. var logger = loggers[i];
  173. var sel = /** @type {!HTMLSelectElement} */ (
  174. dh.getElement('sel' + logger.getName()));
  175. var level = sel.options[sel.selectedIndex].text;
  176. if (level == 'INHERIT') {
  177. logger.setLevel(null);
  178. } else {
  179. logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(level));
  180. }
  181. }
  182. this.writeOptionsToLocalStorage_();
  183. return false;
  184. };
  185. /**
  186. * Resizes the log elements
  187. * @private
  188. */
  189. goog.debug.FancyWindow.prototype.resizeStuff_ = function() {
  190. var dh = this.dh_;
  191. var logel = /** @type {!HTMLElement} */ (dh.getElement('log'));
  192. var headel = /** @type {!HTMLElement} */ (dh.getElement('head'));
  193. logel.style.top = headel.offsetHeight + 'px';
  194. logel.style.height = (dh.getDocument().body.offsetHeight -
  195. headel.offsetHeight - (goog.userAgent.IE ? 4 : 0)) +
  196. 'px';
  197. };
  198. /**
  199. * Handles the user clicking the exit button, disabled the debug window and
  200. * closes the popup.
  201. * @param {Event} e Event object.
  202. * @private
  203. */
  204. goog.debug.FancyWindow.prototype.exit_ = function(e) {
  205. this.setEnabled(false);
  206. if (this.win) {
  207. this.win.close();
  208. }
  209. };
  210. /** @override */
  211. goog.debug.FancyWindow.prototype.getStyleRules = function() {
  212. var baseRules = goog.debug.FancyWindow.base(this, 'getStyleRules');
  213. var extraRules = goog.html.SafeStyleSheet.fromConstant(
  214. goog.string.Const.from(
  215. 'html,body{height:100%;width:100%;margin:0px;padding:0px;' +
  216. 'background-color:#FFF;overflow:hidden}' +
  217. '*{}' +
  218. '.logmsg{border-bottom:1px solid #CCC;padding:2px;font:90% monospace}' +
  219. '#head{position:absolute;width:100%;font:x-small arial;' +
  220. 'border-bottom:2px solid #999;background-color:#EEE;}' +
  221. '#head p{margin:0px 5px;}' +
  222. '#log{position:absolute;width:100%;background-color:#FFF;}' +
  223. '#options{position:absolute;right:0px;width:50%;height:100%;' +
  224. 'border-left:1px solid #999;background-color:#DDD;display:none;' +
  225. 'padding-left: 5px;font:normal small arial;overflow:auto;}' +
  226. '#openbutton,#closebutton{text-decoration:underline;color:#00F;cursor:' +
  227. 'pointer;position:absolute;top:0px;right:5px;font:x-small arial;}' +
  228. '#clearbutton{text-decoration:underline;color:#00F;cursor:' +
  229. 'pointer;position:absolute;top:0px;right:80px;font:x-small arial;}' +
  230. '#exitbutton{text-decoration:underline;color:#00F;cursor:' +
  231. 'pointer;position:absolute;top:0px;right:50px;font:x-small arial;}' +
  232. 'select{font:x-small arial;margin-right:10px;}' +
  233. 'hr{border:0;height:5px;background-color:#8c8;color:#8c8;}'));
  234. return goog.html.SafeStyleSheet.concat(baseRules, extraRules);
  235. };
  236. /**
  237. * Return the default HTML for the debug window
  238. * @return {!goog.html.SafeHtml} Html.
  239. * @private
  240. */
  241. goog.debug.FancyWindow.prototype.getHtml_ = function() {
  242. var SafeHtml = goog.html.SafeHtml;
  243. var head = SafeHtml.create(
  244. 'head', {},
  245. SafeHtml.concat(
  246. SafeHtml.create('title', {}, 'Logging: ' + this.identifier),
  247. SafeHtml.createStyle(this.getStyleRules())));
  248. var body = SafeHtml.create(
  249. 'body', {},
  250. SafeHtml.concat(
  251. SafeHtml.create(
  252. 'div',
  253. {'id': 'log', 'style': goog.string.Const.from('overflow:auto')}),
  254. SafeHtml.create(
  255. 'div', {'id': 'head'},
  256. SafeHtml.concat(
  257. SafeHtml.create(
  258. 'p', {},
  259. SafeHtml.create('b', {}, 'Logging: ' + this.identifier)),
  260. SafeHtml.create('p', {}, this.welcomeMessage),
  261. SafeHtml.create('span', {'id': 'clearbutton'}, 'clear'),
  262. SafeHtml.create('span', {'id': 'exitbutton'}, 'exit'),
  263. SafeHtml.create('span', {'id': 'openbutton'}, 'options'))),
  264. SafeHtml.create(
  265. 'div', {'id': 'options'},
  266. SafeHtml.concat(
  267. SafeHtml.create(
  268. 'big', {}, SafeHtml.create('b', {}, 'Options:')),
  269. SafeHtml.create('div', {'id': 'optionsarea'}),
  270. SafeHtml.create(
  271. 'span', {'id': 'closebutton'}, 'save and close')))));
  272. return SafeHtml.create('html', {}, SafeHtml.concat(head, body));
  273. };
  274. /**
  275. * Write logger levels to localStorage if possible.
  276. * @private
  277. */
  278. goog.debug.FancyWindow.prototype.writeOptionsToLocalStorage_ = function() {
  279. if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
  280. return;
  281. }
  282. var loggers = goog.debug.FancyWindow.getLoggers_();
  283. var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
  284. for (var i = 0; i < loggers.length; i++) {
  285. var key = goog.debug.FancyWindow.LOCAL_STORE_PREFIX + loggers[i].getName();
  286. var level = loggers[i].getLevel();
  287. if (key in storedKeys) {
  288. if (!level) {
  289. window.localStorage.removeItem(key);
  290. } else if (window.localStorage.getItem(key) != level.name) {
  291. window.localStorage.setItem(key, level.name);
  292. }
  293. } else if (level) {
  294. window.localStorage.setItem(key, level.name);
  295. }
  296. }
  297. };
  298. /**
  299. * Sync logger levels with any values stored in localStorage.
  300. * @private
  301. */
  302. goog.debug.FancyWindow.prototype.readOptionsFromLocalStorage_ = function() {
  303. if (!goog.debug.FancyWindow.HAS_LOCAL_STORE) {
  304. return;
  305. }
  306. var storedKeys = goog.debug.FancyWindow.getStoredKeys_();
  307. for (var key in storedKeys) {
  308. var loggerName = key.replace(goog.debug.FancyWindow.LOCAL_STORE_PREFIX, '');
  309. var logger = goog.debug.LogManager.getLogger(loggerName);
  310. var curLevel = logger.getLevel();
  311. var storedLevel = window.localStorage.getItem(key).toString();
  312. if (!curLevel || curLevel.toString() != storedLevel) {
  313. logger.setLevel(goog.debug.Logger.Level.getPredefinedLevel(storedLevel));
  314. }
  315. }
  316. };
  317. /**
  318. * Helper function to create a list of locally stored keys. Used to avoid
  319. * expensive localStorage.getItem() calls.
  320. * @return {!Object} List of keys.
  321. * @private
  322. */
  323. goog.debug.FancyWindow.getStoredKeys_ = function() {
  324. var storedKeys = {};
  325. for (var i = 0, len = window.localStorage.length; i < len; i++) {
  326. var key = window.localStorage.key(i);
  327. if (key != null &&
  328. goog.string.startsWith(
  329. key, goog.debug.FancyWindow.LOCAL_STORE_PREFIX)) {
  330. storedKeys[key] = true;
  331. }
  332. }
  333. return storedKeys;
  334. };
  335. /**
  336. * Gets a sorted array of all the loggers registered.
  337. * @return {!Array<!goog.debug.Logger>} Array of logger instances.
  338. * @private
  339. */
  340. goog.debug.FancyWindow.getLoggers_ = function() {
  341. var loggers = goog.object.getValues(goog.debug.LogManager.getLoggers());
  342. /**
  343. * @param {!goog.debug.Logger} a
  344. * @param {!goog.debug.Logger} b
  345. * @return {number}
  346. */
  347. var loggerSort = function(a, b) {
  348. return goog.array.defaultCompare(a.getName(), b.getName());
  349. };
  350. goog.array.sort(loggers, loggerSort);
  351. return loggers;
  352. };