]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/logger/logger.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / logger / logger.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 /****************************************************************************/
8 /****************************************************************************/
9 /****************************************************************************/
10
11 /**
12  * The LogMsg class defines a single log message.
13  *
14  * @class LogMsg
15  * @constructor
16  * @param oConfigs {Object} Object literal of configuration params.
17  */
18 YAHOO.widget.LogMsg = function(oConfigs) {
19     // Parse configs
20     /**
21      * Log message.
22      *
23      * @property msg
24      * @type String
25      */
26     this.msg =
27     /**
28      * Log timestamp.
29      *
30      * @property time
31      * @type Date
32      */
33     this.time =
34
35     /**
36      * Log category.
37      *
38      * @property category
39      * @type String
40      */
41     this.category =
42
43     /**
44      * Log source. The first word passed in as the source argument.
45      *
46      * @property source
47      * @type String
48      */
49     this.source =
50
51     /**
52      * Log source detail. The remainder of the string passed in as the source argument, not
53      * including the first word (if any).
54      *
55      * @property sourceDetail
56      * @type String
57      */
58     this.sourceDetail = null;
59
60     if (oConfigs && (oConfigs.constructor == Object)) {
61         for(var param in oConfigs) {
62             if (oConfigs.hasOwnProperty(param)) {
63                 this[param] = oConfigs[param];
64             }
65         }
66     }
67 };
68 /****************************************************************************/
69 /****************************************************************************/
70 /****************************************************************************/
71
72 /**
73  * The LogWriter class provides a mechanism to log messages through
74  * YAHOO.widget.Logger from a named source.
75  *
76  * @class LogWriter
77  * @constructor
78  * @param sSource {String} Source of LogWriter instance.
79  */
80 YAHOO.widget.LogWriter = function(sSource) {
81     if(!sSource) {
82         YAHOO.log("Could not instantiate LogWriter due to invalid source.",
83             "error", "LogWriter");
84         return;
85     }
86     this._source = sSource;
87  };
88
89 /////////////////////////////////////////////////////////////////////////////
90 //
91 // Public methods
92 //
93 /////////////////////////////////////////////////////////////////////////////
94
95  /**
96  * Public accessor to the unique name of the LogWriter instance.
97  *
98  * @method toString
99  * @return {String} Unique name of the LogWriter instance.
100  */
101 YAHOO.widget.LogWriter.prototype.toString = function() {
102     return "LogWriter " + this._sSource;
103 };
104
105 /**
106  * Logs a message attached to the source of the LogWriter.
107  *
108  * @method log
109  * @param sMsg {String} The log message.
110  * @param sCategory {String} Category name.
111  */
112 YAHOO.widget.LogWriter.prototype.log = function(sMsg, sCategory) {
113     YAHOO.widget.Logger.log(sMsg, sCategory, this._source);
114 };
115
116 /**
117  * Public accessor to get the source name.
118  *
119  * @method getSource
120  * @return {String} The LogWriter source.
121  */
122 YAHOO.widget.LogWriter.prototype.getSource = function() {
123     return this._source;
124 };
125
126 /**
127  * Public accessor to set the source name.
128  *
129  * @method setSource
130  * @param sSource {String} Source of LogWriter instance.
131  */
132 YAHOO.widget.LogWriter.prototype.setSource = function(sSource) {
133     if(!sSource) {
134         YAHOO.log("Could not set source due to invalid source.", "error", this.toString());
135         return;
136     }
137     else {
138         this._source = sSource;
139     }
140 };
141
142 /////////////////////////////////////////////////////////////////////////////
143 //
144 // Private member variables
145 //
146 /////////////////////////////////////////////////////////////////////////////
147
148 /**
149  * Source of the LogWriter instance.
150  *
151  * @property _source
152  * @type String
153  * @private
154  */
155 YAHOO.widget.LogWriter.prototype._source = null;
156
157
158
159  /**
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
164  * console.
165  *
166  * @module logger
167  * @requires yahoo, event, dom
168  * @optional dragdrop
169  * @namespace YAHOO.widget
170  * @title Logger Widget
171  */
172
173 /****************************************************************************/
174 /****************************************************************************/
175 /****************************************************************************/
176
177 // Define once
178 if(!YAHOO.widget.Logger) {
179     /**
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.
185      *
186      * @class Logger
187      * @static
188      */
189     YAHOO.widget.Logger = {
190         // Initialize properties
191         loggerEnabled: true,
192         _browserConsoleEnabled: false,
193         categories: ["info","warn","error","time","window"],
194         sources: ["global"],
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
201     };
202
203     /////////////////////////////////////////////////////////////////////////////
204     //
205     // Public properties
206     //
207     /////////////////////////////////////////////////////////////////////////////
208     /**
209      * True if Logger is enabled, false otherwise.
210      *
211      * @property loggerEnabled
212      * @type Boolean
213      * @static
214      * @default true
215      */
216
217     /**
218      * Array of categories.
219      *
220      * @property categories
221      * @type String[]
222      * @static
223      * @default ["info","warn","error","time","window"]
224      */
225
226     /**
227      * Array of sources.
228      *
229      * @property sources
230      * @type String[]
231      * @static
232      * @default ["global"]
233      */
234
235     /**
236      * Upper limit on size of internal stack.
237      *
238      * @property maxStackEntries
239      * @type Number
240      * @static
241      * @default 2500
242      */
243
244     /////////////////////////////////////////////////////////////////////////////
245     //
246     // Private properties
247     //
248     /////////////////////////////////////////////////////////////////////////////
249     /**
250      * Internal property to track whether output to browser console is enabled.
251      *
252      * @property _browserConsoleEnabled
253      * @type Boolean
254      * @static
255      * @default false
256      * @private
257      */
258
259     /**
260      * Array to hold all log messages.
261      *
262      * @property _stack
263      * @type Array
264      * @static
265      * @private
266      */
267     /**
268      * Static timestamp of Logger initialization.
269      *
270      * @property _startTime
271      * @type Date
272      * @static
273      * @private
274      */
275     /**
276      * Timestamp of last logged message.
277      *
278      * @property _lastTime
279      * @type Date
280      * @static
281      * @private
282      */
283     /////////////////////////////////////////////////////////////////////////////
284     //
285     // Public methods
286     //
287     /////////////////////////////////////////////////////////////////////////////
288     /**
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.
293      *
294      * @method log
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.
298      */
299     YAHOO.widget.Logger.log = function(sMsg, sCategory, sSource) {
300         if(this.loggerEnabled) {
301             if(!sCategory) {
302                 sCategory = "info"; // default category
303             }
304             else {
305                 sCategory = sCategory.toLocaleLowerCase();
306                 if(this._isNewCategory(sCategory)) {
307                     this._createNewCategory(sCategory);
308                 }
309             }
310             var sClass = "global"; // default source
311             var sDetail = null;
312             if(sSource) {
313                 var spaceIndex = sSource.indexOf(" ");
314                 if(spaceIndex > 0) {
315                     // Substring until first space
316                     sClass = sSource.substring(0,spaceIndex);
317                     // The rest of the source
318                     sDetail = sSource.substring(spaceIndex,sSource.length);
319                 }
320                 else {
321                     sClass = sSource;
322                 }
323                 if(this._isNewSource(sClass)) {
324                     this._createNewSource(sClass);
325                 }
326             }
327
328             var timestamp = new Date();
329             var logEntry = new YAHOO.widget.LogMsg({
330                 msg: sMsg,
331                 time: timestamp,
332                 category: sCategory,
333                 source: sClass,
334                 sourceDetail: sDetail
335             });
336
337             var stack = this._stack;
338             var maxStackEntries = this.maxStackEntries;
339             if(maxStackEntries && !isNaN(maxStackEntries) &&
340                 (stack.length >= maxStackEntries)) {
341                 stack.shift();
342             }
343             stack.push(logEntry);
344             this.newLogEvent.fire(logEntry);
345
346             if(this._browserConsoleEnabled) {
347                 this._printToBrowserConsole(logEntry);
348             }
349             return true;
350         }
351         else {
352             return false;
353         }
354     };
355
356     /**
357      * Resets internal stack and startTime, enables Logger, and fires logResetEvent.
358      *
359      * @method reset
360      */
361     YAHOO.widget.Logger.reset = function() {
362         this._stack = [];
363         this._startTime = new Date().getTime();
364         this.loggerEnabled = true;
365         this.log("Logger reset");
366         this.logResetEvent.fire();
367     };
368
369     /**
370      * Public accessor to internal stack of log message objects.
371      *
372      * @method getStack
373      * @return {Object[]} Array of log message objects.
374      */
375     YAHOO.widget.Logger.getStack = function() {
376         return this._stack;
377     };
378
379     /**
380      * Public accessor to internal start time.
381      *
382      * @method getStartTime
383      * @return {Date} Internal date of when Logger singleton was initialized.
384      */
385     YAHOO.widget.Logger.getStartTime = function() {
386         return this._startTime;
387     };
388
389     /**
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.
392      *
393      * @method disableBrowserConsole
394      */
395     YAHOO.widget.Logger.disableBrowserConsole = function() {
396         YAHOO.log("Logger output to the function console.log() has been disabled.");
397         this._browserConsoleEnabled = false;
398     };
399
400     /**
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.
403      *
404      * @method enableBrowserConsole
405      */
406     YAHOO.widget.Logger.enableBrowserConsole = function() {
407         this._browserConsoleEnabled = true;
408         YAHOO.log("Logger output to the function console.log() has been enabled.");
409     };
410
411     /**
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.
415      *
416      * @method handleWindowErrors
417      */
418     YAHOO.widget.Logger.handleWindowErrors = function() {
419         if(!YAHOO.widget.Logger._windowErrorsHandled) {
420             // Save any previously defined handler to call
421             if(window.error) {
422                 YAHOO.widget.Logger._origOnWindowError = window.onerror;
423             }
424             window.onerror = YAHOO.widget.Logger._onWindowError;
425             YAHOO.widget.Logger._windowErrorsHandled = true;
426             YAHOO.log("Logger handling of window.onerror has been enabled.");
427         }
428         else {
429             YAHOO.log("Logger handling of window.onerror had already been enabled.");
430         }
431     };
432
433     /**
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.
437      *
438      * @method unhandleWindowErrors
439      */
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;
446             }
447             else {
448                 window.onerror = null;
449             }
450             YAHOO.widget.Logger._windowErrorsHandled = false;
451             YAHOO.log("Logger handling of window.onerror has been disabled.");
452         }
453         else {
454             YAHOO.log("Logger handling of window.onerror had already been disabled.");
455         }
456     };
457     
458     /////////////////////////////////////////////////////////////////////////////
459     //
460     // Public events
461     //
462     /////////////////////////////////////////////////////////////////////////////
463
464      /**
465      * Fired when a new category has been created.
466      *
467      * @event categoryCreateEvent
468      * @param sCategory {String} Category name.
469      */
470     YAHOO.widget.Logger.categoryCreateEvent =
471         new YAHOO.util.CustomEvent("categoryCreate", this, true);
472
473      /**
474      * Fired when a new source has been named.
475      *
476      * @event sourceCreateEvent
477      * @param sSource {String} Source name.
478      */
479     YAHOO.widget.Logger.sourceCreateEvent =
480         new YAHOO.util.CustomEvent("sourceCreate", this, true);
481
482      /**
483      * Fired when a new log message has been created.
484      *
485      * @event newLogEvent
486      * @param sMsg {String} Log message.
487      */
488     YAHOO.widget.Logger.newLogEvent = new YAHOO.util.CustomEvent("newLog", this, true);
489
490     /**
491      * Fired when the Logger has been reset has been created.
492      *
493      * @event logResetEvent
494      */
495     YAHOO.widget.Logger.logResetEvent = new YAHOO.util.CustomEvent("logReset", this, true);
496
497     /////////////////////////////////////////////////////////////////////////////
498     //
499     // Private methods
500     //
501     /////////////////////////////////////////////////////////////////////////////
502
503     /**
504      * Creates a new category of log messages and fires categoryCreateEvent.
505      *
506      * @method _createNewCategory
507      * @param sCategory {String} Category name.
508      * @private
509      */
510     YAHOO.widget.Logger._createNewCategory = function(sCategory) {
511         this.categories.push(sCategory);
512         this.categoryCreateEvent.fire(sCategory);
513     };
514
515     /**
516      * Checks to see if a category has already been created.
517      *
518      * @method _isNewCategory
519      * @param sCategory {String} Category name.
520      * @return {Boolean} Returns true if category is unknown, else returns false.
521      * @private
522      */
523     YAHOO.widget.Logger._isNewCategory = function(sCategory) {
524         for(var i=0; i < this.categories.length; i++) {
525             if(sCategory == this.categories[i]) {
526                 return false;
527             }
528         }
529         return true;
530     };
531
532     /**
533      * Creates a new source of log messages and fires sourceCreateEvent.
534      *
535      * @method _createNewSource
536      * @param sSource {String} Source name.
537      * @private
538      */
539     YAHOO.widget.Logger._createNewSource = function(sSource) {
540         this.sources.push(sSource);
541         this.sourceCreateEvent.fire(sSource);
542     };
543
544     /**
545      * Checks to see if a source already exists.
546      *
547      * @method _isNewSource
548      * @param sSource {String} Source name.
549      * @return {Boolean} Returns true if source is unknown, else returns false.
550      * @private
551      */
552     YAHOO.widget.Logger._isNewSource = function(sSource) {
553         if(sSource) {
554             for(var i=0; i < this.sources.length; i++) {
555                 if(sSource == this.sources[i]) {
556                     return false;
557                 }
558             }
559             return true;
560         }
561     };
562
563     /**
564      * Outputs a log message to global console.log() function.
565      *
566      * @method _printToBrowserConsole
567      * @param oEntry {Object} Log entry object.
568      * @private
569      */
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();
574
575             var time = oEntry.time;
576             var localTime;
577             if (time.toLocaleTimeString) {
578                 localTime  = time.toLocaleTimeString();
579             }
580             else {
581                 localTime = time.toString();
582             }
583
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;
588
589             var output =
590                 localTime + " (" +
591                 elapsedTime + "ms): " +
592                 oEntry.source + ": ";
593
594             // for bug 1987607
595             if (YAHOO.env.ua.webkit) {
596                 output += oEntry.msg;
597             }
598
599             console.log(output, oEntry.msg);
600         }
601     };
602
603     /////////////////////////////////////////////////////////////////////////////
604     //
605     // Private event handlers
606     //
607     /////////////////////////////////////////////////////////////////////////////
608
609     /**
610      * Handles logging of messages due to window error events.
611      *
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.
616      * @private
617      */
618     YAHOO.widget.Logger._onWindowError = function(sMsg,sUrl,sLine) {
619         // Logger is not in scope of this event handler
620         try {
621             YAHOO.widget.Logger.log(sMsg+' ('+sUrl+', line '+sLine+')', "window");
622             if(YAHOO.widget.Logger._origOnWindowError) {
623                 YAHOO.widget.Logger._origOnWindowError();
624             }
625         }
626         catch(e) {
627             return false;
628         }
629     };
630
631     /////////////////////////////////////////////////////////////////////////////
632     //
633     // First log
634     //
635     /////////////////////////////////////////////////////////////////////////////
636
637     YAHOO.widget.Logger.log("Logger initialized");
638 }
639
640 /****************************************************************************/
641 /****************************************************************************/
642 /****************************************************************************/
643 (function () {
644 var Logger = YAHOO.widget.Logger,
645     u      = YAHOO.util,
646     Dom    = u.Dom,
647     Event  = u.Event,
648     d      = document;
649
650 function make(el,props) {
651     el = d.createElement(el);
652     if (props) {
653         for (var p in props) {
654             if (props.hasOwnProperty(p)) {
655                 el[p] = props[p];
656             }
657         }
658     }
659     return el;
660 }
661
662 /**
663  * The LogReader class provides UI to read messages logged to YAHOO.widget.Logger.
664  *
665  * @class LogReader
666  * @constructor
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.
670  */
671 function LogReader(elContainer, oConfigs) {
672     this._sName = LogReader._index;
673     LogReader._index++;
674     
675     this._init.apply(this,arguments);
676
677     /**
678      * Render the LogReader immediately upon instantiation.  If set to false,
679      * you must call myLogReader.render() to generate the UI.
680      * 
681      * @property autoRender
682      * @type {Boolean}
683      * @default true
684      */
685     if (this.autoRender !== false) {
686         this.render();
687     }
688 }
689
690 /////////////////////////////////////////////////////////////////////////////
691 //
692 // Static member variables
693 //
694 /////////////////////////////////////////////////////////////////////////////
695 YAHOO.lang.augmentObject(LogReader, {
696     /**
697      * Internal class member to index multiple LogReader instances.
698      *
699      * @property _memberName
700      * @static
701      * @type Number
702      * @default 0
703      * @private
704      */
705     _index : 0,
706
707     /**
708      * Node template for the log entries
709      * @property ENTRY_TEMPLATE
710      * @static
711      * @type {HTMLElement}
712      * @default <code>pre</code> element with class yui-log-entry
713      */
714     ENTRY_TEMPLATE : (function () {
715         return make('pre',{ className: 'yui-log-entry' });
716     })(),
717
718     /**
719      * Template used for innerHTML of verbose entry output.
720      * @property VERBOSE_TEMPLATE
721      * @static
722      * @default "&lt;p>&lt;span class='{category}'>{label}&lt;/span>{totalTime}ms (+{elapsedTime}) {localTime}:&lt;/p>&lt;p>{sourceAndDetail}&lt;/p>&lt;p>{message}&lt;/p>"
723      */
724     VERBOSE_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}:</p><p>{sourceAndDetail}</p><p>{message}</p>",
725
726     /**
727      * Template used for innerHTML of compact entry output.
728      * @property BASIC_TEMPLATE
729      * @static
730      * @default "&lt;p>&lt;span class='{category}'>{label}&lt;/span>{totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}&lt;/p>"
731      */
732     BASIC_TEMPLATE : "<p><span class='{category}'>{label}</span> {totalTime}ms (+{elapsedTime}) {localTime}: {sourceAndDetail}: {message}</p>"
733 });
734
735 /////////////////////////////////////////////////////////////////////////////
736 //
737 // Public member variables
738 //
739 /////////////////////////////////////////////////////////////////////////////
740
741 LogReader.prototype = {
742     /**
743      * Whether or not LogReader is enabled to output log messages.
744      *
745      * @property logReaderEnabled
746      * @type Boolean
747      * @default true
748      */
749     logReaderEnabled : true,
750
751     /**
752      * Public member to access CSS width of the LogReader container.
753      *
754      * @property width
755      * @type String
756      */
757     width : null,
758
759     /**
760      * Public member to access CSS height of the LogReader container.
761      *
762      * @property height
763      * @type String
764      */
765     height : null,
766
767     /**
768      * Public member to access CSS top position of the LogReader container.
769      *
770      * @property top
771      * @type String
772      */
773     top : null,
774
775     /**
776      * Public member to access CSS left position of the LogReader container.
777      *
778      * @property left
779      * @type String
780      */
781     left : null,
782
783     /**
784      * Public member to access CSS right position of the LogReader container.
785      *
786      * @property right
787      * @type String
788      */
789     right : null,
790
791     /**
792      * Public member to access CSS bottom position of the LogReader container.
793      *
794      * @property bottom
795      * @type String
796      */
797     bottom : null,
798
799     /**
800      * Public member to access CSS font size of the LogReader container.
801      *
802      * @property fontSize
803      * @type String
804      */
805     fontSize : null,
806
807     /**
808      * Whether or not the footer UI is enabled for the LogReader.
809      *
810      * @property footerEnabled
811      * @type Boolean
812      * @default true
813      */
814     footerEnabled : true,
815
816     /**
817      * Whether or not output is verbose (more readable). Setting to true will make
818      * output more compact (less readable).
819      *
820      * @property verboseOutput
821      * @type Boolean
822      * @default true
823      */
824     verboseOutput : true,
825
826     /**
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:
831      * <ul>
832      *  <li>category</li>
833      *  <li>label</li>
834      *  <li>sourceAndDetail</li>
835      *  <li>message</li>
836      *  <li>localTime</li>
837      *  <li>elapsedTime</li>
838      *  <li>totalTime</li>
839      * </ul>
840      *
841      * @property entryFormat
842      * @type String
843      * @default null
844      */
845     entryFormat : null,
846
847     /**
848      * Whether or not newest message is printed on top.
849      *
850      * @property newestOnTop
851      * @type Boolean
852      */
853     newestOnTop : true,
854
855     /**
856      * Output timeout buffer in milliseconds.
857      *
858      * @property outputBuffer
859      * @type Number
860      * @default 100
861      */
862     outputBuffer : 100,
863
864     /**
865      * Maximum number of messages a LogReader console will display.
866      *
867      * @property thresholdMax
868      * @type Number
869      * @default 500
870      */
871     thresholdMax : 500,
872
873     /**
874      * When a LogReader console reaches its thresholdMax, it will clear out messages
875      * and print out the latest thresholdMin number of messages.
876      *
877      * @property thresholdMin
878      * @type Number
879      * @default 100
880      */
881     thresholdMin : 100,
882
883     /**
884      * True when LogReader is in a collapsed state, false otherwise.
885      *
886      * @property isCollapsed
887      * @type Boolean
888      * @default false
889      */
890     isCollapsed : false,
891
892     /**
893      * True when LogReader is in a paused state, false otherwise.
894      *
895      * @property isPaused
896      * @type Boolean
897      * @default false
898      */
899     isPaused : false,
900
901     /**
902      * Enables draggable LogReader if DragDrop Utility is present.
903      *
904      * @property draggable
905      * @type Boolean
906      * @default true
907      */
908     draggable : true,
909
910     /////////////////////////////////////////////////////////////////////////////
911     //
912     // Public methods
913     //
914     /////////////////////////////////////////////////////////////////////////////
915
916      /**
917      * Public accessor to the unique name of the LogReader instance.
918      *
919      * @method toString
920      * @return {String} Unique name of the LogReader instance.
921      */
922     toString : function() {
923         return "LogReader instance" + this._sName;
924     },
925     /**
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.
928      *
929      * @method pause
930      */
931     pause : function() {
932         this.isPaused = true;
933         this._timeout = null;
934         this.logReaderEnabled = false;
935         if (this._btnPause) {
936             this._btnPause.value = "Resume";
937         }
938     },
939
940     /**
941      * Resumes output of log messages, including outputting any log messages that
942      * have been saved to buffer while paused.
943      *
944      * @method resume
945      */
946     resume : function() {
947         this.isPaused = false;
948         this.logReaderEnabled = true;
949         this._printBuffer();
950         if (this._btnPause) {
951             this._btnPause.value = "Pause";
952         }
953     },
954
955     /**
956      * Adds the UI to the DOM, attaches event listeners, and bootstraps initial
957      * UI state.
958      *
959      * @method render
960      */
961     render : function () {
962         if (this.rendered) {
963             return;
964         }
965
966         this._initContainerEl();
967         
968         this._initHeaderEl();
969         this._initConsoleEl();
970         this._initFooterEl();
971
972         this._initCategories();
973         this._initSources();
974
975         this._initDragDrop();
976
977         // Subscribe to Logger custom events
978         Logger.newLogEvent.subscribe(this._onNewLog, this);
979         Logger.logResetEvent.subscribe(this._onReset, this);
980
981         Logger.categoryCreateEvent.subscribe(this._onCategoryCreate, this);
982         Logger.sourceCreateEvent.subscribe(this._onSourceCreate, this);
983
984         this.rendered = true;
985
986         this._filterLogs();
987     },
988
989     /**
990      * Removes the UI from the DOM entirely and detaches all event listeners.
991      * Implementers should note that Logger will still accumulate messages.
992      *
993      * @method destroy
994      */
995     destroy : function () {
996         Event.purgeElement(this._elContainer,true);
997         this._elContainer.innerHTML = '';
998         this._elContainer.parentNode.removeChild(this._elContainer);
999
1000         this.rendered = false;
1001     },
1002
1003     /**
1004      * Hides UI of LogReader. Logging functionality is not disrupted.
1005      *
1006      * @method hide
1007      */
1008     hide : function() {
1009         this._elContainer.style.display = "none";
1010     },
1011
1012     /**
1013      * Shows UI of LogReader. Logging functionality is not disrupted.
1014      *
1015      * @method show
1016      */
1017     show : function() {
1018         this._elContainer.style.display = "block";
1019     },
1020
1021     /**
1022      * Collapses UI of LogReader. Logging functionality is not disrupted.
1023      *
1024      * @method collapse
1025      */
1026     collapse : function() {
1027         this._elConsole.style.display = "none";
1028         if(this._elFt) {
1029             this._elFt.style.display = "none";
1030         }
1031         this._btnCollapse.value = "Expand";
1032         this.isCollapsed = true;
1033     },
1034
1035     /**
1036      * Expands UI of LogReader. Logging functionality is not disrupted.
1037      *
1038      * @method expand
1039      */
1040     expand : function() {
1041         this._elConsole.style.display = "block";
1042         if(this._elFt) {
1043             this._elFt.style.display = "block";
1044         }
1045         this._btnCollapse.value = "Collapse";
1046         this.isCollapsed = false;
1047     },
1048
1049     /**
1050      * Returns related checkbox element for given filter (i.e., category or source).
1051      *
1052      * @method getCheckbox
1053      * @param {String} Category or source name.
1054      * @return {Array} Array of all filter checkboxes.
1055      */
1056     getCheckbox : function(filter) {
1057         return this._filterCheckboxes[filter];
1058     },
1059
1060     /**
1061      * Returns array of enabled categories.
1062      *
1063      * @method getCategories
1064      * @return {String[]} Array of enabled categories.
1065      */
1066     getCategories : function() {
1067         return this._categoryFilters;
1068     },
1069
1070     /**
1071      * Shows log messages associated with given category.
1072      *
1073      * @method showCategory
1074      * @param {String} Category name.
1075      */
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) {
1082                 return;
1083             }
1084         }
1085         // ...or do it the old-fashioned way
1086         else {
1087             for(var i=0; i<filtersArray.length; i++) {
1088                if(filtersArray[i] === sCategory){
1089                     return;
1090                 }
1091             }
1092         }
1093
1094         this._categoryFilters.push(sCategory);
1095         this._filterLogs();
1096         var elCheckbox = this.getCheckbox(sCategory);
1097         if(elCheckbox) {
1098             elCheckbox.checked = true;
1099         }
1100     },
1101
1102     /**
1103      * Hides log messages associated with given category.
1104      *
1105      * @method hideCategory
1106      * @param {String} Category name.
1107      */
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);
1113                 break;
1114             }
1115         }
1116         this._filterLogs();
1117         var elCheckbox = this.getCheckbox(sCategory);
1118         if(elCheckbox) {
1119             elCheckbox.checked = false;
1120         }
1121     },
1122
1123     /**
1124      * Returns array of enabled sources.
1125      *
1126      * @method getSources
1127      * @return {Array} Array of enabled sources.
1128      */
1129     getSources : function() {
1130         return this._sourceFilters;
1131     },
1132
1133     /**
1134      * Shows log messages associated with given source.
1135      *
1136      * @method showSource
1137      * @param {String} Source name.
1138      */
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) {
1145                 return;
1146             }
1147         }
1148         // ...or do it the old-fashioned way
1149         else {
1150             for(var i=0; i<filtersArray.length; i++) {
1151                if(sSource == filtersArray[i]){
1152                     return;
1153                 }
1154             }
1155         }
1156         filtersArray.push(sSource);
1157         this._filterLogs();
1158         var elCheckbox = this.getCheckbox(sSource);
1159         if(elCheckbox) {
1160             elCheckbox.checked = true;
1161         }
1162     },
1163
1164     /**
1165      * Hides log messages associated with given source.
1166      *
1167      * @method hideSource
1168      * @param {String} Source name.
1169      */
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);
1175                 break;
1176             }
1177         }
1178         this._filterLogs();
1179         var elCheckbox = this.getCheckbox(sSource);
1180         if(elCheckbox) {
1181             elCheckbox.checked = false;
1182         }
1183     },
1184
1185     /**
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.
1190      *
1191      * @method clearConsole
1192      */
1193     clearConsole : function() {
1194         // Clear the buffer of any pending messages
1195         this._timeout = null;
1196         this._buffer = [];
1197         this._consoleMsgCount = 0;
1198
1199         var elConsole = this._elConsole;
1200         elConsole.innerHTML = '';
1201     },
1202
1203     /**
1204      * Updates title to given string.
1205      *
1206      * @method setTitle
1207      * @param sTitle {String} New title.
1208      */
1209     setTitle : function(sTitle) {
1210         this._title.innerHTML = this.html2Text(sTitle);
1211     },
1212
1213     /**
1214      * Gets timestamp of the last log.
1215      *
1216      * @method getLastTime
1217      * @return {Date} Timestamp of the last log.
1218      */
1219     getLastTime : function() {
1220         return this._lastTime;
1221     },
1222
1223     formatMsg : function (entry) {
1224         var entryFormat = this.entryFormat || (this.verboseOutput ?
1225                           LogReader.VERBOSE_TEMPLATE : LogReader.BASIC_TEMPLATE),
1226             info        = {
1227                 category : entry.category,
1228
1229                 // Label for color-coded display
1230                 label : entry.category.substring(0,4).toUpperCase(),
1231
1232                 sourceAndDetail : entry.sourceDetail ?
1233                                   entry.source + " " + entry.sourceDetail :
1234                                   entry.source,
1235
1236                 // Escape HTML entities in the log message itself for output
1237                 // to console
1238                 message : this.html2Text(entry.msg || entry.message || '')
1239             };
1240
1241         // Add time info
1242         if (entry.time && entry.time.getTime) {
1243             info.localTime = entry.time.toLocaleTimeString ?
1244                              entry.time.toLocaleTimeString() :
1245                              entry.time.toString();
1246
1247             // Calculate the elapsed time to be from the last item that
1248             // passed through the filter, not the absolute previous item
1249             // in the stack
1250             info.elapsedTime = entry.time.getTime() - this.getLastTime();
1251
1252             info.totalTime = entry.time.getTime() - Logger.getStartTime();
1253         }
1254
1255         var msg = LogReader.ENTRY_TEMPLATE.cloneNode(true);
1256         if (this.verboseOutput) {
1257             msg.className += ' yui-log-verbose';
1258         }
1259
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] : '';
1264             });
1265
1266         return msg;
1267     },
1268
1269     /**
1270      * Converts input chars "<", ">", and "&" to HTML entities.
1271      *
1272      * @method html2Text
1273      * @param sHtml {String} String to convert.
1274      * @private
1275      */
1276     html2Text : function(sHtml) {
1277         if(sHtml) {
1278             sHtml += "";
1279             return sHtml.replace(/&/g, "&#38;").
1280                          replace(/</g, "&#60;").
1281                          replace(/>/g, "&#62;");
1282         }
1283         return "";
1284     },
1285
1286 /////////////////////////////////////////////////////////////////////////////
1287 //
1288 // Private member variables
1289 //
1290 /////////////////////////////////////////////////////////////////////////////
1291
1292     /**
1293      * Name of LogReader instance.
1294      *
1295      * @property _sName
1296      * @type String
1297      * @private
1298      */
1299     _sName : null,
1300
1301     //TODO: remove
1302     /**
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.
1306      *
1307      * @property _elDefaultContainer
1308      * @type HTMLElement
1309      * @private
1310      */
1311     //YAHOO.widget.LogReader._elDefaultContainer = null;
1312
1313     /**
1314      * Buffer of log message objects for batch output.
1315      *
1316      * @property _buffer
1317      * @type Object[]
1318      * @private
1319      */
1320     _buffer : null,
1321
1322     /**
1323      * Number of log messages output to console.
1324      *
1325      * @property _consoleMsgCount
1326      * @type Number
1327      * @default 0
1328      * @private
1329      */
1330     _consoleMsgCount : 0,
1331
1332     /**
1333      * Date of last output log message.
1334      *
1335      * @property _lastTime
1336      * @type Date
1337      * @private
1338      */
1339     _lastTime : null,
1340
1341     /**
1342      * Batched output timeout ID.
1343      *
1344      * @property _timeout
1345      * @type Number
1346      * @private
1347      */
1348     _timeout : null,
1349
1350     /**
1351      * Hash of filters and their related checkbox elements.
1352      *
1353      * @property _filterCheckboxes
1354      * @type Object
1355      * @private
1356      */
1357     _filterCheckboxes : null,
1358
1359     /**
1360      * Array of filters for log message categories.
1361      *
1362      * @property _categoryFilters
1363      * @type String[]
1364      * @private
1365      */
1366     _categoryFilters : null,
1367
1368     /**
1369      * Array of filters for log message sources.
1370      *
1371      * @property _sourceFilters
1372      * @type String[]
1373      * @private
1374      */
1375     _sourceFilters : null,
1376
1377     /**
1378      * LogReader container element.
1379      *
1380      * @property _elContainer
1381      * @type HTMLElement
1382      * @private
1383      */
1384     _elContainer : null,
1385
1386     /**
1387      * LogReader header element.
1388      *
1389      * @property _elHd
1390      * @type HTMLElement
1391      * @private
1392      */
1393     _elHd : null,
1394
1395     /**
1396      * LogReader collapse element.
1397      *
1398      * @property _elCollapse
1399      * @type HTMLElement
1400      * @private
1401      */
1402     _elCollapse : null,
1403
1404     /**
1405      * LogReader collapse button element.
1406      *
1407      * @property _btnCollapse
1408      * @type HTMLElement
1409      * @private
1410      */
1411     _btnCollapse : null,
1412
1413     /**
1414      * LogReader title header element.
1415      *
1416      * @property _title
1417      * @type HTMLElement
1418      * @private
1419      */
1420     _title : null,
1421
1422     /**
1423      * LogReader console element.
1424      *
1425      * @property _elConsole
1426      * @type HTMLElement
1427      * @private
1428      */
1429     _elConsole : null,
1430
1431     /**
1432      * LogReader footer element.
1433      *
1434      * @property _elFt
1435      * @type HTMLElement
1436      * @private
1437      */
1438     _elFt : null,
1439
1440     /**
1441      * LogReader buttons container element.
1442      *
1443      * @property _elBtns
1444      * @type HTMLElement
1445      * @private
1446      */
1447     _elBtns : null,
1448
1449     /**
1450      * Container element for LogReader category filter checkboxes.
1451      *
1452      * @property _elCategoryFilters
1453      * @type HTMLElement
1454      * @private
1455      */
1456     _elCategoryFilters : null,
1457
1458     /**
1459      * Container element for LogReader source filter checkboxes.
1460      *
1461      * @property _elSourceFilters
1462      * @type HTMLElement
1463      * @private
1464      */
1465     _elSourceFilters : null,
1466
1467     /**
1468      * LogReader pause button element.
1469      *
1470      * @property _btnPause
1471      * @type HTMLElement
1472      * @private
1473      */
1474     _btnPause : null,
1475
1476     /**
1477      * Clear button element.
1478      *
1479      * @property _btnClear
1480      * @type HTMLElement
1481      * @private
1482      */
1483     _btnClear : null,
1484
1485     /////////////////////////////////////////////////////////////////////////////
1486     //
1487     // Private methods
1488     //
1489     /////////////////////////////////////////////////////////////////////////////
1490
1491     /**
1492      * Initializes the instance's message buffer, start time, etc
1493      *
1494      * @method _init
1495      * @param container {String|HTMLElement} (optional) the render target
1496      * @param config {Object} (optional) instance configuration
1497      * @protected
1498      */
1499     _init : function (container, config) {
1500         // Internal vars
1501         this._buffer = []; // output buffer
1502         this._filterCheckboxes = {}; // pointers to checkboxes
1503         this._lastTime = Logger.getStartTime(); // timestamp of last log message to console
1504
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];
1510                 }
1511             }
1512         }
1513
1514         this._elContainer = Dom.get(container);
1515
1516         YAHOO.log("LogReader initialized", null, this.toString());
1517     },
1518
1519     /**
1520      * Initializes the primary container element.
1521      *
1522      * @method _initContainerEl
1523      * @private
1524      */
1525     _initContainerEl : function() {
1526
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");
1532         }
1533
1534         Dom.addClass(this._elContainer,"yui-log");
1535
1536         // If implementer has provided container values, trust and set those
1537         var style = this._elContainer.style,
1538             styleProps = ['width','right','top','fontSize'],
1539             prop,i;
1540
1541         for (i = styleProps.length - 1; i >= 0; --i) {
1542             prop = styleProps[i];
1543             if (this[prop]){ 
1544                 style[prop] = this[prop];
1545             }
1546         }
1547
1548         if(this.left) {
1549             style.left  = this.left;
1550             style.right = "auto";
1551         }
1552         if(this.bottom) {
1553             style.bottom = this.bottom;
1554             style.top    = "auto";
1555         }
1556
1557         // Opera needs a little prodding to reflow sometimes
1558         if (YAHOO.env.ua.opera) {
1559             d.body.style += '';
1560         }
1561
1562     },
1563
1564     /**
1565      * Initializes the header element.
1566      *
1567      * @method _initHeaderEl
1568      * @private
1569      */
1570     _initHeaderEl : function() {
1571         // Destroy header if present
1572         if(this._elHd) {
1573             // Unhook DOM events
1574             Event.purgeElement(this._elHd, true);
1575
1576             // Remove DOM elements
1577             this._elHd.innerHTML = "";
1578         }
1579         
1580         // Create header
1581         // TODO: refactor this into an innerHTML
1582         this._elHd = make("div",{
1583             id: 'yui-log-hd' + this._sName,
1584             className: "yui-log-hd"
1585         });
1586
1587         this._elCollapse = make("div",{ className: 'yui-log-btns' });
1588
1589         this._btnCollapse = make("input",{
1590             type: 'button',
1591             className: 'yui-log-button',
1592             value: 'Collapse'
1593         });
1594         Event.on(this._btnCollapse,'click',this._onClickCollapseBtn,this);
1595
1596
1597         this._title = make("h4",{ innerHTML : "Logger Console" });
1598
1599         this._elCollapse.appendChild(this._btnCollapse);
1600         this._elHd.appendChild(this._elCollapse);
1601         this._elHd.appendChild(this._title);
1602         this._elContainer.appendChild(this._elHd);
1603     },
1604
1605     /**
1606      * Initializes the console element.
1607      *
1608      * @method _initConsoleEl
1609      * @private
1610      */
1611     _initConsoleEl : function() {
1612         // Destroy console
1613         if(this._elConsole) {
1614             // Unhook DOM events
1615             Event.purgeElement(this._elConsole, true);
1616
1617             // Remove DOM elements
1618             this._elConsole.innerHTML = "";
1619         }
1620
1621         // Ceate console
1622         this._elConsole = make("div", { className: "yui-log-bd" });
1623
1624         // If implementer has provided console, trust and set those
1625         if(this.height) {
1626             this._elConsole.style.height = this.height;
1627         }
1628
1629         this._elContainer.appendChild(this._elConsole);
1630     },
1631
1632     /**
1633      * Initializes the footer element.
1634      *
1635      * @method _initFooterEl
1636      * @private
1637      */
1638     _initFooterEl : function() {
1639         // Don't create footer elements if footer is disabled
1640         if(this.footerEnabled) {
1641             // Destroy console
1642             if(this._elFt) {
1643                 // Unhook DOM events
1644                 Event.purgeElement(this._elFt, true);
1645
1646                 // Remove DOM elements
1647                 this._elFt.innerHTML = "";
1648             }
1649
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", {
1654                 type: "button",
1655                 className: "yui-log-button",
1656                 value: "Pause"
1657             });
1658
1659             Event.on(this._btnPause,'click',this._onClickPauseBtn,this);
1660
1661             this._btnClear = make("input", {
1662                 type: "button",
1663                 className: "yui-log-button",
1664                 value: "Clear"
1665             });
1666
1667             Event.on(this._btnClear,'click',this._onClickClearBtn,this);
1668
1669             this._elCategoryFilters = make("div", { className: "yui-log-categoryfilters" });
1670             this._elSourceFilters = make("div", { className: "yui-log-sourcefilters" });
1671
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);
1678         }
1679     },
1680
1681     /**
1682      * Initializes Drag and Drop on the header element.
1683      *
1684      * @method _initDragDrop
1685      * @private
1686      */
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";
1696         }
1697     },
1698
1699     /**
1700      * Initializes category filters.
1701      *
1702      * @method _initCategories
1703      * @private
1704      */
1705     _initCategories : function() {
1706         // Initialize category filters
1707         this._categoryFilters = [];
1708         var aInitialCategories = Logger.categories;
1709
1710         for(var j=0; j < aInitialCategories.length; j++) {
1711             var sCategory = aInitialCategories[j];
1712
1713             // Add category to the internal array of filters
1714             this._categoryFilters.push(sCategory);
1715
1716             // Add checkbox element if UI is enabled
1717             if(this._elCategoryFilters) {
1718                 this._createCategoryCheckbox(sCategory);
1719             }
1720         }
1721     },
1722
1723     /**
1724      * Initializes source filters.
1725      *
1726      * @method _initSources
1727      * @private
1728      */
1729     _initSources : function() {
1730         // Initialize source filters
1731         this._sourceFilters = [];
1732         var aInitialSources = Logger.sources;
1733
1734         for(var j=0; j < aInitialSources.length; j++) {
1735             var sSource = aInitialSources[j];
1736
1737             // Add source to the internal array of filters
1738             this._sourceFilters.push(sSource);
1739
1740             // Add checkbox element if UI is enabled
1741             if(this._elSourceFilters) {
1742                 this._createSourceCheckbox(sSource);
1743             }
1744         }
1745     },
1746
1747     /**
1748      * Creates the UI for a category filter in the LogReader footer element.
1749      *
1750      * @method _createCategoryCheckbox
1751      * @param sCategory {String} Category name.
1752      * @private
1753      */
1754     _createCategoryCheckbox : function(sCategory) {
1755         if(this._elFt) {
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,
1760                     type: "checkbox",
1761                     category: sCategory
1762                 }),
1763                 label  = make("label", {
1764                     htmlFor: check.id,
1765                     className: sCategory,
1766                     innerHTML: sCategory
1767                 });
1768             
1769
1770             // Subscribe to the click event
1771             Event.on(check,'click',this._onCheckCategory,this);
1772
1773             this._filterCheckboxes[sCategory] = check;
1774
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;
1781         }
1782     },
1783
1784     /**
1785      * Creates a checkbox in the LogReader footer element to filter by source.
1786      *
1787      * @method _createSourceCheckbox
1788      * @param sSource {String} Source name.
1789      * @private
1790      */
1791     _createSourceCheckbox : function(sSource) {
1792         if(this._elFt) {
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,
1797                     type: "checkbox",
1798                     source: sSource
1799                 }),
1800                 label  = make("label", {
1801                     htmlFor: check.id,
1802                     className: sSource,
1803                     innerHTML: sSource
1804                 });
1805             
1806
1807             // Subscribe to the click event
1808             Event.on(check,'click',this._onCheckSource,this);
1809
1810             this._filterCheckboxes[sSource] = check;
1811
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;
1818         }
1819     },
1820
1821     /**
1822      * Reprints all log messages in the stack through filters.
1823      *
1824      * @method _filterLogs
1825      * @private
1826      */
1827     _filterLogs : function() {
1828         // Reprint stack with new filters
1829         if (this._elConsole !== null) {
1830             this.clearConsole();
1831             this._printToConsole(Logger.getStack());
1832         }
1833     },
1834
1835     /**
1836      * Sends buffer of log messages to output and clears buffer.
1837      *
1838      * @method _printBuffer
1839      * @private
1840      */
1841     _printBuffer : function() {
1842         this._timeout = null;
1843
1844         if(this._elConsole !== null) {
1845             var thresholdMax = this.thresholdMax;
1846             thresholdMax = (thresholdMax && !isNaN(thresholdMax)) ? thresholdMax : 500;
1847             if(this._consoleMsgCount < thresholdMax) {
1848                 var entries = [];
1849                 for (var i=0; i<this._buffer.length; i++) {
1850                     entries[i] = this._buffer[i];
1851                 }
1852                 this._buffer = [];
1853                 this._printToConsole(entries);
1854             }
1855             else {
1856                 this._filterLogs();
1857             }
1858             
1859             if(!this.newestOnTop) {
1860                 this._elConsole.scrollTop = this._elConsole.scrollHeight;
1861             }
1862         }
1863     },
1864
1865     /**
1866      * Cycles through an array of log messages, and outputs each one to the console
1867      * if its category has not been filtered out.
1868      *
1869      * @method _printToConsole
1870      * @param aEntries {Object[]} Array of LogMsg objects to output to console.
1871      * @private
1872      */
1873     _printToConsole : function(aEntries) {
1874         // Manage the number of messages displayed in the console
1875         var entriesLen         = aEntries.length,
1876             df                 = d.createDocumentFragment(),
1877             msgHTML            = [],
1878             thresholdMin       = this.thresholdMin,
1879             sourceFiltersLen   = this._sourceFilters.length,
1880             categoryFiltersLen = this._categoryFilters.length,
1881             entriesStartIndex,
1882             i, j, msg, before;
1883
1884         if(isNaN(thresholdMin) || (thresholdMin > this.thresholdMax)) {
1885             thresholdMin = 0;
1886         }
1887         entriesStartIndex = (entriesLen > thresholdMin) ? (entriesLen - thresholdMin) : 0;
1888         
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;
1897
1898             for(j=0; j<sourceFiltersLen; j++) {
1899                 if(source == this._sourceFilters[j]) {
1900                     okToFilterCats = true;
1901                     break;
1902                 }
1903             }
1904             if(okToFilterCats) {
1905                 for(j=0; j<categoryFiltersLen; j++) {
1906                     if(category == this._categoryFilters[j]) {
1907                         okToPrint = true;
1908                         break;
1909                     }
1910                 }
1911             }
1912             if(okToPrint) {
1913                 // Start from 0ms elapsed time
1914                 if (this._consoleMsgCount === 0) {
1915                     this._lastTime = entry.time.getTime();
1916                 }
1917
1918                 msg = this.formatMsg(entry);
1919                 if (typeof msg === 'string') {
1920                     msgHTML[msgHTML.length] = msg;
1921                 } else {
1922                     df.insertBefore(msg, this.newestOnTop ?
1923                         df.firstChild || null : null);
1924                 }
1925                 this._consoleMsgCount++;
1926                 this._lastTime = entry.time.getTime();
1927             }
1928         }
1929
1930         if (msgHTML.length) {
1931             msgHTML.splice(0,0,this._elConsole.innerHTML);
1932             this._elConsole.innerHTML = this.newestOnTop ?
1933                                             msgHTML.reverse().join('') :
1934                                             msgHTML.join('');
1935         } else if (df.firstChild) {
1936             this._elConsole.insertBefore(df, this.newestOnTop ?
1937                         this._elConsole.firstChild || null : null);
1938         }
1939     },
1940
1941 /////////////////////////////////////////////////////////////////////////////
1942 //
1943 // Private event handlers
1944 //
1945 /////////////////////////////////////////////////////////////////////////////
1946
1947     /**
1948      * Handles Logger's categoryCreateEvent.
1949      *
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.
1954      * @private
1955      */
1956     _onCategoryCreate : function(sType, aArgs, oSelf) {
1957         var category = aArgs[0];
1958         
1959         // Add category to the internal array of filters
1960         oSelf._categoryFilters.push(category);
1961
1962         if(oSelf._elFt) {
1963             oSelf._createCategoryCheckbox(category);
1964         }
1965     },
1966
1967     /**
1968      * Handles Logger's sourceCreateEvent.
1969      *
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.
1974      * @private
1975      */
1976     _onSourceCreate : function(sType, aArgs, oSelf) {
1977         var source = aArgs[0];
1978         
1979         // Add source to the internal array of filters
1980         oSelf._sourceFilters.push(source);
1981
1982         if(oSelf._elFt) {
1983             oSelf._createSourceCheckbox(source);
1984         }
1985     },
1986
1987     /**
1988      * Handles check events on the category filter checkboxes.
1989      *
1990      * @method _onCheckCategory
1991      * @param v {HTMLEvent} The click event.
1992      * @param oSelf {Object} The LogReader instance.
1993      * @private
1994      */
1995     _onCheckCategory : function(v, oSelf) {
1996         var category = this.category;
1997         if(!this.checked) {
1998             oSelf.hideCategory(category);
1999         }
2000         else {
2001             oSelf.showCategory(category);
2002         }
2003     },
2004
2005     /**
2006      * Handles check events on the category filter checkboxes.
2007      *
2008      * @method _onCheckSource
2009      * @param v {HTMLEvent} The click event.
2010      * @param oSelf {Object} The LogReader instance.
2011      * @private
2012      */
2013     _onCheckSource : function(v, oSelf) {
2014         var source = this.source;
2015         if(!this.checked) {
2016             oSelf.hideSource(source);
2017         }
2018         else {
2019             oSelf.showSource(source);
2020         }
2021     },
2022
2023     /**
2024      * Handles click events on the collapse button.
2025      *
2026      * @method _onClickCollapseBtn
2027      * @param v {HTMLEvent} The click event.
2028      * @param oSelf {Object} The LogReader instance
2029      * @private
2030      */
2031     _onClickCollapseBtn : function(v, oSelf) {
2032         if(!oSelf.isCollapsed) {
2033             oSelf.collapse();
2034         }
2035         else {
2036             oSelf.expand();
2037         }
2038     },
2039
2040     /**
2041      * Handles click events on the pause button.
2042      *
2043      * @method _onClickPauseBtn
2044      * @param v {HTMLEvent} The click event.
2045      * @param oSelf {Object} The LogReader instance.
2046      * @private
2047      */
2048     _onClickPauseBtn : function(v, oSelf) {
2049         if(!oSelf.isPaused) {
2050             oSelf.pause();
2051         }
2052         else {
2053             oSelf.resume();
2054         }
2055     },
2056
2057     /**
2058      * Handles click events on the clear button.
2059      *
2060      * @method _onClickClearBtn
2061      * @param v {HTMLEvent} The click event.
2062      * @param oSelf {Object} The LogReader instance.
2063      * @private
2064      */
2065     _onClickClearBtn : function(v, oSelf) {
2066         oSelf.clearConsole();
2067     },
2068
2069     /**
2070      * Handles Logger's newLogEvent.
2071      *
2072      * @method _onNewLog
2073      * @param sType {String} The event.
2074      * @param aArgs {Object[]} Data passed from event firer.
2075      * @param oSelf {Object} The LogReader instance.
2076      * @private
2077      */
2078     _onNewLog : function(sType, aArgs, oSelf) {
2079         var logEntry = aArgs[0];
2080         oSelf._buffer.push(logEntry);
2081
2082         if (oSelf.logReaderEnabled === true && oSelf._timeout === null) {
2083             oSelf._timeout = setTimeout(function(){oSelf._printBuffer();}, oSelf.outputBuffer);
2084         }
2085     },
2086
2087     /**
2088      * Handles Logger's resetEvent.
2089      *
2090      * @method _onReset
2091      * @param sType {String} The event.
2092      * @param aArgs {Object[]} Data passed from event firer.
2093      * @param oSelf {Object} The LogReader instance.
2094      * @private
2095      */
2096     _onReset : function(sType, aArgs, oSelf) {
2097         oSelf._filterLogs();
2098     }
2099 };
2100
2101 YAHOO.widget.LogReader = LogReader;
2102
2103 })();
2104 YAHOO.register("logger", YAHOO.widget.Logger, {version: "2.8.0r4", build: "2449"});