2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
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.
107 * Note: the LogReader adds the message and category to the DOM as HTML.
110 * @param sMsg {HTML} The log message.
111 * @param sCategory {HTML} Category name.
113 YAHOO.widget.LogWriter.prototype.log = function(sMsg, sCategory) {
114 YAHOO.widget.Logger.log(sMsg, sCategory, this._source);
118 * Public accessor to get the source name.
121 * @return {String} The LogWriter source.
123 YAHOO.widget.LogWriter.prototype.getSource = function() {
128 * Public accessor to set the source name.
131 * @param sSource {String} Source of LogWriter instance.
133 YAHOO.widget.LogWriter.prototype.setSource = function(sSource) {
135 YAHOO.log("Could not set source due to invalid source.", "error", this.toString());
139 this._source = sSource;
143 /////////////////////////////////////////////////////////////////////////////
145 // Private member variables
147 /////////////////////////////////////////////////////////////////////////////
150 * Source of the LogWriter instance.
156 YAHOO.widget.LogWriter.prototype._source = null;
161 * The Logger widget provides a simple way to read or write log messages in
162 * JavaScript code. Integration with the YUI Library's debug builds allow
163 * implementers to access under-the-hood events, errors, and debugging messages.
164 * Output may be read through a LogReader console and/or output to a browser
168 * @requires yahoo, event, dom
170 * @namespace YAHOO.widget
171 * @title Logger Widget
174 /****************************************************************************/
175 /****************************************************************************/
176 /****************************************************************************/
179 if(!YAHOO.widget.Logger) {
181 * The singleton Logger class provides core log management functionality. Saves
182 * logs written through the global YAHOO.log function or written by a LogWriter
183 * instance. Provides access to logs for reading by a LogReader instance or
184 * native browser console such as the Firebug extension to Firefox or Safari's
185 * JavaScript console through integration with the console.log() method.
190 YAHOO.widget.Logger = {
191 // Initialize properties
193 _browserConsoleEnabled: false,
194 categories: ["info","warn","error","time","window"],
196 _stack: [], // holds all log msgs
197 maxStackEntries: 2500,
198 _startTime: new Date().getTime(), // static start timestamp
199 _lastTime: null, // timestamp of last logged message
200 _windowErrorsHandled: false,
201 _origOnWindowError: null
204 /////////////////////////////////////////////////////////////////////////////
208 /////////////////////////////////////////////////////////////////////////////
210 * True if Logger is enabled, false otherwise.
212 * @property loggerEnabled
219 * Array of categories.
221 * @property categories
224 * @default ["info","warn","error","time","window"]
233 * @default ["global"]
237 * Upper limit on size of internal stack.
239 * @property maxStackEntries
245 /////////////////////////////////////////////////////////////////////////////
247 // Private properties
249 /////////////////////////////////////////////////////////////////////////////
251 * Internal property to track whether output to browser console is enabled.
253 * @property _browserConsoleEnabled
261 * Array to hold all log messages.
269 * Static timestamp of Logger initialization.
271 * @property _startTime
277 * Timestamp of last logged message.
279 * @property _lastTime
284 /////////////////////////////////////////////////////////////////////////////
288 /////////////////////////////////////////////////////////////////////////////
290 * Saves a log message to the stack and fires newLogEvent. If the log message is
291 * assigned to an unknown category, creates a new category. If the log message is
292 * from an unknown source, creates a new source. If browser console is enabled,
293 * outputs the log message to browser console.
294 * Note: the LogReader adds the message, category, and source to the DOM
298 * @param sMsg {HTML} The log message.
299 * @param sCategory {HTML} Category of log message, or null.
300 * @param sSource {HTML} Source of LogWriter, or null if global.
302 YAHOO.widget.Logger.log = function(sMsg, sCategory, sSource) {
303 if(this.loggerEnabled) {
305 sCategory = "info"; // default category
308 sCategory = sCategory.toLocaleLowerCase();
309 if(this._isNewCategory(sCategory)) {
310 this._createNewCategory(sCategory);
313 var sClass = "global"; // default source
316 var spaceIndex = sSource.indexOf(" ");
318 // Substring until first space
319 sClass = sSource.substring(0,spaceIndex);
320 // The rest of the source
321 sDetail = sSource.substring(spaceIndex,sSource.length);
326 if(this._isNewSource(sClass)) {
327 this._createNewSource(sClass);
331 var timestamp = new Date();
332 var logEntry = new YAHOO.widget.LogMsg({
337 sourceDetail: sDetail
340 var stack = this._stack;
341 var maxStackEntries = this.maxStackEntries;
342 if(maxStackEntries && !isNaN(maxStackEntries) &&
343 (stack.length >= maxStackEntries)) {
346 stack.push(logEntry);
347 this.newLogEvent.fire(logEntry);
349 if(this._browserConsoleEnabled) {
350 this._printToBrowserConsole(logEntry);
360 * Resets internal stack and startTime, enables Logger, and fires logResetEvent.
364 YAHOO.widget.Logger.reset = function() {
366 this._startTime = new Date().getTime();
367 this.loggerEnabled = true;
368 this.log("Logger reset");
369 this.logResetEvent.fire();
373 * Public accessor to internal stack of log message objects.
376 * @return {Object[]} Array of log message objects.
378 YAHOO.widget.Logger.getStack = function() {
383 * Public accessor to internal start time.
385 * @method getStartTime
386 * @return {Date} Internal date of when Logger singleton was initialized.
388 YAHOO.widget.Logger.getStartTime = function() {
389 return this._startTime;
393 * Disables output to the browser's global console.log() function, which is used
394 * by the Firebug extension to Firefox as well as Safari.
396 * @method disableBrowserConsole
398 YAHOO.widget.Logger.disableBrowserConsole = function() {
399 YAHOO.log("Logger output to the function console.log() has been disabled.");
400 this._browserConsoleEnabled = false;
404 * Enables output to the browser's global console.log() function, which is used
405 * by the Firebug extension to Firefox as well as Safari.
407 * @method enableBrowserConsole
409 YAHOO.widget.Logger.enableBrowserConsole = function() {
410 this._browserConsoleEnabled = true;
411 YAHOO.log("Logger output to the function console.log() has been enabled.");
415 * Surpresses native JavaScript errors and outputs to console. By default,
416 * Logger does not handle JavaScript window error events.
417 * NB: Not all browsers support the window.onerror event.
419 * @method handleWindowErrors
421 YAHOO.widget.Logger.handleWindowErrors = function() {
422 if(!YAHOO.widget.Logger._windowErrorsHandled) {
423 // Save any previously defined handler to call
425 YAHOO.widget.Logger._origOnWindowError = window.onerror;
427 window.onerror = YAHOO.widget.Logger._onWindowError;
428 YAHOO.widget.Logger._windowErrorsHandled = true;
429 YAHOO.log("Logger handling of window.onerror has been enabled.");
432 YAHOO.log("Logger handling of window.onerror had already been enabled.");
437 * Unsurpresses native JavaScript errors. By default,
438 * Logger does not handle JavaScript window error events.
439 * NB: Not all browsers support the window.onerror event.
441 * @method unhandleWindowErrors
443 YAHOO.widget.Logger.unhandleWindowErrors = function() {
444 if(YAHOO.widget.Logger._windowErrorsHandled) {
445 // Revert to any previously defined handler to call
446 if(YAHOO.widget.Logger._origOnWindowError) {
447 window.onerror = YAHOO.widget.Logger._origOnWindowError;
448 YAHOO.widget.Logger._origOnWindowError = null;
451 window.onerror = null;
453 YAHOO.widget.Logger._windowErrorsHandled = false;
454 YAHOO.log("Logger handling of window.onerror has been disabled.");
457 YAHOO.log("Logger handling of window.onerror had already been disabled.");
461 /////////////////////////////////////////////////////////////////////////////
465 /////////////////////////////////////////////////////////////////////////////
468 * Fired when a new category has been created.
470 * @event categoryCreateEvent
471 * @param sCategory {String} Category name.
473 YAHOO.widget.Logger.categoryCreateEvent =
474 new YAHOO.util.CustomEvent("categoryCreate", this, true);
477 * Fired when a new source has been named.
479 * @event sourceCreateEvent
480 * @param sSource {String} Source name.
482 YAHOO.widget.Logger.sourceCreateEvent =
483 new YAHOO.util.CustomEvent("sourceCreate", this, true);
486 * Fired when a new log message has been created.
489 * @param sMsg {String} Log message.
491 YAHOO.widget.Logger.newLogEvent = new YAHOO.util.CustomEvent("newLog", this, true);
494 * Fired when the Logger has been reset has been created.
496 * @event logResetEvent
498 YAHOO.widget.Logger.logResetEvent = new YAHOO.util.CustomEvent("logReset", this, true);
500 /////////////////////////////////////////////////////////////////////////////
504 /////////////////////////////////////////////////////////////////////////////
507 * Creates a new category of log messages and fires categoryCreateEvent.
509 * @method _createNewCategory
510 * @param sCategory {String} Category name.
513 YAHOO.widget.Logger._createNewCategory = function(sCategory) {
514 this.categories.push(sCategory);
515 this.categoryCreateEvent.fire(sCategory);
519 * Checks to see if a category has already been created.
521 * @method _isNewCategory
522 * @param sCategory {String} Category name.
523 * @return {Boolean} Returns true if category is unknown, else returns false.
526 YAHOO.widget.Logger._isNewCategory = function(sCategory) {
527 for(var i=0; i < this.categories.length; i++) {
528 if(sCategory == this.categories[i]) {
536 * Creates a new source of log messages and fires sourceCreateEvent.
538 * @method _createNewSource
539 * @param sSource {String} Source name.
542 YAHOO.widget.Logger._createNewSource = function(sSource) {
543 this.sources.push(sSource);
544 this.sourceCreateEvent.fire(sSource);
548 * Checks to see if a source already exists.
550 * @method _isNewSource
551 * @param sSource {String} Source name.
552 * @return {Boolean} Returns true if source is unknown, else returns false.
555 YAHOO.widget.Logger._isNewSource = function(sSource) {
557 for(var i=0; i < this.sources.length; i++) {
558 if(sSource == this.sources[i]) {
567 * Outputs a log message to global console.log() function.
569 * @method _printToBrowserConsole
570 * @param oEntry {Object} Log entry object.
573 YAHOO.widget.Logger._printToBrowserConsole = function(oEntry) {
574 if ((window.console && console.log) ||
575 (window.opera && opera.postError)) {
576 var category = oEntry.category;
577 var label = oEntry.category.substring(0,4).toUpperCase();
579 var time = oEntry.time;
581 if (time.toLocaleTimeString) {
582 localTime = time.toLocaleTimeString();
585 localTime = time.toString();
588 var msecs = time.getTime();
589 var elapsedTime = (YAHOO.widget.Logger._lastTime) ?
590 (msecs - YAHOO.widget.Logger._lastTime) : 0;
591 YAHOO.widget.Logger._lastTime = msecs;
595 elapsedTime + "ms): " +
596 oEntry.source + ": ";
598 if (window.console) {
599 console.log(output, oEntry.msg);
601 opera.postError(output + oEntry.msg);
606 /////////////////////////////////////////////////////////////////////////////
608 // Private event handlers
610 /////////////////////////////////////////////////////////////////////////////
613 * Handles logging of messages due to window error events.
615 * @method _onWindowError
616 * @param sMsg {String} The error message.
617 * @param sUrl {String} URL of the error.
618 * @param sLine {String} Line number of the error.
621 YAHOO.widget.Logger._onWindowError = function(sMsg,sUrl,sLine) {
622 // Logger is not in scope of this event handler
624 YAHOO.widget.Logger.log(sMsg+' ('+sUrl+', line '+sLine+')', "window");
625 if(YAHOO.widget.Logger._origOnWindowError) {
626 YAHOO.widget.Logger._origOnWindowError();
634 /////////////////////////////////////////////////////////////////////////////
638 /////////////////////////////////////////////////////////////////////////////
640 YAHOO.widget.Logger.log("Logger initialized");
643 /****************************************************************************/
644 /****************************************************************************/
645 /****************************************************************************/
647 var Logger = YAHOO.widget.Logger,
653 function make(el,props) {
654 el = d.createElement(el);
656 for (var p in props) {
657 if (props.hasOwnProperty(p)) {
666 * The LogReader class provides UI to read messages logged to YAHOO.widget.Logger.
670 * @param elContainer {HTMLElement} (optional) DOM element reference of an existing DIV.
671 * @param elContainer {String} (optional) String ID of an existing DIV.
672 * @param oConfigs {Object} (optional) Object literal of configuration params.
674 function LogReader(elContainer, oConfigs) {
675 this._sName = LogReader._index;
678 this._init.apply(this,arguments);
681 * Render the LogReader immediately upon instantiation. If set to false,
682 * you must call myLogReader.render() to generate the UI.
684 * @property autoRender
688 if (this.autoRender !== false) {
693 /////////////////////////////////////////////////////////////////////////////
695 // Static member variables
697 /////////////////////////////////////////////////////////////////////////////
698 YAHOO.lang.augmentObject(LogReader, {
700 * Internal class member to index multiple LogReader instances.
702 * @property _memberName
711 * Node template for the log entries
712 * @property ENTRY_TEMPLATE
714 * @type {HTMLElement}
715 * @default <code>pre</code> element with class yui-log-entry
717 ENTRY_TEMPLATE : (function () {
718 return make('pre',{ className: 'yui-log-entry' });
722 * Template used for innerHTML of verbose entry output.
723 * @property VERBOSE_TEMPLATE
725 * @default "<p><span class='{category}'>{label}</span>{totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>"
727 VERBOSE_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",
730 * Template used for innerHTML of compact entry output.
731 * @property BASIC_TEMPLATE
733 * @default "<p><span class='{category}'>{label}</span>{totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
735 BASIC_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
738 /////////////////////////////////////////////////////////////////////////////
740 // Public member variables
742 /////////////////////////////////////////////////////////////////////////////
744 LogReader.prototype = {
746 * Whether or not LogReader is enabled to output log messages.
748 * @property logReaderEnabled
752 logReaderEnabled : true,
755 * Public member to access CSS width of the LogReader container.
763 * Public member to access CSS height of the LogReader container.
771 * Public member to access CSS top position of the LogReader container.
779 * Public member to access CSS left position of the LogReader container.
787 * Public member to access CSS right position of the LogReader container.
795 * Public member to access CSS bottom position of the LogReader container.
803 * Public member to access CSS font size of the LogReader container.
811 * Whether or not the footer UI is enabled for the LogReader.
813 * @property footerEnabled
817 footerEnabled : true,
820 * Whether or not output is verbose (more readable). Setting to true will make
821 * output more compact (less readable).
823 * @property verboseOutput
827 verboseOutput : true,
830 * Custom output format for log messages. Defaults to null, which falls
831 * back to verboseOutput param deciding between LogReader.VERBOSE_TEMPLATE
832 * and LogReader.BASIC_TEMPLATE. Use bracketed place holders to mark where
833 * message info should go. Available place holder names include:
837 * <li>sourceAndDetail</li>
840 * <li>elapsedTime</li>
844 * @property entryFormat
851 * Whether or not newest message is printed on top.
853 * @property newestOnTop
859 * Output timeout buffer in milliseconds.
861 * @property outputBuffer
868 * Maximum number of messages a LogReader console will display.
870 * @property thresholdMax
877 * When a LogReader console reaches its thresholdMax, it will clear out messages
878 * and print out the latest thresholdMin number of messages.
880 * @property thresholdMin
887 * True when LogReader is in a collapsed state, false otherwise.
889 * @property isCollapsed
896 * True when LogReader is in a paused state, false otherwise.
905 * Enables draggable LogReader if DragDrop Utility is present.
907 * @property draggable
913 /////////////////////////////////////////////////////////////////////////////
917 /////////////////////////////////////////////////////////////////////////////
920 * Public accessor to the unique name of the LogReader instance.
923 * @return {String} Unique name of the LogReader instance.
925 toString : function() {
926 return "LogReader instance" + this._sName;
929 * Pauses output of log messages. While paused, log messages are not lost, but
930 * get saved to a buffer and then output upon resume of LogReader.
935 this.isPaused = true;
936 this._timeout = null;
937 this.logReaderEnabled = false;
938 if (this._btnPause) {
939 this._btnPause.value = "Resume";
944 * Resumes output of log messages, including outputting any log messages that
945 * have been saved to buffer while paused.
949 resume : function() {
950 this.isPaused = false;
951 this.logReaderEnabled = true;
953 if (this._btnPause) {
954 this._btnPause.value = "Pause";
959 * Adds the UI to the DOM, attaches event listeners, and bootstraps initial
964 render : function () {
969 this._initContainerEl();
971 this._initHeaderEl();
972 this._initConsoleEl();
973 this._initFooterEl();
975 this._initCategories();
978 this._initDragDrop();
980 // Subscribe to Logger custom events
981 Logger.newLogEvent.subscribe(this._onNewLog, this);
982 Logger.logResetEvent.subscribe(this._onReset, this);
984 Logger.categoryCreateEvent.subscribe(this._onCategoryCreate, this);
985 Logger.sourceCreateEvent.subscribe(this._onSourceCreate, this);
987 this.rendered = true;
993 * Removes the UI from the DOM entirely and detaches all event listeners.
994 * Implementers should note that Logger will still accumulate messages.
998 destroy : function () {
999 Event.purgeElement(this._elContainer,true);
1000 this._elContainer.innerHTML = '';
1001 this._elContainer.parentNode.removeChild(this._elContainer);
1003 this.rendered = false;
1007 * Hides UI of LogReader. Logging functionality is not disrupted.
1012 this._elContainer.style.display = "none";
1016 * Shows UI of LogReader. Logging functionality is not disrupted.
1021 this._elContainer.style.display = "block";
1025 * Collapses UI of LogReader. Logging functionality is not disrupted.
1029 collapse : function() {
1030 this._elConsole.style.display = "none";
1032 this._elFt.style.display = "none";
1034 this._btnCollapse.value = "Expand";
1035 this.isCollapsed = true;
1039 * Expands UI of LogReader. Logging functionality is not disrupted.
1043 expand : function() {
1044 this._elConsole.style.display = "block";
1046 this._elFt.style.display = "block";
1048 this._btnCollapse.value = "Collapse";
1049 this.isCollapsed = false;
1053 * Returns related checkbox element for given filter (i.e., category or source).
1055 * @method getCheckbox
1056 * @param {String} Category or source name.
1057 * @return {Array} Array of all filter checkboxes.
1059 getCheckbox : function(filter) {
1060 return this._filterCheckboxes[filter];
1064 * Returns array of enabled categories.
1066 * @method getCategories
1067 * @return {String[]} Array of enabled categories.
1069 getCategories : function() {
1070 return this._categoryFilters;
1074 * Shows log messages associated with given category.
1076 * @method showCategory
1077 * @param {String} Category name.
1079 showCategory : function(sCategory) {
1080 var filtersArray = this._categoryFilters;
1081 // Don't do anything if category is already enabled
1082 // Use Array.indexOf if available...
1083 if(filtersArray.indexOf) {
1084 if(filtersArray.indexOf(sCategory) > -1) {
1088 // ...or do it the old-fashioned way
1090 for(var i=0; i<filtersArray.length; i++) {
1091 if(filtersArray[i] === sCategory){
1097 this._categoryFilters.push(sCategory);
1099 var elCheckbox = this.getCheckbox(sCategory);
1101 elCheckbox.checked = true;
1106 * Hides log messages associated with given category.
1108 * @method hideCategory
1109 * @param {String} Category name.
1111 hideCategory : function(sCategory) {
1112 var filtersArray = this._categoryFilters;
1113 for(var i=0; i<filtersArray.length; i++) {
1114 if(sCategory == filtersArray[i]) {
1115 filtersArray.splice(i, 1);
1120 var elCheckbox = this.getCheckbox(sCategory);
1122 elCheckbox.checked = false;
1127 * Returns array of enabled sources.
1129 * @method getSources
1130 * @return {Array} Array of enabled sources.
1132 getSources : function() {
1133 return this._sourceFilters;
1137 * Shows log messages associated with given source.
1139 * @method showSource
1140 * @param {String} Source name.
1142 showSource : function(sSource) {
1143 var filtersArray = this._sourceFilters;
1144 // Don't do anything if category is already enabled
1145 // Use Array.indexOf if available...
1146 if(filtersArray.indexOf) {
1147 if(filtersArray.indexOf(sSource) > -1) {
1151 // ...or do it the old-fashioned way
1153 for(var i=0; i<filtersArray.length; i++) {
1154 if(sSource == filtersArray[i]){
1159 filtersArray.push(sSource);
1161 var elCheckbox = this.getCheckbox(sSource);
1163 elCheckbox.checked = true;
1168 * Hides log messages associated with given source.
1170 * @method hideSource
1171 * @param {String} Source name.
1173 hideSource : function(sSource) {
1174 var filtersArray = this._sourceFilters;
1175 for(var i=0; i<filtersArray.length; i++) {
1176 if(sSource == filtersArray[i]) {
1177 filtersArray.splice(i, 1);
1182 var elCheckbox = this.getCheckbox(sSource);
1184 elCheckbox.checked = false;
1189 * Does not delete any log messages, but clears all printed log messages from
1190 * the console. Log messages will be printed out again if user re-filters. The
1191 * static method YAHOO.widget.Logger.reset() should be called in order to
1192 * actually delete log messages.
1194 * @method clearConsole
1196 clearConsole : function() {
1197 // Clear the buffer of any pending messages
1198 this._timeout = null;
1200 this._consoleMsgCount = 0;
1202 var elConsole = this._elConsole;
1203 elConsole.innerHTML = '';
1207 * Updates title to given string.
1210 * @param sTitle {String} New title.
1212 setTitle : function(sTitle) {
1213 this._title.innerHTML = this.html2Text(sTitle);
1217 * Gets timestamp of the last log.
1219 * @method getLastTime
1220 * @return {Date} Timestamp of the last log.
1222 getLastTime : function() {
1223 return this._lastTime;
1226 formatMsg : function (entry) {
1227 var entryFormat = this.entryFormat || (this.verboseOutput ?
1228 LogReader.VERBOSE_TEMPLATE : LogReader.BASIC_TEMPLATE),
1230 category : entry.category,
1232 // Label for color-coded display
1233 label : entry.category.substring(0,4).toUpperCase(),
1235 sourceAndDetail : entry.sourceDetail ?
1236 entry.source + " " + entry.sourceDetail :
1239 // Escape HTML entities in the log message itself for output
1241 message : this.html2Text(entry.msg || entry.message || '')
1245 if (entry.time && entry.time.getTime) {
1246 info.localTime = entry.time.toLocaleTimeString ?
1247 entry.time.toLocaleTimeString() :
1248 entry.time.toString();
1250 // Calculate the elapsed time to be from the last item that
1251 // passed through the filter, not the absolute previous item
1253 info.elapsedTime = entry.time.getTime() - this.getLastTime();
1255 info.totalTime = entry.time.getTime() - Logger.getStartTime();
1258 var msg = LogReader.ENTRY_TEMPLATE.cloneNode(true);
1259 if (this.verboseOutput) {
1260 msg.className += ' yui-log-verbose';
1263 // Bug 2061169: Workaround for YAHOO.lang.substitute()
1264 msg.innerHTML = entryFormat.replace(/\{(\w+)\}/g,
1265 function (x, placeholder) {
1266 return (placeholder in info) ? info[placeholder] : '';
1273 * Converts input chars "<", ">", and "&" to HTML entities.
1276 * @param sHtml {String} String to convert.
1279 html2Text : function(sHtml) {
1282 return sHtml.replace(/&/g, "&").
1283 replace(/</g, "<").
1284 replace(/>/g, ">");
1289 /////////////////////////////////////////////////////////////////////////////
1291 // Private member variables
1293 /////////////////////////////////////////////////////////////////////////////
1296 * Name of LogReader instance.
1306 * A class member shared by all LogReaders if a container needs to be
1307 * created during instantiation. Will be null if a container element never needs to
1308 * be created on the fly, such as when the implementer passes in their own element.
1310 * @property _elDefaultContainer
1314 //YAHOO.widget.LogReader._elDefaultContainer = null;
1317 * Buffer of log message objects for batch output.
1326 * Number of log messages output to console.
1328 * @property _consoleMsgCount
1333 _consoleMsgCount : 0,
1336 * Date of last output log message.
1338 * @property _lastTime
1345 * Batched output timeout ID.
1347 * @property _timeout
1354 * Hash of filters and their related checkbox elements.
1356 * @property _filterCheckboxes
1360 _filterCheckboxes : null,
1363 * Array of filters for log message categories.
1365 * @property _categoryFilters
1369 _categoryFilters : null,
1372 * Array of filters for log message sources.
1374 * @property _sourceFilters
1378 _sourceFilters : null,
1381 * LogReader container element.
1383 * @property _elContainer
1387 _elContainer : null,
1390 * LogReader header element.
1399 * LogReader collapse element.
1401 * @property _elCollapse
1408 * LogReader collapse button element.
1410 * @property _btnCollapse
1414 _btnCollapse : null,
1417 * LogReader title header element.
1426 * LogReader console element.
1428 * @property _elConsole
1435 * LogReader footer element.
1444 * LogReader buttons container element.
1453 * Container element for LogReader category filter checkboxes.
1455 * @property _elCategoryFilters
1459 _elCategoryFilters : null,
1462 * Container element for LogReader source filter checkboxes.
1464 * @property _elSourceFilters
1468 _elSourceFilters : null,
1471 * LogReader pause button element.
1473 * @property _btnPause
1480 * Clear button element.
1482 * @property _btnClear
1488 /////////////////////////////////////////////////////////////////////////////
1492 /////////////////////////////////////////////////////////////////////////////
1495 * Initializes the instance's message buffer, start time, etc
1498 * @param container {String|HTMLElement} (optional) the render target
1499 * @param config {Object} (optional) instance configuration
1502 _init : function (container, config) {
1504 this._buffer = []; // output buffer
1505 this._filterCheckboxes = {}; // pointers to checkboxes
1506 this._lastTime = Logger.getStartTime(); // timestamp of last log message to console
1508 // Parse config vars here
1509 if (config && (config.constructor == Object)) {
1510 for(var param in config) {
1511 if (config.hasOwnProperty(param)) {
1512 this[param] = config[param];
1517 this._elContainer = Dom.get(container);
1519 YAHOO.log("LogReader initialized", null, this.toString());
1523 * Initializes the primary container element.
1525 * @method _initContainerEl
1528 _initContainerEl : function() {
1530 // Default the container if unset or not a div
1531 if(!this._elContainer || !/div$/i.test(this._elContainer.tagName)) {
1532 this._elContainer = d.body.insertBefore(make("div"),d.body.firstChild);
1533 // Only position absolutely if an in-DOM element is not supplied
1534 Dom.addClass(this._elContainer,"yui-log-container");
1537 Dom.addClass(this._elContainer,"yui-log");
1539 // If implementer has provided container values, trust and set those
1540 var style = this._elContainer.style,
1541 styleProps = ['width','right','top','fontSize'],
1544 for (i = styleProps.length - 1; i >= 0; --i) {
1545 prop = styleProps[i];
1547 style[prop] = this[prop];
1552 style.left = this.left;
1553 style.right = "auto";
1556 style.bottom = this.bottom;
1560 // Opera needs a little prodding to reflow sometimes
1561 if (YAHOO.env.ua.opera) {
1568 * Initializes the header element.
1570 * @method _initHeaderEl
1573 _initHeaderEl : function() {
1574 // Destroy header if present
1576 // Unhook DOM events
1577 Event.purgeElement(this._elHd, true);
1579 // Remove DOM elements
1580 this._elHd.innerHTML = "";
1584 // TODO: refactor this into an innerHTML
1585 this._elHd = make("div",{
1586 className: "yui-log-hd"
1588 Dom.generateId(this._elHd, 'yui-log-hd' + this._sName);
1590 this._elCollapse = make("div",{ className: 'yui-log-btns' });
1592 this._btnCollapse = make("input",{
1594 className: 'yui-log-button',
1597 Event.on(this._btnCollapse,'click',this._onClickCollapseBtn,this);
1600 this._title = make("h4",{ innerHTML : "Logger Console" });
1602 this._elCollapse.appendChild(this._btnCollapse);
1603 this._elHd.appendChild(this._elCollapse);
1604 this._elHd.appendChild(this._title);
1605 this._elContainer.appendChild(this._elHd);
1609 * Initializes the console element.
1611 * @method _initConsoleEl
1614 _initConsoleEl : function() {
1616 if(this._elConsole) {
1617 // Unhook DOM events
1618 Event.purgeElement(this._elConsole, true);
1620 // Remove DOM elements
1621 this._elConsole.innerHTML = "";
1625 this._elConsole = make("div", { className: "yui-log-bd" });
1627 // If implementer has provided console, trust and set those
1629 this._elConsole.style.height = this.height;
1632 this._elContainer.appendChild(this._elConsole);
1636 * Initializes the footer element.
1638 * @method _initFooterEl
1641 _initFooterEl : function() {
1642 // Don't create footer elements if footer is disabled
1643 if(this.footerEnabled) {
1646 // Unhook DOM events
1647 Event.purgeElement(this._elFt, true);
1649 // Remove DOM elements
1650 this._elFt.innerHTML = "";
1653 // TODO: use innerHTML
1654 this._elFt = make("div",{ className: "yui-log-ft" });
1655 this._elBtns = make("div", { className: "yui-log-btns" });
1656 this._btnPause = make("input", {
1658 className: "yui-log-button",
1662 Event.on(this._btnPause,'click',this._onClickPauseBtn,this);
1664 this._btnClear = make("input", {
1666 className: "yui-log-button",
1670 Event.on(this._btnClear,'click',this._onClickClearBtn,this);
1672 this._elCategoryFilters = make("div", { className: "yui-log-categoryfilters" });
1673 this._elSourceFilters = make("div", { className: "yui-log-sourcefilters" });
1675 this._elBtns.appendChild(this._btnPause);
1676 this._elBtns.appendChild(this._btnClear);
1677 this._elFt.appendChild(this._elBtns);
1678 this._elFt.appendChild(this._elCategoryFilters);
1679 this._elFt.appendChild(this._elSourceFilters);
1680 this._elContainer.appendChild(this._elFt);
1685 * Initializes Drag and Drop on the header element.
1687 * @method _initDragDrop
1690 _initDragDrop : function() {
1691 // If Drag and Drop utility is available...
1692 // ...and draggable is true...
1693 // ...then make the header draggable
1694 if(u.DD && this.draggable && this._elHd) {
1695 var ylog_dd = new u.DD(this._elContainer);
1696 ylog_dd.setHandleElId(this._elHd.id);
1697 //TODO: use class name
1698 this._elHd.style.cursor = "move";
1703 * Initializes category filters.
1705 * @method _initCategories
1708 _initCategories : function() {
1709 // Initialize category filters
1710 this._categoryFilters = [];
1711 var aInitialCategories = Logger.categories;
1713 for(var j=0; j < aInitialCategories.length; j++) {
1714 var sCategory = aInitialCategories[j];
1716 // Add category to the internal array of filters
1717 this._categoryFilters.push(sCategory);
1719 // Add checkbox element if UI is enabled
1720 if(this._elCategoryFilters) {
1721 this._createCategoryCheckbox(sCategory);
1727 * Initializes source filters.
1729 * @method _initSources
1732 _initSources : function() {
1733 // Initialize source filters
1734 this._sourceFilters = [];
1735 var aInitialSources = Logger.sources;
1737 for(var j=0; j < aInitialSources.length; j++) {
1738 var sSource = aInitialSources[j];
1740 // Add source to the internal array of filters
1741 this._sourceFilters.push(sSource);
1743 // Add checkbox element if UI is enabled
1744 if(this._elSourceFilters) {
1745 this._createSourceCheckbox(sSource);
1751 * Creates the UI for a category filter in the LogReader footer element.
1753 * @method _createCategoryCheckbox
1754 * @param sCategory {String} Category name.
1757 _createCategoryCheckbox : function(sCategory) {
1759 var filter = make("span",{ className: "yui-log-filtergrp" }),
1760 checkid = Dom.generateId(null, "yui-log-filter-" + sCategory + this._sName),
1761 check = make("input", {
1763 className: "yui-log-filter-" + sCategory,
1767 label = make("label", {
1769 className: sCategory,
1770 innerHTML: sCategory
1774 // Subscribe to the click event
1775 Event.on(check,'click',this._onCheckCategory,this);
1777 this._filterCheckboxes[sCategory] = check;
1779 // Append el at the end so IE 5.5 can set "type" attribute
1780 // and THEN set checked property
1781 filter.appendChild(check);
1782 filter.appendChild(label);
1783 this._elCategoryFilters.appendChild(filter);
1784 check.checked = true;
1789 * Creates a checkbox in the LogReader footer element to filter by source.
1791 * @method _createSourceCheckbox
1792 * @param sSource {String} Source name.
1795 _createSourceCheckbox : function(sSource) {
1797 var filter = make("span",{ className: "yui-log-filtergrp" }),
1798 checkid = Dom.generateId(null, "yui-log-filter-" + sSource + this._sName),
1799 check = make("input", {
1801 className: "yui-log-filter-" + sSource,
1805 label = make("label", {
1812 // Subscribe to the click event
1813 Event.on(check,'click',this._onCheckSource,this);
1815 this._filterCheckboxes[sSource] = check;
1817 // Append el at the end so IE 5.5 can set "type" attribute
1818 // and THEN set checked property
1819 filter.appendChild(check);
1820 filter.appendChild(label);
1821 this._elSourceFilters.appendChild(filter);
1822 check.checked = true;
1827 * Reprints all log messages in the stack through filters.
1829 * @method _filterLogs
1832 _filterLogs : function() {
1833 // Reprint stack with new filters
1834 if (this._elConsole !== null) {
1835 this.clearConsole();
1836 this._printToConsole(Logger.getStack());
1841 * Sends buffer of log messages to output and clears buffer.
1843 * @method _printBuffer
1846 _printBuffer : function() {
1847 this._timeout = null;
1849 if(this._elConsole !== null) {
1850 var thresholdMax = this.thresholdMax;
1851 thresholdMax = (thresholdMax && !isNaN(thresholdMax)) ? thresholdMax : 500;
1852 if(this._consoleMsgCount < thresholdMax) {
1854 for (var i=0; i<this._buffer.length; i++) {
1855 entries[i] = this._buffer[i];
1858 this._printToConsole(entries);
1864 if(!this.newestOnTop) {
1865 this._elConsole.scrollTop = this._elConsole.scrollHeight;
1871 * Cycles through an array of log messages, and outputs each one to the console
1872 * if its category has not been filtered out.
1874 * @method _printToConsole
1875 * @param aEntries {Object[]} Array of LogMsg objects to output to console.
1878 _printToConsole : function(aEntries) {
1879 // Manage the number of messages displayed in the console
1880 var entriesLen = aEntries.length,
1881 df = d.createDocumentFragment(),
1883 thresholdMin = this.thresholdMin,
1884 sourceFiltersLen = this._sourceFilters.length,
1885 categoryFiltersLen = this._categoryFilters.length,
1889 if(isNaN(thresholdMin) || (thresholdMin > this.thresholdMax)) {
1892 entriesStartIndex = (entriesLen > thresholdMin) ? (entriesLen - thresholdMin) : 0;
1894 // Iterate through all log entries
1895 for(i=entriesStartIndex; i<entriesLen; i++) {
1896 // Print only the ones that filter through
1897 var okToPrint = false,
1898 okToFilterCats = false,
1899 entry = aEntries[i],
1900 source = entry.source,
1901 category = entry.category;
1903 for(j=0; j<sourceFiltersLen; j++) {
1904 if(source == this._sourceFilters[j]) {
1905 okToFilterCats = true;
1909 if(okToFilterCats) {
1910 for(j=0; j<categoryFiltersLen; j++) {
1911 if(category == this._categoryFilters[j]) {
1918 // Start from 0ms elapsed time
1919 if (this._consoleMsgCount === 0) {
1920 this._lastTime = entry.time.getTime();
1923 msg = this.formatMsg(entry);
1924 if (typeof msg === 'string') {
1925 msgHTML[msgHTML.length] = msg;
1927 df.insertBefore(msg, this.newestOnTop ?
1928 df.firstChild || null : null);
1930 this._consoleMsgCount++;
1931 this._lastTime = entry.time.getTime();
1935 if (msgHTML.length) {
1936 msgHTML.splice(0,0,this._elConsole.innerHTML);
1937 this._elConsole.innerHTML = this.newestOnTop ?
1938 msgHTML.reverse().join('') :
1940 } else if (df.firstChild) {
1941 this._elConsole.insertBefore(df, this.newestOnTop ?
1942 this._elConsole.firstChild || null : null);
1946 /////////////////////////////////////////////////////////////////////////////
1948 // Private event handlers
1950 /////////////////////////////////////////////////////////////////////////////
1953 * Handles Logger's categoryCreateEvent.
1955 * @method _onCategoryCreate
1956 * @param sType {String} The event.
1957 * @param aArgs {Object[]} Data passed from event firer.
1958 * @param oSelf {Object} The LogReader instance.
1961 _onCategoryCreate : function(sType, aArgs, oSelf) {
1962 var category = aArgs[0];
1964 // Add category to the internal array of filters
1965 oSelf._categoryFilters.push(category);
1968 oSelf._createCategoryCheckbox(category);
1973 * Handles Logger's sourceCreateEvent.
1975 * @method _onSourceCreate
1976 * @param sType {String} The event.
1977 * @param aArgs {Object[]} Data passed from event firer.
1978 * @param oSelf {Object} The LogReader instance.
1981 _onSourceCreate : function(sType, aArgs, oSelf) {
1982 var source = aArgs[0];
1984 // Add source to the internal array of filters
1985 oSelf._sourceFilters.push(source);
1988 oSelf._createSourceCheckbox(source);
1993 * Handles check events on the category filter checkboxes.
1995 * @method _onCheckCategory
1996 * @param v {HTMLEvent} The click event.
1997 * @param oSelf {Object} The LogReader instance.
2000 _onCheckCategory : function(v, oSelf) {
2001 var category = this.category;
2003 oSelf.hideCategory(category);
2006 oSelf.showCategory(category);
2011 * Handles check events on the category filter checkboxes.
2013 * @method _onCheckSource
2014 * @param v {HTMLEvent} The click event.
2015 * @param oSelf {Object} The LogReader instance.
2018 _onCheckSource : function(v, oSelf) {
2019 var source = this.source;
2021 oSelf.hideSource(source);
2024 oSelf.showSource(source);
2029 * Handles click events on the collapse button.
2031 * @method _onClickCollapseBtn
2032 * @param v {HTMLEvent} The click event.
2033 * @param oSelf {Object} The LogReader instance
2036 _onClickCollapseBtn : function(v, oSelf) {
2037 if(!oSelf.isCollapsed) {
2046 * Handles click events on the pause button.
2048 * @method _onClickPauseBtn
2049 * @param v {HTMLEvent} The click event.
2050 * @param oSelf {Object} The LogReader instance.
2053 _onClickPauseBtn : function(v, oSelf) {
2054 if(!oSelf.isPaused) {
2063 * Handles click events on the clear button.
2065 * @method _onClickClearBtn
2066 * @param v {HTMLEvent} The click event.
2067 * @param oSelf {Object} The LogReader instance.
2070 _onClickClearBtn : function(v, oSelf) {
2071 oSelf.clearConsole();
2075 * Handles Logger's newLogEvent.
2078 * @param sType {String} The event.
2079 * @param aArgs {Object[]} Data passed from event firer.
2080 * @param oSelf {Object} The LogReader instance.
2083 _onNewLog : function(sType, aArgs, oSelf) {
2084 var logEntry = aArgs[0];
2085 oSelf._buffer.push(logEntry);
2087 if (oSelf.logReaderEnabled === true && oSelf._timeout === null) {
2088 oSelf._timeout = setTimeout(function(){oSelf._printBuffer();}, oSelf.outputBuffer);
2093 * Handles Logger's resetEvent.
2096 * @param sType {String} The event.
2097 * @param aArgs {Object[]} Data passed from event firer.
2098 * @param oSelf {Object} The LogReader instance.
2101 _onReset : function(sType, aArgs, oSelf) {
2102 oSelf._filterLogs();
2106 YAHOO.widget.LogReader = LogReader;
2109 YAHOO.register("logger", YAHOO.widget.Logger, {version: "2.9.0", build: "2800"});