]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - jssource/src_files/include/javascript/yui3/build/console/console.js
Release 6.2.0beta4
[Github/sugarcrm.git] / jssource / src_files / include / javascript / yui3 / build / console / console.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: 3.0.0
6 build: 1549
7 */
8 YUI.add('console', function(Y) {
9
10 /**
11  * Console creates a visualization for messages logged through calls to a YUI
12  * instance's <code>Y.log( message, category, source )</code> method.  The
13  * debug versions of YUI modules will include logging statements to offer some
14  * insight into the steps executed during that module's operation.  Including
15  * log statements in your code will cause those messages to also appear in the
16  * Console.  Use Console to aid in developing your page or application.
17  *
18  * Entry categories &quot;info&quot;, &quot;warn&quot;, and &quot;error&quot;
19  * are also referred to as the log level, and entries are filtered against the
20  * configured logLevel.
21  *
22  * @module console
23  * @class Console
24  * @extends Widget
25  * @param conf {Object} Configuration object (see Configuration attributes)
26  * @constructor
27  */
28 function Console() {
29     Console.superclass.constructor.apply(this,arguments);
30 }
31
32 var getCN = Y.ClassNameManager.getClassName,
33     CHECKED        = 'checked',
34     CLEAR          = 'clear',
35     CLICK          = 'click',
36     COLLAPSED      = 'collapsed',
37     CONSOLE        = 'console',
38     CONTENT_BOX    = 'contentBox',
39     DISABLED       = 'disabled',
40     ENTRY          = 'entry',
41     ERROR          = 'error',
42     HEIGHT         = 'height',
43     INFO           = 'info',
44     INNER_HTML     = 'innerHTML',
45     LAST_TIME      = 'lastTime',
46     PAUSE          = 'pause',
47     PAUSED         = 'paused',
48     RESET          = 'reset',
49     START_TIME     = 'startTime',
50     TITLE          = 'title',
51     WARN           = 'warn',
52
53     DOT = '.',
54
55     C_BUTTON           = getCN(CONSOLE,'button'),
56     C_CHECKBOX         = getCN(CONSOLE,'checkbox'),
57     C_CLEAR            = getCN(CONSOLE,CLEAR),
58     C_COLLAPSE         = getCN(CONSOLE,'collapse'),
59     C_COLLAPSED        = getCN(CONSOLE,COLLAPSED),
60     C_CONSOLE_CONTROLS = getCN(CONSOLE,'controls'),
61     C_CONSOLE_HD       = getCN(CONSOLE,'hd'),
62     C_CONSOLE_BD       = getCN(CONSOLE,'bd'),
63     C_CONSOLE_FT       = getCN(CONSOLE,'ft'),
64     C_CONSOLE_TITLE    = getCN(CONSOLE,TITLE),
65     C_ENTRY            = getCN(CONSOLE,ENTRY),
66     C_ENTRY_CAT        = getCN(CONSOLE,ENTRY,'cat'),
67     C_ENTRY_CONTENT    = getCN(CONSOLE,ENTRY,'content'),
68     C_ENTRY_META       = getCN(CONSOLE,ENTRY,'meta'),
69     C_ENTRY_SRC        = getCN(CONSOLE,ENTRY,'src'),
70     C_ENTRY_TIME       = getCN(CONSOLE,ENTRY,'time'),
71     C_PAUSE            = getCN(CONSOLE,PAUSE),
72     C_PAUSE_LABEL      = getCN(CONSOLE,PAUSE,'label'),
73
74     RE_INLINE_SOURCE = /^(\S+)\s/,
75     RE_AMP = /&/g,
76     RE_GT  = />/g,
77     RE_LT  = /</g,
78
79     ESC_AMP = '&#38;',
80     ESC_GT  = '&#62;',
81     ESC_LT  = '&#60;',
82     
83     ENTRY_TEMPLATE_STR =
84         '<div class="{entry_class} {cat_class} {src_class}">'+
85             '<p class="{entry_meta_class}">'+
86                 '<span class="{entry_src_class}">'+
87                     '{sourceAndDetail}'+
88                 '</span>'+
89                 '<span class="{entry_cat_class}">'+
90                     '{category}</span>'+
91                 '<span class="{entry_time_class}">'+
92                     ' {totalTime}ms (+{elapsedTime}) {localTime}'+
93                 '</span>'+
94             '</p>'+
95             '<pre class="{entry_content_class}">{message}</pre>'+
96         '</div>',
97
98     L = Y.Lang,
99     create     = Y.Node.create,
100     isNumber   = L.isNumber,
101     isString   = L.isString,
102     merge      = Y.merge,
103     substitute = Y.substitute;
104     
105
106 Y.mix(Console, {
107
108     /**
109      * The identity of the widget.
110      *
111      * @property Console.NAME
112      * @type String
113      * @static
114      */
115     NAME : CONSOLE,
116
117     /**
118      * Static identifier for logLevel configuration setting to allow all
119      * incoming messages to generate Console entries.
120      *
121      * @property Console.LOG_LEVEL_INFO
122      * @type String
123      * @static
124      */
125     LOG_LEVEL_INFO  : INFO,
126
127     /**
128      * Static identifier for logLevel configuration setting to allow only
129      * incoming messages of logLevel &quot;warn&quot; or &quot;error&quot;
130      * to generate Console entries.
131      *
132      * @property Console.LOG_LEVEL_WARN
133      * @type String
134      * @static
135      */
136     LOG_LEVEL_WARN  : WARN,
137
138     /**
139      * Static identifier for logLevel configuration setting to allow only
140      * incoming messages of logLevel &quot;error&quot; to generate
141      * Console entries.
142      *
143      * @property Console.LOG_LEVEL_ERROR
144      * @type String
145      * @static
146      */
147     LOG_LEVEL_ERROR : ERROR,
148
149     /**
150      * Map (object) of classNames used to populate the placeholders in the
151      * Console.ENTRY_TEMPLATE markup when rendering a new Console entry.
152      *
153      * <p>By default, the keys contained in the object are:</p>
154      * <ul>
155      *    <li>entry_class</li>
156      *    <li>entry_meta_class</li>
157      *    <li>entry_cat_class</li>
158      *    <li>entry_src_class</li>
159      *    <li>entry_time_class</li>
160      *    <li>entry_content_class</li>
161      * </ul>
162      *
163      * @property Console.ENTRY_CLASSES
164      * @type Object
165      * @static
166      */
167     ENTRY_CLASSES   : {
168         entry_class         : C_ENTRY,
169         entry_meta_class    : C_ENTRY_META,
170         entry_cat_class     : C_ENTRY_CAT,
171         entry_src_class     : C_ENTRY_SRC,
172         entry_time_class    : C_ENTRY_TIME,
173         entry_content_class : C_ENTRY_CONTENT
174     },
175
176     /**
177      * Map (object) of classNames used to populate the placeholders in the
178      * Console.HEADER_TEMPLATE, Console.BODY_TEMPLATE, and
179      * Console.FOOTER_TEMPLATE markup when rendering the Console UI.
180      *
181      * <p>By default, the keys contained in the object are:</p>
182      * <ul>
183      *   <li>console_hd_class</li>
184      *   <li>console_bd_class</li>
185      *   <li>console_ft_class</li>
186      *   <li>console_controls_class</li>
187      *   <li>console_checkbox_class</li>
188      *   <li>console_pause_class</li>
189      *   <li>console_pause_label_class</li>
190      *   <li>console_button_class</li>
191      *   <li>console_clear_class</li>
192      *   <li>console_collapse_class</li>
193      *   <li>console_title_class</li>
194      * </ul>
195      *
196      * @property Console.CHROME_CLASSES
197      * @type Object
198      * @static
199      */
200     CHROME_CLASSES  : {
201         console_hd_class       : C_CONSOLE_HD,
202         console_bd_class       : C_CONSOLE_BD,
203         console_ft_class       : C_CONSOLE_FT,
204         console_controls_class : C_CONSOLE_CONTROLS,
205         console_checkbox_class : C_CHECKBOX,
206         console_pause_class    : C_PAUSE,
207         console_pause_label_class : C_PAUSE_LABEL,
208         console_button_class   : C_BUTTON,
209         console_clear_class    : C_CLEAR,
210         console_collapse_class : C_COLLAPSE,
211         console_title_class    : C_CONSOLE_TITLE
212     },
213
214     /**
215      * Markup template used to generate the DOM structure for the header
216      * section of the Console when it is rendered.  The template includes
217      * these {placeholder}s:
218      *
219      * <ul>
220      *   <li>console_button_class - contributed by Console.CHROME_CLASSES</li>
221      *   <li>console_collapse_class - contributed by Console.CHROME_CLASSES</li>
222      *   <li>console_hd_class - contributed by Console.CHROME_CLASSES</li>
223      *   <li>console_title_class - contributed by Console.CHROME_CLASSES</li>
224      *   <li>str_collapse - pulled from attribute strings.collapse</li>
225      *   <li>str_title - pulled from attribute strings.title</li>
226      * </ul>
227      *
228      * @property Console.HEADER_TEMPLATE
229      * @type String
230      * @static
231      */
232     HEADER_TEMPLATE :
233         '<div class="{console_hd_class}">'+
234             '<h4 class="{console_title_class}">{str_title}</h4>'+
235             '<button type="button" class="'+
236                 '{console_button_class} {console_collapse_class}">{str_collapse}'+
237             '</button>'+
238         '</div>',
239
240     /**
241      * Markup template used to generate the DOM structure for the Console body
242      * (where the messages are inserted) when it is rendered.  The template
243      * includes only the {placeholder} &quot;console_bd_class&quot;, which is
244      * constributed by Console.CHROME_CLASSES.
245      *
246      * @property Console.BODY_TEMPLATE
247      * @type String
248      * @static
249      */
250     BODY_TEMPLATE : '<div class="{console_bd_class}"></div>',
251
252     /**
253      * Markup template used to generate the DOM structure for the footer
254      * section of the Console when it is rendered.  The template includes
255      * many of the {placeholder}s from Console.CHROME_CLASSES as well as:
256      *
257      * <ul>
258      *   <li>id_guid - generated unique id, relates the label and checkbox</li>
259      *   <li>str_pause - pulled from attribute strings.pause</li>
260      *   <li>str_clear - pulled from attribute strings.clear</li>
261      * </ul>
262      *
263      * @property Console.FOOTER_TEMPLATE
264      * @type String
265      * @static
266      */
267     FOOTER_TEMPLATE :
268         '<div class="{console_ft_class}">'+
269             '<div class="{console_controls_class}">'+
270                 '<label for="{id_guid}" class="{console_pause_label_class}">'+
271                     '<input type="checkbox" class="{console_checkbox_class} '+
272                         '{console_pause_class}" value="1" id="{id_guid}"> '+
273                     '{str_pause}</label>' +
274                 '<button type="button" class="'+
275                     '{console_button_class} {console_clear_class}">{str_clear}'+
276                 '</button>'+
277             '</div>'+
278         '</div>',
279
280     /**
281      * Default markup template used to create the DOM structure for Console
282      * entries. The markup contains {placeholder}s for content and classes
283      * that are replaced via Y.substitute.  The default template contains
284      * the {placeholder}s identified in Console.ENTRY_CLASSES as well as the
285      * following placeholders that will be populated by the log entry data:
286      *
287      * <ul>
288      *   <li>cat_class</li>
289      *   <li>src_class</li>
290      *   <li>totalTime</li>
291      *   <li>elapsedTime</li>
292      *   <li>localTime</li>
293      *   <li>sourceAndDetail</li>
294      *   <li>message</li>
295      * </ul>
296      *
297      * @property Console.ENTRY_TEMPLATE
298      * @type String
299      * @static
300      */
301     ENTRY_TEMPLATE : ENTRY_TEMPLATE_STR,
302
303     /**
304      * Static property used to define the default attribute configuration of
305      * the Widget.
306      *
307      * @property Console.ATTRS
308      * @Type Object
309      * @static
310      */
311     ATTRS : {
312
313         /**
314          * Name of the custom event that will communicate log messages.
315          *
316          * @attribute logEvent
317          * @type String
318          * @default "yui:log"
319          */
320         logEvent : {
321             value : 'yui:log',
322             writeOnce : true,
323             validator : isString
324         },
325
326         /**
327          * Object that will emit the log events.  By default the YUI instance.
328          * To have a single Console capture events from all YUI instances, set
329          * this to the Y.Global object.
330          *
331          * @attribute logSource
332          * @type EventTarget
333          * @default Y
334          */
335         logSource : {
336             value : Y,
337             writeOnce : true,
338             validator : function (v) {
339                 return v && Y.Lang.isFunction(v.on);
340             }
341         },
342
343         /**
344          * Collection of strings used to label elements in the Console UI.
345          * Default collection contains the following name:value pairs:
346          *
347          * <ul>
348          *   <li>title : &quot;Log Console&quot;</li>
349          *   <li>pause : &quot;Pause&quot;</li>
350          *   <li>clear : &quot;Clear&quot;</li>
351          *   <li>collapse : &quot;Collapse&quot;</li>
352          *   <li>expand : &quot;Expand&quot;</li>
353          * </ul>
354          *
355          * @attribute strings
356          * @type Object
357          */
358         strings : {
359             value : {
360                 title : "Log Console",
361                 pause : "Pause",
362                 clear : "Clear",
363                 collapse : "Collapse",
364                 expand   : "Expand"
365             }
366         },
367
368         /**
369          * Boolean to pause the outputting of new messages to the console.
370          * When paused, messages will accumulate in the buffer.
371          *
372          * @attribute paused
373          * @type boolean
374          * @default false
375          */
376         paused : {
377             value : false,
378             validator : L.isBoolean
379         },
380
381         /**
382          * If a category is not specified in the Y.log(..) statement, this
383          * category will be used. Categories &quot;info&quot;,
384          * &quot;warn&quot;, and &quot;error&quot; are also called log level.
385          *
386          * @attribute defaultCategory
387          * @type String
388          * @default "info"
389          */
390         defaultCategory : {
391             value : INFO,
392             validator : isString
393         },
394
395         /**
396          * If a source is not specified in the Y.log(..) statement, this
397          * source will be used.
398          *
399          * @attribute defaultSource
400          * @type String
401          * @default "global"
402          */
403         defaultSource   : {
404             value : 'global',
405             validator : isString
406         },
407
408         /**
409          * Markup template used to create the DOM structure for Console entries.
410          *
411          * @attribute entryTemplate
412          * @type String
413          * @default Console.ENTRY_TEMPLATE
414          */
415         entryTemplate : {
416             value : ENTRY_TEMPLATE_STR,
417             validator : isString
418         },
419
420         /**
421          * Minimum entry log level to render into the Console.  The initial
422          * logLevel value for all Console instances defaults from the
423          * Y.config.logLevel YUI configuration, or Console.LOG_LEVEL_INFO if
424          * that configuration is not set.
425          *
426          * Possible values are &quot;info&quot;, &quot;warn&quot;,
427          * &quot;error&quot; (case insensitive), or their corresponding statics
428          * Console.LOG_LEVEL_INFO and so on.
429          *
430          * @attribute logLevel
431          * @type String
432          * @default Y.config.logLevel or Console.LOG_LEVEL_INFO
433          */
434         logLevel : {
435             value : Y.config.logLevel || INFO,
436             setter : function (v) {
437                 return this._setLogLevel(v);
438             }
439         },
440
441         /**
442          * Millisecond timeout between iterations of the print loop, moving
443          * entries from the buffer to the UI.
444          *
445          * @attribute printTimeout
446          * @type Number
447          * @default 100
448          */
449         printTimeout : {
450             value : 100,
451             validator : isNumber
452         },
453
454         /**
455          * Maximum number of entries printed in each iteration of the print
456          * loop. This is used to prevent excessive logging locking the page UI.
457          *
458          * @attribute printLimit
459          * @type Number
460          * @default 50
461          */
462         printLimit : {
463             value : 50,
464             validator : isNumber
465         },
466
467         /**
468          * Maximum number of Console entries allowed in the Console body at one
469          * time.  This is used to keep acquired messages from exploding the
470          * DOM tree and impacting page performance.
471          *
472          * @attribute consoleLimit
473          * @type Number
474          * @default 300
475          */
476         consoleLimit : {
477             value : 300,
478             validator : isNumber
479         },
480
481         /**
482          * New entries should display at the top of the Console or the bottom?
483          *
484          * @attribute newestOnTop
485          * @type Boolean
486          * @default true
487          */
488         newestOnTop : {
489             value : true
490         },
491
492         /**
493          * When new entries are added to the Console UI, should they be
494          * scrolled into view?
495          *
496          * @attribute scrollIntoView
497          * @type Boolean
498          * @default true
499          */
500         scrollIntoView : {
501             value : true
502         },
503
504         /**
505          * The baseline time for this Console instance, used to measure elapsed
506          * time from the moment the console module is <code>use</code>d to the
507          * moment each new entry is logged (not rendered).
508          *
509          * This value is reset by the instance method myConsole.reset().
510          *
511          * @attribute startTime
512          * @type Date
513          * @default The moment the console module is <code>use</code>d
514          */
515         startTime : {
516             value : new Date()
517         },
518
519         /**
520          * The precise time the last entry was logged.  Used to measure elapsed
521          * time between log messages.
522          *
523          * @attribute lastTime
524          * @type Date
525          * @default The moment the console module is <code>use</code>d
526          */
527         lastTime : {
528             value : new Date(),
529             readOnly: true
530         },
531
532         /**
533          * Controls the collapsed state of the Console
534          *
535          * @attribute collapsed
536          * @type Boolean
537          * @default false
538          */
539         collapsed : {
540             value : false
541         },
542
543         /**
544         * String with units, or number, representing the height of the Console,
545         * inclusive of header and footer. If a number is provided, the default
546         * unit, defined by Widget's DEF_UNIT, property is used.
547         *
548         * @attribute height
549         * @default "300px"
550         * @type {String | Number}
551         */
552         height: {
553             value: "300px"
554         },
555
556         /**
557         * String with units, or number, representing the width of the Console.
558         * If a number is provided, the default unit, defined by Widget's
559         * DEF_UNIT, property is used.
560         *
561         * @attribute width
562         * @default "300px"
563         * @type {String | Number}
564         */
565         width: {
566             value: "300px"
567         },
568
569         /**
570          * Pass through to the YUI instance useBrowserConsole configuration.
571          * By default this is set to false, which will disable logging to the
572          * browser console when a Console instance is created.  If the
573          * logSource is not a YUI instance, this has no effect.
574          * 
575          * @attribute useBrowserConsole
576          * @type {Boolean}
577          * @default false
578          */
579          useBrowserConsole : {
580             lazyAdd: false,
581             value: false,
582             getter : function () {
583                 var logSource = this.get('logSource');
584                 return logSource instanceof YUI ?
585                     logSource.config.useBrowserConsole : null;
586             },
587             setter : function (v) {
588                 var logSource = this.get('logSource');
589                 if (logSource instanceof YUI) {
590                     v = !!v;
591                     logSource.config.useBrowserConsole = !!v;
592                     return v;
593                 } else {
594                     return Y.Attribute.INVALID_VALUE;
595                 }
596             }
597          },
598
599          /**
600           * Allows the Console to flow in the document.  Available values are
601           * 'inline', 'block', and 'separate' (the default).  
602           *
603           * @attribute style
604           * @type {String}
605           * @default 'separate'
606           */
607          style : {
608             value : 'separate',
609             writeOnce : true,
610             validator : function (v) {
611                 return this._validateStyle(v);
612             }
613          }
614     }
615
616 });
617
618 Y.extend(Console,Y.Widget,{
619
620     /**
621      * Category to prefix all event subscriptions to allow for ease of detach
622      * during destroy.
623      *
624      * @property _evtCat
625      * @type string
626      * @protected
627      */
628     _evtCat : null,
629
630     /**
631      * Reference to the Node instance containing the header contents.
632      *
633      * @property _head
634      * @type Node
635      * @default null
636      * @protected
637      */
638     _head    : null,
639
640     /**
641      * Reference to the Node instance that will house the console messages.
642      *
643      * @property _body
644      * @type Node
645      * @default null
646      * @protected
647      */
648     _body    : null,
649
650     /**
651      * Reference to the Node instance containing the footer contents.
652      *
653      * @property _foot
654      * @type Node
655      * @default null
656      * @protected
657      */
658     _foot    : null,
659
660     /**
661      * Holds the object API returned from <code>Y.later</code> for the print
662      * loop interval.
663      *
664      * @property _printLoop
665      * @type Object
666      * @default null
667      * @protected
668      */
669     _printLoop : null,
670
671     /**
672      * Array of normalized message objects awaiting printing.
673      *
674      * @property buffer
675      * @type Array
676      * @default null
677      * @protected
678      */
679     buffer   : null,
680
681     /**
682      * Wrapper for <code>Y.log</code>.
683      *
684      * @method log
685      * @param arg* {MIXED} (all arguments passed through to <code>Y.log</code>)
686      * @chainable
687      */
688     log : function () {
689         Y.log.apply(Y,arguments);
690
691         return this;
692     },
693
694     /**
695      * Clear the console of messages and flush the buffer of pending messages.
696      *
697      * @method clearConsole
698      * @chainable
699      */
700     clearConsole : function () {
701         // TODO: clear event listeners from console contents
702         this._body.set(INNER_HTML,'');
703
704         this._cancelPrintLoop();
705
706         this.buffer = [];
707
708         return this;
709     },
710
711     /**
712      * Clears the console and resets internal timers.
713      *
714      * @method reset
715      * @chainable
716      */
717     reset : function () {
718         this.fire(RESET);
719         
720         return this;
721     },
722
723     /**
724      * Collapses the body and footer.
725      *
726      * @method collapse
727      * @chainable
728      */
729     collapse : function () {
730         this.set(COLLAPSED, true);
731
732         return this;
733     },
734
735     /**
736      * Expands the body and footer if collapsed.
737      *
738      * @method expand
739      * @chainable
740      */
741     expand : function () {
742         this.set(COLLAPSED, false);
743
744         return this;
745     },
746
747     /**
748      * Outputs buffered messages to the console UI.  This is typically called
749      * from a scheduled interval until the buffer is empty (referred to as the
750      * print loop).  The number of buffered messages output to the Console is
751      * limited to the number provided as an argument.  If no limit is passed,
752      * all buffered messages are rendered.
753      * 
754      * @method printBuffer
755      * @param limit {Number} (optional) max number of buffered entries to write
756      * @chainable
757      */
758     printBuffer: function (limit) {
759         var messages    = this.buffer,
760             debug       = Y.config.debug,
761             entries     = [],
762             consoleLimit= this.get('consoleLimit'),
763             newestOnTop = this.get('newestOnTop'),
764             anchor      = newestOnTop ? this._body.get('firstChild') : null,
765             i;
766
767         if (messages.length > consoleLimit) {
768             messages.splice(0, messages.length - consoleLimit);
769         }
770
771         limit = Math.min(messages.length, (limit || messages.length));
772         
773         // turn off logging system
774         Y.config.debug = false;
775
776         if (!this.get(PAUSED) && this.get('rendered')) {
777
778             for (i = 0; i < limit && messages.length; ++i) {
779                 entries[i] = this._createEntryHTML(messages.shift());
780             }
781
782             if (!messages.length) {
783                 this._cancelPrintLoop();
784             }
785
786             if (entries.length) {
787                 if (newestOnTop) {
788                     entries.reverse();
789                 }
790
791                 this._body.insertBefore(create(entries.join('')), anchor);
792
793                 if (this.get('scrollIntoView')) {
794                     this.scrollToLatest();
795                 }
796
797                 this._trimOldEntries();
798             }
799         }
800
801         // restore logging system
802         Y.config.debug = debug;
803
804         return this;
805     },
806
807     
808     /**
809      * Constructor code.  Set up the buffer and entry template, publish
810      * internal events, and subscribe to the configured logEvent.
811      * 
812      * @method initializer
813      * @protected
814      */
815     initializer : function () {
816         this._evtCat = Y.stamp(this) + '|';
817
818         this.buffer = [];
819
820         this.get('logSource').on(this._evtCat +
821             this.get('logEvent'),Y.bind("_onLogEvent",this));
822
823         /**
824          * Transfers a received message to the print loop buffer.  Default
825          * behavior defined in _defEntryFn.
826          *
827          * @event entry
828          * @param event {Event.Facade} An Event Facade object with the following attribute specific properties added:
829          *  <dl>
830          *      <dt>message</dt>
831          *          <dd>The message data normalized into an object literal (see _normalizeMessage)</dd>
832          *  </dl>
833          * @preventable _defEntryFn
834          */
835         this.publish(ENTRY, { defaultFn: this._defEntryFn });
836
837         /**
838          * Triggers the reset behavior via the default logic in _defResetFn.
839          *
840          * @event reset
841          * @param event {Event.Facade} Event Facade object
842          * @preventable _defResetFn
843          */
844         this.publish(RESET, { defaultFn: this._defResetFn });
845
846         this.after('rendered', this._schedulePrint);
847     },
848
849     /**
850      * Tears down the instance, flushing event subscriptions and purging the UI.
851      *
852      * @method destructor
853      * @protected
854      */
855     destructor : function () {
856         var bb = this.get('boundingBox');
857
858         this._cancelPrintLoop();
859
860         this.get('logSource').detach(this._evtCat + '*');
861         
862         Y.Event.purgeElement(bb, true);
863
864         bb.set('innerHTML','');
865     },
866
867     /**
868      * Generate the Console UI.
869      *
870      * @method renderUI
871      * @protected
872      */
873     renderUI : function () {
874         this._initHead();
875         this._initBody();
876         this._initFoot();
877
878         // Apply positioning to the bounding box if appropriate
879         var style = this.get('style');
880         if (style !== 'block') {
881             this.get('boundingBox').addClass('yui-'+style+'-console');
882         }
883     },
884
885     /**
886      * Sync the UI state to the current attribute state.
887      *
888      * @method syncUI
889      */
890     syncUI : function () {
891         this._uiUpdatePaused(this.get(PAUSED));
892         this._uiUpdateCollapsed(this.get(COLLAPSED));
893         this._uiSetHeight(this.get(HEIGHT));
894     },
895
896     /**
897      * Set up event listeners to wire up the UI to the internal state.
898      *
899      * @method bindUI
900      * @protected
901      */
902     bindUI : function () {
903         this.get(CONTENT_BOX).query('button.'+C_COLLAPSE).
904             on(CLICK,this._onCollapseClick,this);
905
906         this.get(CONTENT_BOX).query('input[type=checkbox].'+C_PAUSE).
907             on(CLICK,this._onPauseClick,this);
908
909         this.get(CONTENT_BOX).query('button.'+C_CLEAR).
910             on(CLICK,this._onClearClick,this);
911         
912         // Attribute changes
913         this.after(this._evtCat + 'stringsChange',
914             this._afterStringsChange);
915         this.after(this._evtCat + 'pausedChange',
916             this._afterPausedChange);
917         this.after(this._evtCat + 'consoleLimitChange',
918             this._afterConsoleLimitChange);
919         this.after(this._evtCat + 'collapsedChange',
920             this._afterCollapsedChange);
921     },
922
923     
924     /**
925      * Create the DOM structure for the header elements.
926      *
927      * @method _initHead
928      * @protected
929      */
930     _initHead : function () {
931         var cb   = this.get(CONTENT_BOX),
932             info = merge(Console.CHROME_CLASSES, {
933                         str_collapse : this.get('strings.collapse'),
934                         str_title : this.get('strings.title')
935                     });
936
937         this._head = create(substitute(Console.HEADER_TEMPLATE,info));
938
939         cb.insertBefore(this._head,cb.get('firstChild'));
940     },
941
942     /**
943      * Create the DOM structure for the console body&#8212;where messages are
944      * rendered.
945      *
946      * @method _initBody
947      * @protected
948      */
949     _initBody : function () {
950         this._body = create(substitute(
951                             Console.BODY_TEMPLATE,
952                             Console.CHROME_CLASSES));
953
954         this.get(CONTENT_BOX).appendChild(this._body);
955     },
956
957     /**
958      * Create the DOM structure for the footer elements.
959      *
960      * @method _initFoot
961      * @protected
962      */
963     _initFoot : function () {
964         var info = merge(Console.CHROME_CLASSES, {
965                 id_guid   : Y.guid(),
966                 str_pause : this.get('strings.pause'),
967                 str_clear : this.get('strings.clear')
968             });
969
970         this._foot = create(substitute(Console.FOOTER_TEMPLATE,info));
971
972         this.get(CONTENT_BOX).appendChild(this._foot);
973     },
974
975     /**
976      * Determine if incoming log messages are within the configured logLevel
977      * to be buffered for printing.
978      *
979      * @method _isInLogLevel
980      * @protected
981      */
982     _isInLogLevel : function (e) {
983         var cat = e.cat, lvl = this.get('logLevel');
984
985         if (lvl !== INFO) {
986             cat = cat || INFO;
987
988             if (isString(cat)) {
989                 cat = cat.toLowerCase();
990             }
991
992             if ((cat === WARN && lvl === ERROR) ||
993                 (cat === INFO && lvl !== INFO)) {
994                 return false;
995             }
996         }
997
998         return true;
999     },
1000
1001     /**
1002      * Create a log entry message from the inputs including the following keys:
1003      * <ul>
1004      *     <li>time - this moment</li>
1005      *     <li>message - leg message</li>
1006      *     <li>category - logLevel or custom category for the message</li>
1007      *     <li>source - when provided, the widget or util calling Y.log</li>
1008      *     <li>sourceAndDetail - same as source but can include instance info</li>
1009      *     <li>localTime - readable version of time</li>
1010      *     <li>elapsedTime - ms since last entry</li>
1011      *     <li>totalTime - ms since Console was instantiated or reset</li>
1012      * </ul>
1013      *
1014      * @method _normalizeMessage
1015      * @param e {Event} custom event containing the log message
1016      * @return Object the message object
1017      * @protected
1018      */
1019     _normalizeMessage : function (e) {
1020
1021         var msg = e.msg,
1022             cat = e.cat,
1023             src = e.src,
1024
1025             m = {
1026                 time            : new Date(),
1027                 message         : msg,
1028                 category        : cat || this.get('defaultCategory'),
1029                 sourceAndDetail : src || this.get('defaultSource'),
1030                 source          : null,
1031                 localTime       : null,
1032                 elapsedTime     : null,
1033                 totalTime       : null
1034             };
1035
1036         // Extract m.source "Foo" from m.sourceAndDetail "Foo bar baz"
1037         m.source          = RE_INLINE_SOURCE.test(m.sourceAndDetail) ?
1038                                 RegExp.$1 : m.sourceAndDetail;
1039         m.localTime       = m.time.toLocaleTimeString ? 
1040                             m.time.toLocaleTimeString() : (m.time + '');
1041         m.elapsedTime     = m.time - this.get(LAST_TIME);
1042         m.totalTime       = m.time - this.get(START_TIME);
1043
1044         this._set(LAST_TIME,m.time);
1045
1046         return m;
1047     },
1048
1049     /**
1050      * Sets an interval for buffered messages to be output to the console.
1051      *
1052      * @method _schedulePrint
1053      * @protected
1054      */
1055     _schedulePrint : function () {
1056         if (!this._printLoop && !this.get(PAUSED) && this.get('rendered')) {
1057             this._printLoop = Y.later(
1058                                 this.get('printTimeout'),
1059                                 this, this.printBuffer,
1060                                 this.get('printLimit'), true);
1061         }
1062     },
1063
1064     /**
1065      * Translates message meta into the markup for a console entry.
1066      *
1067      * @method _createEntryHTML
1068      * @param m {Object} object literal containing normalized message metadata
1069      * @return String
1070      * @protected
1071      */
1072     _createEntryHTML : function (m) {
1073         m = merge(
1074                 this._htmlEscapeMessage(m),
1075                 Console.ENTRY_CLASSES,
1076                 {
1077                     cat_class : this.getClassName(ENTRY,m.category),
1078                     src_class : this.getClassName(ENTRY,m.source)
1079                 });
1080
1081         return this.get('entryTemplate').replace(/\{(\w+)\}/g,
1082             function (_,token) {
1083                 return token in m ? m[token] : '';
1084             });
1085     },
1086
1087     /**
1088      * Scrolls to the most recent entry
1089      *
1090      * @method scrollToLatest
1091      * @chainable
1092      */
1093     scrollToLatest : function () {
1094         var scrollTop = this.get('newestOnTop') ?
1095                             0 :
1096                             this._body.get('scrollHeight');
1097
1098         this._body.set('scrollTop', scrollTop);
1099     },
1100
1101     /**
1102      * Performs HTML escaping on strings in the message object.
1103      *
1104      * @method _htmlEscapeMessage
1105      * @param m {Object} the normalized message object
1106      * @return Object the message object with proper escapement
1107      * @protected
1108      */
1109     _htmlEscapeMessage : function (m) {
1110         m.message         = this._encodeHTML(m.message);
1111         m.source          = this._encodeHTML(m.source);
1112         m.sourceAndDetail = this._encodeHTML(m.sourceAndDetail);
1113         m.category        = this._encodeHTML(m.category);
1114
1115         return m;
1116     },
1117
1118     /**
1119      * Removes the oldest message entries from the UI to maintain the limit
1120      * specified in the consoleLimit configuration.
1121      *
1122      * @method _trimOldEntries
1123      * @protected
1124      */
1125     _trimOldEntries : function () {
1126         // Turn off the logging system for the duration of this operation
1127         // to prevent an infinite loop
1128         Y.config.debug = false;
1129
1130         var bd = this._body,
1131             limit = this.get('consoleLimit'),
1132             debug = Y.config.debug,
1133             entries,e,i,l;
1134
1135         if (bd) {
1136             entries = bd.queryAll(DOT+C_ENTRY);
1137             l = entries.size() - limit;
1138
1139             if (l > 0) {
1140                 if (this.get('newestOnTop')) {
1141                     i = limit;
1142                     l = entries.size();
1143                 } else {
1144                     i = 0;
1145                 }
1146
1147                 this._body.setStyle('display','none');
1148
1149                 for (;i < l; ++i) {
1150                     e = entries.item(i);
1151                     if (e) {
1152                         e.remove();
1153                     }
1154                 }
1155
1156                 this._body.setStyle('display','');
1157             }
1158
1159         }
1160
1161         Y.config.debug = debug;
1162     },
1163
1164     /**
1165      * Returns the input string with ampersands (&amp;), &lt, and &gt; encoded
1166      * as HTML entities.
1167      *
1168      * @method _encodeHTML
1169      * @param s {String} the raw string
1170      * @return String the encoded string
1171      * @protected
1172      */
1173     _encodeHTML : function (s) {
1174         return isString(s) ?
1175             s.replace(RE_AMP,ESC_AMP).
1176               replace(RE_LT, ESC_LT).
1177               replace(RE_GT, ESC_GT) :
1178             s;
1179     },
1180
1181     /**
1182      * Clears the timeout for printing buffered messages.
1183      *
1184      * @method _cancelPrintLoop
1185      * @protected
1186      */
1187     _cancelPrintLoop : function () {
1188         if (this._printLoop) {
1189             this._printLoop.cancel();
1190             this._printLoop = null;
1191         }
1192     },
1193
1194     /**
1195      * Validates input value for style attribute.  Accepts only values 'inline',
1196      * 'block', and 'separate'.
1197      *
1198      * @method _validateStyle
1199      * @param style {String} the proposed value
1200      * @return {Boolean} pass/fail
1201      * @protected
1202      */
1203     _validateStyle : function (style) {
1204         return style === 'inline' || style === 'block' || style === 'separate';
1205     },
1206
1207     /**
1208      * Event handler for clicking on the Pause checkbox to update the paused
1209      * attribute.
1210      *
1211      * @method _onPauseClick
1212      * @param e {Event} DOM event facade for the click event
1213      * @protected
1214      */
1215     _onPauseClick : function (e) {
1216         this.set(PAUSED,e.target.get(CHECKED));
1217     },
1218
1219     /**
1220      * Event handler for clicking on the Clear button.  Pass-through to
1221      * <code>this.clearConsole()</code>.
1222      *
1223      * @method _onClearClick
1224      * @param e {Event} DOM event facade for the click event
1225      * @protected
1226      */
1227     _onClearClick : function (e) {
1228         this.clearConsole();
1229     },
1230
1231     /**
1232      * Event handler for clicking on the Collapse/Expand button. Sets the
1233      * &quot;collapsed&quot; attribute accordingly.
1234      *
1235      * @method _onCollapseClick
1236      * @param e {Event} DOM event facade for the click event
1237      * @protected
1238      */
1239     _onCollapseClick : function (e) {
1240         this.set(COLLAPSED, !this.get(COLLAPSED));
1241     },
1242
1243
1244     /**
1245      * Setter method for logLevel attribute.  Acceptable values are
1246      * &quot;error&quot, &quot;warn&quot, and &quot;info&quot (case
1247      * insensitive).  Other values are treated as &quot;info&quot;.
1248      *
1249      * @method _setLogLevel
1250      * @param v {String} the desired log level
1251      * @return String One of Console.LOG_LEVEL_INFO, _WARN, or _ERROR
1252      * @protected
1253      */
1254     _setLogLevel : function (v) {
1255         if (isString(v)) {
1256             v = v.toLowerCase();
1257         }
1258         
1259         return (v === WARN || v === ERROR) ? v : INFO;
1260     },
1261
1262     /**
1263      * Set the height of the Console container.  Set the body height to the difference between the configured height and the calculated heights of the header and footer.
1264      * Overrides Widget.prototype._uiSetHeight.
1265      *
1266      * @method _uiSetHeight
1267      * @param v {String|Number} the new height
1268      * @protected
1269      */
1270     _uiSetHeight : function (v) {
1271         Console.superclass._uiSetHeight.apply(this,arguments);
1272
1273         if (this._head && this._foot) {
1274             var h = this.get('boundingBox').get('offsetHeight') -
1275                     this._head.get('offsetHeight') -
1276                     this._foot.get('offsetHeight');
1277
1278             this._body.setStyle(HEIGHT,h+'px');
1279         }
1280     },
1281
1282     /**
1283      * Updates the UI if changes are made to any of the strings in the strings
1284      * attribute.
1285      *
1286      * @method _afterStringsChange
1287      * @param e {Event} Custom event for the attribute change
1288      * @protected
1289      */
1290     _afterStringsChange : function (e) {
1291         var prop   = e.subAttrName ? e.subAttrName.split(DOT)[1] : null,
1292             cb     = this.get(CONTENT_BOX),
1293             before = e.prevVal,
1294             after  = e.newVal;
1295
1296         if ((!prop || prop === TITLE) && before.title !== after.title) {
1297             cb.queryAll(DOT+C_CONSOLE_TITLE).set(INNER_HTML, after.title);
1298         }
1299
1300         if ((!prop || prop === PAUSE) && before.pause !== after.pause) {
1301             cb.queryAll(DOT+C_PAUSE_LABEL).set(INNER_HTML, after.pause);
1302         }
1303
1304         if ((!prop || prop === CLEAR) && before.clear !== after.clear) {
1305             cb.queryAll(DOT+C_CLEAR).set('value',after.clear);
1306         }
1307     },
1308
1309     /**
1310      * Updates the UI and schedules or cancels the print loop.
1311      *
1312      * @method _afterPausedChange
1313      * @param e {Event} Custom event for the attribute change
1314      * @protected
1315      */
1316     _afterPausedChange : function (e) {
1317         var paused = e.newVal;
1318
1319         if (e.src !== Y.Widget.SRC_UI) {
1320             this._uiUpdatePaused(paused);
1321         }
1322
1323         if (!paused) {
1324             this._schedulePrint();
1325         } else if (this._printLoop) {
1326             this._cancelPrintLoop();
1327         }
1328     },
1329
1330     /**
1331      * Checks or unchecks the paused checkbox
1332      *
1333      * @method _uiUpdatePaused
1334      * @param on {Boolean} the new checked state
1335      * @protected
1336      */
1337     _uiUpdatePaused : function (on) {
1338         var node = this._foot.queryAll('input[type=checkbox].'+C_PAUSE);
1339
1340         if (node) {
1341             node.set(CHECKED,on);
1342         }
1343     },
1344
1345     /**
1346      * Calls this._trimOldEntries() in response to changes in the configured
1347      * consoleLimit attribute.
1348      * 
1349      * @method _afterConsoleLimitChange
1350      * @param e {Event} Custom event for the attribute change
1351      * @protected
1352      */
1353     _afterConsoleLimitChange : function () {
1354         this._trimOldEntries();
1355     },
1356
1357
1358     /**
1359      * Updates the className of the contentBox, which should trigger CSS to
1360      * hide or show the body and footer sections depending on the new value.
1361      *
1362      * @method _afterCollapsedChange
1363      * @param e {Event} Custom event for the attribute change
1364      * @protected
1365      */
1366     _afterCollapsedChange : function (e) {
1367         this._uiUpdateCollapsed(e.newVal);
1368     },
1369
1370     /**
1371      * Updates the UI to reflect the new Collapsed state
1372      *
1373      * @method _uiUpdateCollapsed
1374      * @param v {Boolean} true for collapsed, false for expanded
1375      * @protected
1376      */
1377     _uiUpdateCollapsed : function (v) {
1378         var bb     = this.get('boundingBox'),
1379             button = bb.queryAll('button.'+C_COLLAPSE),
1380             method = v ? 'addClass' : 'removeClass',
1381             str    = this.get('strings.'+(v ? 'expand' : 'collapse'));
1382
1383         bb[method](C_COLLAPSED);
1384
1385         if (button) {
1386             button.set('innerHTML',str);
1387         }
1388
1389         this._uiSetHeight(v ? this._head.get('offsetHeight'): this.get(HEIGHT));
1390     },
1391
1392     /**
1393      * Makes adjustments to the UI if needed when the Console is hidden or shown
1394      *
1395      * @method _afterVisibleChange
1396      * @param e {Event} the visibleChange event
1397      * @protected
1398      */
1399     _afterVisibleChange : function (e) {
1400         Console.superclass._afterVisibleChange.apply(this,arguments);
1401
1402         this._uiUpdateFromHideShow(e.newVal);
1403     },
1404
1405     /**
1406      * Recalculates dimensions and updates appropriately when shown
1407      *
1408      * @method _uiUpdateFromHideShow
1409      * @param v {Boolean} true for visible, false for hidden
1410      * @protected
1411      */
1412     _uiUpdateFromHideShow : function (v) {
1413         if (v) {
1414             this._uiSetHeight(this.get(HEIGHT));
1415         }
1416     },
1417
1418     /**
1419      * Responds to log events by normalizing qualifying messages and passing
1420      * them along through the entry event for buffering etc.
1421      * 
1422      * @method _onLogEvent
1423      * @param msg {String} the log message
1424      * @param cat {String} OPTIONAL the category or logLevel of the message
1425      * @param src {String} OPTIONAL the source of the message (e.g. widget name)
1426      * @protected
1427      */
1428     _onLogEvent : function (e) {
1429
1430         if (!this.get(DISABLED) && this._isInLogLevel(e)) {
1431
1432             var debug = Y.config.debug;
1433
1434             /* TODO: needed? */
1435             Y.config.debug = false;
1436
1437             this.fire(ENTRY, {
1438                 message : this._normalizeMessage(e)
1439             });
1440
1441             Y.config.debug = debug;
1442         }
1443     },
1444
1445     /**
1446      * Clears the console, resets the startTime attribute, enables and
1447      * unpauses the widget.
1448      *
1449      * @method _defResetFn
1450      * @protected
1451      */
1452     _defResetFn : function () {
1453         this.clearConsole();
1454         this.set(START_TIME,new Date());
1455         this.set(DISABLED,false);
1456         this.set(PAUSED,false);
1457     },
1458
1459     /**
1460      * Buffers incoming message objects and schedules the printing.
1461      *
1462      * @method _defEntryFn
1463      * @param e {Event} The Custom event carrying the message in its payload
1464      * @protected
1465      */
1466     _defEntryFn : function (e) {
1467         if (e.message) {
1468             this.buffer.push(e.message);
1469             this._schedulePrint();
1470         }
1471     }
1472
1473 });
1474
1475 Y.Console = Console;
1476
1477
1478 }, '3.0.0' ,{requires:['substitute','widget']});