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