2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
7 /****************************************************************************/
8 /****************************************************************************/
9 /****************************************************************************/
12 * The LogMsg class defines a single log message.
16 * @param oConfigs {Object} Object literal of configuration params.
18 YAHOO.widget.LogMsg = function(oConfigs) {
44 * Log source. The first word passed in as the source argument.
52 * Log source detail. The remainder of the string passed in as the source argument, not
53 * including the first word (if any).
55 * @property sourceDetail
58 this.sourceDetail = null;
60 if (oConfigs && (oConfigs.constructor == Object)) {
61 for(var param in oConfigs) {
62 if (oConfigs.hasOwnProperty(param)) {
63 this[param] = oConfigs[param];
68 /****************************************************************************/
69 /****************************************************************************/
70 /****************************************************************************/
73 * The LogWriter class provides a mechanism to log messages through
74 * YAHOO.widget.Logger from a named source.
78 * @param sSource {String} Source of LogWriter instance.
80 YAHOO.widget.LogWriter = function(sSource) {
82 YAHOO.log("Could not instantiate LogWriter due to invalid source.",
83 "error", "LogWriter");
86 this._source = sSource;
89 /////////////////////////////////////////////////////////////////////////////
93 /////////////////////////////////////////////////////////////////////////////
96 * Public accessor to the unique name of the LogWriter instance.
99 * @return {String} Unique name of the LogWriter instance.
101 YAHOO.widget.LogWriter.prototype.toString = function() {
102 return "LogWriter " + this._sSource;
106 * Logs a message attached to the source of the LogWriter.
109 * @param sMsg {String} The log message.
110 * @param sCategory {String} Category name.
112 YAHOO.widget.LogWriter.prototype.log = function(sMsg, sCategory) {
113 YAHOO.widget.Logger.log(sMsg, sCategory, this._source);
117 * Public accessor to get the source name.
120 * @return {String} The LogWriter source.
122 YAHOO.widget.LogWriter.prototype.getSource = function() {
127 * Public accessor to set the source name.
130 * @param sSource {String} Source of LogWriter instance.
132 YAHOO.widget.LogWriter.prototype.setSource = function(sSource) {
134 YAHOO.log("Could not set source due to invalid source.", "error", this.toString());
138 this._source = sSource;
142 /////////////////////////////////////////////////////////////////////////////
144 // Private member variables
146 /////////////////////////////////////////////////////////////////////////////
149 * Source of the LogWriter instance.
155 YAHOO.widget.LogWriter.prototype._source = null;
160 * The Logger widget provides a simple way to read or write log messages in
161 * JavaScript code. Integration with the YUI Library's debug builds allow
162 * implementers to access under-the-hood events, errors, and debugging messages.
163 * Output may be read through a LogReader console and/or output to a browser
167 * @requires yahoo, event, dom
169 * @namespace YAHOO.widget
170 * @title Logger Widget
173 /****************************************************************************/
174 /****************************************************************************/
175 /****************************************************************************/
178 if(!YAHOO.widget.Logger) {
180 * The singleton Logger class provides core log management functionality. Saves
181 * logs written through the global YAHOO.log function or written by a LogWriter
182 * instance. Provides access to logs for reading by a LogReader instance or
183 * native browser console such as the Firebug extension to Firefox or Safari's
184 * JavaScript console through integration with the console.log() method.
189 YAHOO.widget.Logger = {
190 // Initialize properties
192 _browserConsoleEnabled: false,
193 categories: ["info","warn","error","time","window"],
195 _stack: [], // holds all log msgs
196 maxStackEntries: 2500,
197 _startTime: new Date().getTime(), // static start timestamp
198 _lastTime: null, // timestamp of last logged message
199 _windowErrorsHandled: false,
200 _origOnWindowError: null
203 /////////////////////////////////////////////////////////////////////////////
207 /////////////////////////////////////////////////////////////////////////////
209 * True if Logger is enabled, false otherwise.
211 * @property loggerEnabled
218 * Array of categories.
220 * @property categories
223 * @default ["info","warn","error","time","window"]
232 * @default ["global"]
236 * Upper limit on size of internal stack.
238 * @property maxStackEntries
244 /////////////////////////////////////////////////////////////////////////////
246 // Private properties
248 /////////////////////////////////////////////////////////////////////////////
250 * Internal property to track whether output to browser console is enabled.
252 * @property _browserConsoleEnabled
260 * Array to hold all log messages.
268 * Static timestamp of Logger initialization.
270 * @property _startTime
276 * Timestamp of last logged message.
278 * @property _lastTime
283 /////////////////////////////////////////////////////////////////////////////
287 /////////////////////////////////////////////////////////////////////////////
289 * Saves a log message to the stack and fires newLogEvent. If the log message is
290 * assigned to an unknown category, creates a new category. If the log message is
291 * from an unknown source, creates a new source. If browser console is enabled,
292 * outputs the log message to browser console.
295 * @param sMsg {String} The log message.
296 * @param sCategory {String} Category of log message, or null.
297 * @param sSource {String} Source of LogWriter, or null if global.
299 YAHOO.widget.Logger.log = function(sMsg, sCategory, sSource) {
300 if(this.loggerEnabled) {
302 sCategory = "info"; // default category
305 sCategory = sCategory.toLocaleLowerCase();
306 if(this._isNewCategory(sCategory)) {
307 this._createNewCategory(sCategory);
310 var sClass = "global"; // default source
313 var spaceIndex = sSource.indexOf(" ");
315 // Substring until first space
316 sClass = sSource.substring(0,spaceIndex);
317 // The rest of the source
318 sDetail = sSource.substring(spaceIndex,sSource.length);
323 if(this._isNewSource(sClass)) {
324 this._createNewSource(sClass);
328 var timestamp = new Date();
329 var logEntry = new YAHOO.widget.LogMsg({
334 sourceDetail: sDetail
337 var stack = this._stack;
338 var maxStackEntries = this.maxStackEntries;
339 if(maxStackEntries && !isNaN(maxStackEntries) &&
340 (stack.length >= maxStackEntries)) {
343 stack.push(logEntry);
344 this.newLogEvent.fire(logEntry);
346 if(this._browserConsoleEnabled) {
347 this._printToBrowserConsole(logEntry);
357 * Resets internal stack and startTime, enables Logger, and fires logResetEvent.
361 YAHOO.widget.Logger.reset = function() {
363 this._startTime = new Date().getTime();
364 this.loggerEnabled = true;
365 this.log("Logger reset");
366 this.logResetEvent.fire();
370 * Public accessor to internal stack of log message objects.
373 * @return {Object[]} Array of log message objects.
375 YAHOO.widget.Logger.getStack = function() {
380 * Public accessor to internal start time.
382 * @method getStartTime
383 * @return {Date} Internal date of when Logger singleton was initialized.
385 YAHOO.widget.Logger.getStartTime = function() {
386 return this._startTime;
390 * Disables output to the browser's global console.log() function, which is used
391 * by the Firebug extension to Firefox as well as Safari.
393 * @method disableBrowserConsole
395 YAHOO.widget.Logger.disableBrowserConsole = function() {
396 YAHOO.log("Logger output to the function console.log() has been disabled.");
397 this._browserConsoleEnabled = false;
401 * Enables output to the browser's global console.log() function, which is used
402 * by the Firebug extension to Firefox as well as Safari.
404 * @method enableBrowserConsole
406 YAHOO.widget.Logger.enableBrowserConsole = function() {
407 this._browserConsoleEnabled = true;
408 YAHOO.log("Logger output to the function console.log() has been enabled.");
412 * Surpresses native JavaScript errors and outputs to console. By default,
413 * Logger does not handle JavaScript window error events.
414 * NB: Not all browsers support the window.onerror event.
416 * @method handleWindowErrors
418 YAHOO.widget.Logger.handleWindowErrors = function() {
419 if(!YAHOO.widget.Logger._windowErrorsHandled) {
420 // Save any previously defined handler to call
422 YAHOO.widget.Logger._origOnWindowError = window.onerror;
424 window.onerror = YAHOO.widget.Logger._onWindowError;
425 YAHOO.widget.Logger._windowErrorsHandled = true;
426 YAHOO.log("Logger handling of window.onerror has been enabled.");
429 YAHOO.log("Logger handling of window.onerror had already been enabled.");
434 * Unsurpresses native JavaScript errors. By default,
435 * Logger does not handle JavaScript window error events.
436 * NB: Not all browsers support the window.onerror event.
438 * @method unhandleWindowErrors
440 YAHOO.widget.Logger.unhandleWindowErrors = function() {
441 if(YAHOO.widget.Logger._windowErrorsHandled) {
442 // Revert to any previously defined handler to call
443 if(YAHOO.widget.Logger._origOnWindowError) {
444 window.onerror = YAHOO.widget.Logger._origOnWindowError;
445 YAHOO.widget.Logger._origOnWindowError = null;
448 window.onerror = null;
450 YAHOO.widget.Logger._windowErrorsHandled = false;
451 YAHOO.log("Logger handling of window.onerror has been disabled.");
454 YAHOO.log("Logger handling of window.onerror had already been disabled.");
458 /////////////////////////////////////////////////////////////////////////////
462 /////////////////////////////////////////////////////////////////////////////
465 * Fired when a new category has been created.
467 * @event categoryCreateEvent
468 * @param sCategory {String} Category name.
470 YAHOO.widget.Logger.categoryCreateEvent =
471 new YAHOO.util.CustomEvent("categoryCreate", this, true);
474 * Fired when a new source has been named.
476 * @event sourceCreateEvent
477 * @param sSource {String} Source name.
479 YAHOO.widget.Logger.sourceCreateEvent =
480 new YAHOO.util.CustomEvent("sourceCreate", this, true);
483 * Fired when a new log message has been created.
486 * @param sMsg {String} Log message.
488 YAHOO.widget.Logger.newLogEvent = new YAHOO.util.CustomEvent("newLog", this, true);
491 * Fired when the Logger has been reset has been created.
493 * @event logResetEvent
495 YAHOO.widget.Logger.logResetEvent = new YAHOO.util.CustomEvent("logReset", this, true);
497 /////////////////////////////////////////////////////////////////////////////
501 /////////////////////////////////////////////////////////////////////////////
504 * Creates a new category of log messages and fires categoryCreateEvent.
506 * @method _createNewCategory
507 * @param sCategory {String} Category name.
510 YAHOO.widget.Logger._createNewCategory = function(sCategory) {
511 this.categories.push(sCategory);
512 this.categoryCreateEvent.fire(sCategory);
516 * Checks to see if a category has already been created.
518 * @method _isNewCategory
519 * @param sCategory {String} Category name.
520 * @return {Boolean} Returns true if category is unknown, else returns false.
523 YAHOO.widget.Logger._isNewCategory = function(sCategory) {
524 for(var i=0; i < this.categories.length; i++) {
525 if(sCategory == this.categories[i]) {
533 * Creates a new source of log messages and fires sourceCreateEvent.
535 * @method _createNewSource
536 * @param sSource {String} Source name.
539 YAHOO.widget.Logger._createNewSource = function(sSource) {
540 this.sources.push(sSource);
541 this.sourceCreateEvent.fire(sSource);
545 * Checks to see if a source already exists.
547 * @method _isNewSource
548 * @param sSource {String} Source name.
549 * @return {Boolean} Returns true if source is unknown, else returns false.
552 YAHOO.widget.Logger._isNewSource = function(sSource) {
554 for(var i=0; i < this.sources.length; i++) {
555 if(sSource == this.sources[i]) {
564 * Outputs a log message to global console.log() function.
566 * @method _printToBrowserConsole
567 * @param oEntry {Object} Log entry object.
570 YAHOO.widget.Logger._printToBrowserConsole = function(oEntry) {
571 if(window.console && console.log) {
572 var category = oEntry.category;
573 var label = oEntry.category.substring(0,4).toUpperCase();
575 var time = oEntry.time;
577 if (time.toLocaleTimeString) {
578 localTime = time.toLocaleTimeString();
581 localTime = time.toString();
584 var msecs = time.getTime();
585 var elapsedTime = (YAHOO.widget.Logger._lastTime) ?
586 (msecs - YAHOO.widget.Logger._lastTime) : 0;
587 YAHOO.widget.Logger._lastTime = msecs;
591 elapsedTime + "ms): " +
592 oEntry.source + ": ";
595 if (YAHOO.env.ua.webkit) {
596 output += oEntry.msg;
599 console.log(output, oEntry.msg);
603 /////////////////////////////////////////////////////////////////////////////
605 // Private event handlers
607 /////////////////////////////////////////////////////////////////////////////
610 * Handles logging of messages due to window error events.
612 * @method _onWindowError
613 * @param sMsg {String} The error message.
614 * @param sUrl {String} URL of the error.
615 * @param sLine {String} Line number of the error.
618 YAHOO.widget.Logger._onWindowError = function(sMsg,sUrl,sLine) {
619 // Logger is not in scope of this event handler
621 YAHOO.widget.Logger.log(sMsg+' ('+sUrl+', line '+sLine+')', "window");
622 if(YAHOO.widget.Logger._origOnWindowError) {
623 YAHOO.widget.Logger._origOnWindowError();
631 /////////////////////////////////////////////////////////////////////////////
635 /////////////////////////////////////////////////////////////////////////////
637 YAHOO.widget.Logger.log("Logger initialized");
640 /****************************************************************************/
641 /****************************************************************************/
642 /****************************************************************************/
644 var Logger = YAHOO.widget.Logger,
650 function make(el,props) {
651 el = d.createElement(el);
653 for (var p in props) {
654 if (props.hasOwnProperty(p)) {
663 * The LogReader class provides UI to read messages logged to YAHOO.widget.Logger.
667 * @param elContainer {HTMLElement} (optional) DOM element reference of an existing DIV.
668 * @param elContainer {String} (optional) String ID of an existing DIV.
669 * @param oConfigs {Object} (optional) Object literal of configuration params.
671 function LogReader(elContainer, oConfigs) {
672 this._sName = LogReader._index;
675 this._init.apply(this,arguments);
678 * Render the LogReader immediately upon instantiation. If set to false,
679 * you must call myLogReader.render() to generate the UI.
681 * @property autoRender
685 if (this.autoRender !== false) {
690 /////////////////////////////////////////////////////////////////////////////
692 // Static member variables
694 /////////////////////////////////////////////////////////////////////////////
695 YAHOO.lang.augmentObject(LogReader, {
697 * Internal class member to index multiple LogReader instances.
699 * @property _memberName
708 * Node template for the log entries
709 * @property ENTRY_TEMPLATE
711 * @type {HTMLElement}
712 * @default <code>pre</code> element with class yui-log-entry
714 ENTRY_TEMPLATE : (function () {
715 return make('pre',{ className: 'yui-log-entry' });
719 * Template used for innerHTML of verbose entry output.
720 * @property VERBOSE_TEMPLATE
722 * @default "<p><span class='{category}'>{label}</span>{totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>"
724 VERBOSE_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",
727 * Template used for innerHTML of compact entry output.
728 * @property BASIC_TEMPLATE
730 * @default "<p><span class='{category}'>{label}</span>{totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
732 BASIC_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
735 /////////////////////////////////////////////////////////////////////////////
737 // Public member variables
739 /////////////////////////////////////////////////////////////////////////////
741 LogReader.prototype = {
743 * Whether or not LogReader is enabled to output log messages.
745 * @property logReaderEnabled
749 logReaderEnabled : true,
752 * Public member to access CSS width of the LogReader container.
760 * Public member to access CSS height of the LogReader container.
768 * Public member to access CSS top position of the LogReader container.
776 * Public member to access CSS left position of the LogReader container.
784 * Public member to access CSS right position of the LogReader container.
792 * Public member to access CSS bottom position of the LogReader container.
800 * Public member to access CSS font size of the LogReader container.
808 * Whether or not the footer UI is enabled for the LogReader.
810 * @property footerEnabled
814 footerEnabled : true,
817 * Whether or not output is verbose (more readable). Setting to true will make
818 * output more compact (less readable).
820 * @property verboseOutput
824 verboseOutput : true,
827 * Custom output format for log messages. Defaults to null, which falls
828 * back to verboseOutput param deciding between LogReader.VERBOSE_TEMPLATE
829 * and LogReader.BASIC_TEMPLATE. Use bracketed place holders to mark where
830 * message info should go. Available place holder names include:
834 * <li>sourceAndDetail</li>
837 * <li>elapsedTime</li>
841 * @property entryFormat
848 * Whether or not newest message is printed on top.
850 * @property newestOnTop
856 * Output timeout buffer in milliseconds.
858 * @property outputBuffer
865 * Maximum number of messages a LogReader console will display.
867 * @property thresholdMax
874 * When a LogReader console reaches its thresholdMax, it will clear out messages
875 * and print out the latest thresholdMin number of messages.
877 * @property thresholdMin
884 * True when LogReader is in a collapsed state, false otherwise.
886 * @property isCollapsed
893 * True when LogReader is in a paused state, false otherwise.
902 * Enables draggable LogReader if DragDrop Utility is present.
904 * @property draggable
910 /////////////////////////////////////////////////////////////////////////////
914 /////////////////////////////////////////////////////////////////////////////
917 * Public accessor to the unique name of the LogReader instance.
920 * @return {String} Unique name of the LogReader instance.
922 toString : function() {
923 return "LogReader instance" + this._sName;
926 * Pauses output of log messages. While paused, log messages are not lost, but
927 * get saved to a buffer and then output upon resume of LogReader.
932 this.isPaused = true;
933 this._timeout = null;
934 this.logReaderEnabled = false;
935 if (this._btnPause) {
936 this._btnPause.value = "Resume";
941 * Resumes output of log messages, including outputting any log messages that
942 * have been saved to buffer while paused.
946 resume : function() {
947 this.isPaused = false;
948 this.logReaderEnabled = true;
950 if (this._btnPause) {
951 this._btnPause.value = "Pause";
956 * Adds the UI to the DOM, attaches event listeners, and bootstraps initial
961 render : function () {
966 this._initContainerEl();
968 this._initHeaderEl();
969 this._initConsoleEl();
970 this._initFooterEl();
972 this._initCategories();
975 this._initDragDrop();
977 // Subscribe to Logger custom events
978 Logger.newLogEvent.subscribe(this._onNewLog, this);
979 Logger.logResetEvent.subscribe(this._onReset, this);
981 Logger.categoryCreateEvent.subscribe(this._onCategoryCreate, this);
982 Logger.sourceCreateEvent.subscribe(this._onSourceCreate, this);
984 this.rendered = true;
990 * Removes the UI from the DOM entirely and detaches all event listeners.
991 * Implementers should note that Logger will still accumulate messages.
995 destroy : function () {
996 Event.purgeElement(this._elContainer,true);
997 this._elContainer.innerHTML = '';
998 this._elContainer.parentNode.removeChild(this._elContainer);
1000 this.rendered = false;
1004 * Hides UI of LogReader. Logging functionality is not disrupted.
1009 this._elContainer.style.display = "none";
1013 * Shows UI of LogReader. Logging functionality is not disrupted.
1018 this._elContainer.style.display = "block";
1022 * Collapses UI of LogReader. Logging functionality is not disrupted.
1026 collapse : function() {
1027 this._elConsole.style.display = "none";
1029 this._elFt.style.display = "none";
1031 this._btnCollapse.value = "Expand";
1032 this.isCollapsed = true;
1036 * Expands UI of LogReader. Logging functionality is not disrupted.
1040 expand : function() {
1041 this._elConsole.style.display = "block";
1043 this._elFt.style.display = "block";
1045 this._btnCollapse.value = "Collapse";
1046 this.isCollapsed = false;
1050 * Returns related checkbox element for given filter (i.e., category or source).
1052 * @method getCheckbox
1053 * @param {String} Category or source name.
1054 * @return {Array} Array of all filter checkboxes.
1056 getCheckbox : function(filter) {
1057 return this._filterCheckboxes[filter];
1061 * Returns array of enabled categories.
1063 * @method getCategories
1064 * @return {String[]} Array of enabled categories.
1066 getCategories : function() {
1067 return this._categoryFilters;
1071 * Shows log messages associated with given category.
1073 * @method showCategory
1074 * @param {String} Category name.
1076 showCategory : function(sCategory) {
1077 var filtersArray = this._categoryFilters;
1078 // Don't do anything if category is already enabled
1079 // Use Array.indexOf if available...
1080 if(filtersArray.indexOf) {
1081 if(filtersArray.indexOf(sCategory) > -1) {
1085 // ...or do it the old-fashioned way
1087 for(var i=0; i<filtersArray.length; i++) {
1088 if(filtersArray[i] === sCategory){
1094 this._categoryFilters.push(sCategory);
1096 var elCheckbox = this.getCheckbox(sCategory);
1098 elCheckbox.checked = true;
1103 * Hides log messages associated with given category.
1105 * @method hideCategory
1106 * @param {String} Category name.
1108 hideCategory : function(sCategory) {
1109 var filtersArray = this._categoryFilters;
1110 for(var i=0; i<filtersArray.length; i++) {
1111 if(sCategory == filtersArray[i]) {
1112 filtersArray.splice(i, 1);
1117 var elCheckbox = this.getCheckbox(sCategory);
1119 elCheckbox.checked = false;
1124 * Returns array of enabled sources.
1126 * @method getSources
1127 * @return {Array} Array of enabled sources.
1129 getSources : function() {
1130 return this._sourceFilters;
1134 * Shows log messages associated with given source.
1136 * @method showSource
1137 * @param {String} Source name.
1139 showSource : function(sSource) {
1140 var filtersArray = this._sourceFilters;
1141 // Don't do anything if category is already enabled
1142 // Use Array.indexOf if available...
1143 if(filtersArray.indexOf) {
1144 if(filtersArray.indexOf(sSource) > -1) {
1148 // ...or do it the old-fashioned way
1150 for(var i=0; i<filtersArray.length; i++) {
1151 if(sSource == filtersArray[i]){
1156 filtersArray.push(sSource);
1158 var elCheckbox = this.getCheckbox(sSource);
1160 elCheckbox.checked = true;
1165 * Hides log messages associated with given source.
1167 * @method hideSource
1168 * @param {String} Source name.
1170 hideSource : function(sSource) {
1171 var filtersArray = this._sourceFilters;
1172 for(var i=0; i<filtersArray.length; i++) {
1173 if(sSource == filtersArray[i]) {
1174 filtersArray.splice(i, 1);
1179 var elCheckbox = this.getCheckbox(sSource);
1181 elCheckbox.checked = false;
1186 * Does not delete any log messages, but clears all printed log messages from
1187 * the console. Log messages will be printed out again if user re-filters. The
1188 * static method YAHOO.widget.Logger.reset() should be called in order to
1189 * actually delete log messages.
1191 * @method clearConsole
1193 clearConsole : function() {
1194 // Clear the buffer of any pending messages
1195 this._timeout = null;
1197 this._consoleMsgCount = 0;
1199 var elConsole = this._elConsole;
1200 elConsole.innerHTML = '';
1204 * Updates title to given string.
1207 * @param sTitle {String} New title.
1209 setTitle : function(sTitle) {
1210 this._title.innerHTML = this.html2Text(sTitle);
1214 * Gets timestamp of the last log.
1216 * @method getLastTime
1217 * @return {Date} Timestamp of the last log.
1219 getLastTime : function() {
1220 return this._lastTime;
1223 formatMsg : function (entry) {
1224 var entryFormat = this.entryFormat || (this.verboseOutput ?
1225 LogReader.VERBOSE_TEMPLATE : LogReader.BASIC_TEMPLATE),
1227 category : entry.category,
1229 // Label for color-coded display
1230 label : entry.category.substring(0,4).toUpperCase(),
1232 sourceAndDetail : entry.sourceDetail ?
1233 entry.source + " " + entry.sourceDetail :
1236 // Escape HTML entities in the log message itself for output
1238 message : this.html2Text(entry.msg || entry.message || '')
1242 if (entry.time && entry.time.getTime) {
1243 info.localTime = entry.time.toLocaleTimeString ?
1244 entry.time.toLocaleTimeString() :
1245 entry.time.toString();
1247 // Calculate the elapsed time to be from the last item that
1248 // passed through the filter, not the absolute previous item
1250 info.elapsedTime = entry.time.getTime() - this.getLastTime();
1252 info.totalTime = entry.time.getTime() - Logger.getStartTime();
1255 var msg = LogReader.ENTRY_TEMPLATE.cloneNode(true);
1256 if (this.verboseOutput) {
1257 msg.className += ' yui-log-verbose';
1260 // Bug 2061169: Workaround for YAHOO.lang.substitute()
1261 msg.innerHTML = entryFormat.replace(/\{(\w+)\}/g,
1262 function (x, placeholder) {
1263 return (placeholder in info) ? info[placeholder] : '';
1270 * Converts input chars "<", ">", and "&" to HTML entities.
1273 * @param sHtml {String} String to convert.
1276 html2Text : function(sHtml) {
1279 return sHtml.replace(/&/g, "&").
1280 replace(/</g, "<").
1281 replace(/>/g, ">");
1286 /////////////////////////////////////////////////////////////////////////////
1288 // Private member variables
1290 /////////////////////////////////////////////////////////////////////////////
1293 * Name of LogReader instance.
1303 * A class member shared by all LogReaders if a container needs to be
1304 * created during instantiation. Will be null if a container element never needs to
1305 * be created on the fly, such as when the implementer passes in their own element.
1307 * @property _elDefaultContainer
1311 //YAHOO.widget.LogReader._elDefaultContainer = null;
1314 * Buffer of log message objects for batch output.
1323 * Number of log messages output to console.
1325 * @property _consoleMsgCount
1330 _consoleMsgCount : 0,
1333 * Date of last output log message.
1335 * @property _lastTime
1342 * Batched output timeout ID.
1344 * @property _timeout
1351 * Hash of filters and their related checkbox elements.
1353 * @property _filterCheckboxes
1357 _filterCheckboxes : null,
1360 * Array of filters for log message categories.
1362 * @property _categoryFilters
1366 _categoryFilters : null,
1369 * Array of filters for log message sources.
1371 * @property _sourceFilters
1375 _sourceFilters : null,
1378 * LogReader container element.
1380 * @property _elContainer
1384 _elContainer : null,
1387 * LogReader header element.
1396 * LogReader collapse element.
1398 * @property _elCollapse
1405 * LogReader collapse button element.
1407 * @property _btnCollapse
1411 _btnCollapse : null,
1414 * LogReader title header element.
1423 * LogReader console element.
1425 * @property _elConsole
1432 * LogReader footer element.
1441 * LogReader buttons container element.
1450 * Container element for LogReader category filter checkboxes.
1452 * @property _elCategoryFilters
1456 _elCategoryFilters : null,
1459 * Container element for LogReader source filter checkboxes.
1461 * @property _elSourceFilters
1465 _elSourceFilters : null,
1468 * LogReader pause button element.
1470 * @property _btnPause
1477 * Clear button element.
1479 * @property _btnClear
1485 /////////////////////////////////////////////////////////////////////////////
1489 /////////////////////////////////////////////////////////////////////////////
1492 * Initializes the instance's message buffer, start time, etc
1495 * @param container {String|HTMLElement} (optional) the render target
1496 * @param config {Object} (optional) instance configuration
1499 _init : function (container, config) {
1501 this._buffer = []; // output buffer
1502 this._filterCheckboxes = {}; // pointers to checkboxes
1503 this._lastTime = Logger.getStartTime(); // timestamp of last log message to console
1505 // Parse config vars here
1506 if (config && (config.constructor == Object)) {
1507 for(var param in config) {
1508 if (config.hasOwnProperty(param)) {
1509 this[param] = config[param];
1514 this._elContainer = Dom.get(container);
1516 YAHOO.log("LogReader initialized", null, this.toString());
1520 * Initializes the primary container element.
1522 * @method _initContainerEl
1525 _initContainerEl : function() {
1527 // Default the container if unset or not a div
1528 if(!this._elContainer || !/div$/i.test(this._elContainer.tagName)) {
1529 this._elContainer = d.body.insertBefore(make("div"),d.body.firstChild);
1530 // Only position absolutely if an in-DOM element is not supplied
1531 Dom.addClass(this._elContainer,"yui-log-container");
1534 Dom.addClass(this._elContainer,"yui-log");
1536 // If implementer has provided container values, trust and set those
1537 var style = this._elContainer.style,
1538 styleProps = ['width','right','top','fontSize'],
1541 for (i = styleProps.length - 1; i >= 0; --i) {
1542 prop = styleProps[i];
1544 style[prop] = this[prop];
1549 style.left = this.left;
1550 style.right = "auto";
1553 style.bottom = this.bottom;
1557 // Opera needs a little prodding to reflow sometimes
1558 if (YAHOO.env.ua.opera) {
1565 * Initializes the header element.
1567 * @method _initHeaderEl
1570 _initHeaderEl : function() {
1571 // Destroy header if present
1573 // Unhook DOM events
1574 Event.purgeElement(this._elHd, true);
1576 // Remove DOM elements
1577 this._elHd.innerHTML = "";
1581 // TODO: refactor this into an innerHTML
1582 this._elHd = make("div",{
1583 id: 'yui-log-hd' + this._sName,
1584 className: "yui-log-hd"
1587 this._elCollapse = make("div",{ className: 'yui-log-btns' });
1589 this._btnCollapse = make("input",{
1591 className: 'yui-log-button',
1594 Event.on(this._btnCollapse,'click',this._onClickCollapseBtn,this);
1597 this._title = make("h4",{ innerHTML : "Logger Console" });
1599 this._elCollapse.appendChild(this._btnCollapse);
1600 this._elHd.appendChild(this._elCollapse);
1601 this._elHd.appendChild(this._title);
1602 this._elContainer.appendChild(this._elHd);
1606 * Initializes the console element.
1608 * @method _initConsoleEl
1611 _initConsoleEl : function() {
1613 if(this._elConsole) {
1614 // Unhook DOM events
1615 Event.purgeElement(this._elConsole, true);
1617 // Remove DOM elements
1618 this._elConsole.innerHTML = "";
1622 this._elConsole = make("div", { className: "yui-log-bd" });
1624 // If implementer has provided console, trust and set those
1626 this._elConsole.style.height = this.height;
1629 this._elContainer.appendChild(this._elConsole);
1633 * Initializes the footer element.
1635 * @method _initFooterEl
1638 _initFooterEl : function() {
1639 // Don't create footer elements if footer is disabled
1640 if(this.footerEnabled) {
1643 // Unhook DOM events
1644 Event.purgeElement(this._elFt, true);
1646 // Remove DOM elements
1647 this._elFt.innerHTML = "";
1650 // TODO: use innerHTML
1651 this._elFt = make("div",{ className: "yui-log-ft" });
1652 this._elBtns = make("div", { className: "yui-log-btns" });
1653 this._btnPause = make("input", {
1655 className: "yui-log-button",
1659 Event.on(this._btnPause,'click',this._onClickPauseBtn,this);
1661 this._btnClear = make("input", {
1663 className: "yui-log-button",
1667 Event.on(this._btnClear,'click',this._onClickClearBtn,this);
1669 this._elCategoryFilters = make("div", { className: "yui-log-categoryfilters" });
1670 this._elSourceFilters = make("div", { className: "yui-log-sourcefilters" });
1672 this._elBtns.appendChild(this._btnPause);
1673 this._elBtns.appendChild(this._btnClear);
1674 this._elFt.appendChild(this._elBtns);
1675 this._elFt.appendChild(this._elCategoryFilters);
1676 this._elFt.appendChild(this._elSourceFilters);
1677 this._elContainer.appendChild(this._elFt);
1682 * Initializes Drag and Drop on the header element.
1684 * @method _initDragDrop
1687 _initDragDrop : function() {
1688 // If Drag and Drop utility is available...
1689 // ...and draggable is true...
1690 // ...then make the header draggable
1691 if(u.DD && this.draggable && this._elHd) {
1692 var ylog_dd = new u.DD(this._elContainer);
1693 ylog_dd.setHandleElId(this._elHd.id);
1694 //TODO: use class name
1695 this._elHd.style.cursor = "move";
1700 * Initializes category filters.
1702 * @method _initCategories
1705 _initCategories : function() {
1706 // Initialize category filters
1707 this._categoryFilters = [];
1708 var aInitialCategories = Logger.categories;
1710 for(var j=0; j < aInitialCategories.length; j++) {
1711 var sCategory = aInitialCategories[j];
1713 // Add category to the internal array of filters
1714 this._categoryFilters.push(sCategory);
1716 // Add checkbox element if UI is enabled
1717 if(this._elCategoryFilters) {
1718 this._createCategoryCheckbox(sCategory);
1724 * Initializes source filters.
1726 * @method _initSources
1729 _initSources : function() {
1730 // Initialize source filters
1731 this._sourceFilters = [];
1732 var aInitialSources = Logger.sources;
1734 for(var j=0; j < aInitialSources.length; j++) {
1735 var sSource = aInitialSources[j];
1737 // Add source to the internal array of filters
1738 this._sourceFilters.push(sSource);
1740 // Add checkbox element if UI is enabled
1741 if(this._elSourceFilters) {
1742 this._createSourceCheckbox(sSource);
1748 * Creates the UI for a category filter in the LogReader footer element.
1750 * @method _createCategoryCheckbox
1751 * @param sCategory {String} Category name.
1754 _createCategoryCheckbox : function(sCategory) {
1756 var filter = make("span",{ className: "yui-log-filtergrp" }),
1757 check = make("input", {
1758 id: "yui-log-filter-" + sCategory + this._sName,
1759 className: "yui-log-filter-" + sCategory,
1763 label = make("label", {
1765 className: sCategory,
1766 innerHTML: sCategory
1770 // Subscribe to the click event
1771 Event.on(check,'click',this._onCheckCategory,this);
1773 this._filterCheckboxes[sCategory] = check;
1775 // Append el at the end so IE 5.5 can set "type" attribute
1776 // and THEN set checked property
1777 filter.appendChild(check);
1778 filter.appendChild(label);
1779 this._elCategoryFilters.appendChild(filter);
1780 check.checked = true;
1785 * Creates a checkbox in the LogReader footer element to filter by source.
1787 * @method _createSourceCheckbox
1788 * @param sSource {String} Source name.
1791 _createSourceCheckbox : function(sSource) {
1793 var filter = make("span",{ className: "yui-log-filtergrp" }),
1794 check = make("input", {
1795 id: "yui-log-filter-" + sSource + this._sName,
1796 className: "yui-log-filter-" + sSource,
1800 label = make("label", {
1807 // Subscribe to the click event
1808 Event.on(check,'click',this._onCheckSource,this);
1810 this._filterCheckboxes[sSource] = check;
1812 // Append el at the end so IE 5.5 can set "type" attribute
1813 // and THEN set checked property
1814 filter.appendChild(check);
1815 filter.appendChild(label);
1816 this._elSourceFilters.appendChild(filter);
1817 check.checked = true;
1822 * Reprints all log messages in the stack through filters.
1824 * @method _filterLogs
1827 _filterLogs : function() {
1828 // Reprint stack with new filters
1829 if (this._elConsole !== null) {
1830 this.clearConsole();
1831 this._printToConsole(Logger.getStack());
1836 * Sends buffer of log messages to output and clears buffer.
1838 * @method _printBuffer
1841 _printBuffer : function() {
1842 this._timeout = null;
1844 if(this._elConsole !== null) {
1845 var thresholdMax = this.thresholdMax;
1846 thresholdMax = (thresholdMax && !isNaN(thresholdMax)) ? thresholdMax : 500;
1847 if(this._consoleMsgCount < thresholdMax) {
1849 for (var i=0; i<this._buffer.length; i++) {
1850 entries[i] = this._buffer[i];
1853 this._printToConsole(entries);
1859 if(!this.newestOnTop) {
1860 this._elConsole.scrollTop = this._elConsole.scrollHeight;
1866 * Cycles through an array of log messages, and outputs each one to the console
1867 * if its category has not been filtered out.
1869 * @method _printToConsole
1870 * @param aEntries {Object[]} Array of LogMsg objects to output to console.
1873 _printToConsole : function(aEntries) {
1874 // Manage the number of messages displayed in the console
1875 var entriesLen = aEntries.length,
1876 df = d.createDocumentFragment(),
1878 thresholdMin = this.thresholdMin,
1879 sourceFiltersLen = this._sourceFilters.length,
1880 categoryFiltersLen = this._categoryFilters.length,
1884 if(isNaN(thresholdMin) || (thresholdMin > this.thresholdMax)) {
1887 entriesStartIndex = (entriesLen > thresholdMin) ? (entriesLen - thresholdMin) : 0;
1889 // Iterate through all log entries
1890 for(i=entriesStartIndex; i<entriesLen; i++) {
1891 // Print only the ones that filter through
1892 var okToPrint = false,
1893 okToFilterCats = false,
1894 entry = aEntries[i],
1895 source = entry.source,
1896 category = entry.category;
1898 for(j=0; j<sourceFiltersLen; j++) {
1899 if(source == this._sourceFilters[j]) {
1900 okToFilterCats = true;
1904 if(okToFilterCats) {
1905 for(j=0; j<categoryFiltersLen; j++) {
1906 if(category == this._categoryFilters[j]) {
1913 // Start from 0ms elapsed time
1914 if (this._consoleMsgCount === 0) {
1915 this._lastTime = entry.time.getTime();
1918 msg = this.formatMsg(entry);
1919 if (typeof msg === 'string') {
1920 msgHTML[msgHTML.length] = msg;
1922 df.insertBefore(msg, this.newestOnTop ?
1923 df.firstChild || null : null);
1925 this._consoleMsgCount++;
1926 this._lastTime = entry.time.getTime();
1930 if (msgHTML.length) {
1931 msgHTML.splice(0,0,this._elConsole.innerHTML);
1932 this._elConsole.innerHTML = this.newestOnTop ?
1933 msgHTML.reverse().join('') :
1935 } else if (df.firstChild) {
1936 this._elConsole.insertBefore(df, this.newestOnTop ?
1937 this._elConsole.firstChild || null : null);
1941 /////////////////////////////////////////////////////////////////////////////
1943 // Private event handlers
1945 /////////////////////////////////////////////////////////////////////////////
1948 * Handles Logger's categoryCreateEvent.
1950 * @method _onCategoryCreate
1951 * @param sType {String} The event.
1952 * @param aArgs {Object[]} Data passed from event firer.
1953 * @param oSelf {Object} The LogReader instance.
1956 _onCategoryCreate : function(sType, aArgs, oSelf) {
1957 var category = aArgs[0];
1959 // Add category to the internal array of filters
1960 oSelf._categoryFilters.push(category);
1963 oSelf._createCategoryCheckbox(category);
1968 * Handles Logger's sourceCreateEvent.
1970 * @method _onSourceCreate
1971 * @param sType {String} The event.
1972 * @param aArgs {Object[]} Data passed from event firer.
1973 * @param oSelf {Object} The LogReader instance.
1976 _onSourceCreate : function(sType, aArgs, oSelf) {
1977 var source = aArgs[0];
1979 // Add source to the internal array of filters
1980 oSelf._sourceFilters.push(source);
1983 oSelf._createSourceCheckbox(source);
1988 * Handles check events on the category filter checkboxes.
1990 * @method _onCheckCategory
1991 * @param v {HTMLEvent} The click event.
1992 * @param oSelf {Object} The LogReader instance.
1995 _onCheckCategory : function(v, oSelf) {
1996 var category = this.category;
1998 oSelf.hideCategory(category);
2001 oSelf.showCategory(category);
2006 * Handles check events on the category filter checkboxes.
2008 * @method _onCheckSource
2009 * @param v {HTMLEvent} The click event.
2010 * @param oSelf {Object} The LogReader instance.
2013 _onCheckSource : function(v, oSelf) {
2014 var source = this.source;
2016 oSelf.hideSource(source);
2019 oSelf.showSource(source);
2024 * Handles click events on the collapse button.
2026 * @method _onClickCollapseBtn
2027 * @param v {HTMLEvent} The click event.
2028 * @param oSelf {Object} The LogReader instance
2031 _onClickCollapseBtn : function(v, oSelf) {
2032 if(!oSelf.isCollapsed) {
2041 * Handles click events on the pause button.
2043 * @method _onClickPauseBtn
2044 * @param v {HTMLEvent} The click event.
2045 * @param oSelf {Object} The LogReader instance.
2048 _onClickPauseBtn : function(v, oSelf) {
2049 if(!oSelf.isPaused) {
2058 * Handles click events on the clear button.
2060 * @method _onClickClearBtn
2061 * @param v {HTMLEvent} The click event.
2062 * @param oSelf {Object} The LogReader instance.
2065 _onClickClearBtn : function(v, oSelf) {
2066 oSelf.clearConsole();
2070 * Handles Logger's newLogEvent.
2073 * @param sType {String} The event.
2074 * @param aArgs {Object[]} Data passed from event firer.
2075 * @param oSelf {Object} The LogReader instance.
2078 _onNewLog : function(sType, aArgs, oSelf) {
2079 var logEntry = aArgs[0];
2080 oSelf._buffer.push(logEntry);
2082 if (oSelf.logReaderEnabled === true && oSelf._timeout === null) {
2083 oSelf._timeout = setTimeout(function(){oSelf._printBuffer();}, oSelf.outputBuffer);
2088 * Handles Logger's resetEvent.
2091 * @param sType {String} The event.
2092 * @param aArgs {Object[]} Data passed from event firer.
2093 * @param oSelf {Object} The LogReader instance.
2096 _onReset : function(sType, aArgs, oSelf) {
2097 oSelf._filterLogs();
2101 YAHOO.widget.LogReader = LogReader;
2104 YAHOO.register("logger", YAHOO.widget.Logger, {version: "2.8.0r4", build: "2449"});